Muy bien, primera regla de manejo de errores en Haskell: nunca usarerror
.
Es terrible en todos los sentidos. Existe únicamente como un acto de la historia y el hecho de que Prelude lo use es terrible. No lo uses
El único momento concebible que podría usar es cuando algo es tan terriblemente interno que algo debe estar mal con la estructura misma de la realidad, lo que hace que el resultado de su programa sea discutible.
Ahora la pregunta es Maybe
vs Either
. Maybe
se adapta muy bien a algo como head
, que puede o no devolver un valor, pero solo hay una posible razón para fallar. Nothing
está diciendo algo como "se rompió, y ya sabes por qué". Algunos dirían que indica una función parcial.
La forma más robusta de manejo de errores es Either
+ un error ADT.
Por ejemplo, en uno de mis compiladores de pasatiempos, tengo algo como
data CompilerError = ParserError ParserError
| TCError TCError
...
| ImpossibleError String
data ParserError = ParserError (Int, Int) String
data TCError = CouldntUnify Ty Ty
| MissingDefinition Name
| InfiniteType Ty
...
type ErrorM m = ExceptT CompilerError m -- from MTL
Ahora defino un montón de tipos de error, anidándolos para tener un glorioso error de nivel superior. Esto puede ser un error de cualquier etapa de la compilación o ImpossibleError
, lo que significa un error del compilador.
Cada uno de estos tipos de error intenta mantener la mayor cantidad de información posible durante una impresión bonita u otro análisis. Más importante aún, al no tener una cadena, ¡puedo probar que ejecutar un programa mal escrito a través del verificador de tipo realmente genera un error de unificación! Una vez que algo es un String
, desaparece para siempre y cualquier información que contenga es opaca para el compilador / pruebas, por lo Either String
que tampoco es genial.
Finalmente empaque este tipo en ExceptT
un nuevo transformador de mónada de MTL. Esto es esencialmente EitherT
y viene con un buen lote de funciones para lanzar y atrapar errores de una manera pura y agradable.
Finalmente, vale la pena mencionar que Haskell tiene los mecanismos para admitir el manejo de excepciones como lo hacen otros idiomas, excepto que la captura de una excepción vive IO
. Sé que a algunas personas les gusta usarlas para IO
aplicaciones pesadas en las que todo podría fallar, pero con tan poca frecuencia que no les gusta pensar en ello. Si usa estas excepciones impuras o simplemente ExceptT Error IO
es realmente una cuestión de gustos. Personalmente, opto ExceptT
porque me gusta que me recuerden la posibilidad de fracaso.
Como un resumen,
Maybe
- Puedo fallar de una manera obvia
Either CustomType
- Puedo fallar y te diré lo que pasó
IO
+ excepciones: a veces fallo. Revisa mis documentos para ver qué arrojo cuando lo hago
error
- Yo también te odio usuario
head
olast
parecen usarerror
, por lo que me preguntaba si esa era realmente una buena manera de hacer las cosas y simplemente me faltaba algo. Esto responde a esa pregunta. :)No separaría 2 y 3 en función de cuántas formas puede fallar algo. Tan pronto como creas que solo hay una forma posible, otra mostrará su rostro. En cambio, la métrica debería ser "¿les importa a mis llamantes por qué algo falló? ¿Pueden realmente hacer algo al respecto?".
Más allá de eso, no me queda claro de inmediato que
Either String SomeType
pueda producir una condición de error. Haría un tipo de datos algebraico simple con las condiciones con un nombre más descriptivo.Lo que use depende de la naturaleza del problema que enfrenta y de los modismos del paquete de software con el que está trabajando. Aunque tendería a evitar el n. ° 1.
fuente
Either
de errores es un patrón muy conocido en Haskell.Either
no es la parte poco clara, el uso deString
un error en lugar de una cadena real.