El uso Maybe
(o su primo Either
que funciona básicamente de la misma manera pero le permite devolver un valor arbitrario en lugar de Nothing
) tiene un propósito ligeramente diferente al de las excepciones. En términos de Java, es como tener una excepción marcada en lugar de una excepción de tiempo de ejecución. Representa algo esperado que hay que tratar, en lugar de un error que no esperaba.
Entonces, una función como indexOf
devolvería un Maybe
valor porque espera la posibilidad de que el elemento no esté en la lista. Esto es muy parecido a regresar null
de una función, excepto en una forma de tipo seguro que lo obliga a lidiar con el null
caso. Either
funciona de la misma manera, excepto que puede devolver información asociada con el caso de error, por lo que en realidad es más similar a una excepción que Maybe
.
¿Cuáles son las ventajas del enfoque Maybe
/ Either
? Por un lado, es un ciudadano de primera clase del idioma. Comparemos una función con Either
una que arroje una excepción. Para el caso de excepción, su único recurso real es una try...catch
declaración. Para la Either
función, puede usar los combinadores existentes para aclarar el control de flujo. Aquí hay un par de ejemplos:
Primero, supongamos que desea probar varias funciones que podrían generar errores en una fila hasta que obtenga una que no lo haga. Si no obtiene ninguno sin errores, desea devolver un mensaje de error especial. Este es en realidad un patrón muy útil, pero sería un dolor horrible usarlo try...catch
. Afortunadamente, dado que Either
es solo un valor normal, puede usar las funciones existentes para que el código sea mucho más claro:
firstThing <|> secondThing <|> throwError (SomeError "error message")
Otro ejemplo es tener una función opcional. Supongamos que tiene varias funciones para ejecutar, incluida una que intenta optimizar una consulta. Si esto falla, desea que todo lo demás se ejecute de todos modos. Podrías escribir código como:
do a <- getA
b <- getB
optional (optimize query)
execute query a b
Ambos casos son más claros y cortos que el uso try..catch
y, lo que es más importante, más semántico. Usar una función como <|>
o optional
hace que sus intenciones sean mucho más claras que usar try...catch
para manejar siempre las excepciones.
También tenga en cuenta que no tiene que llenar su código con líneas como if a == Nothing then Nothing else ...
! El objetivo de tratar Maybe
y Either
como mónada es evitar esto. Puede codificar la semántica de propagación en la función de enlace para obtener las comprobaciones de nulo / error de forma gratuita. El único momento en el que tiene que verificar explícitamente es si desea devolver algo distinto de Nothing
un dado Nothing
, e incluso así es fácil: hay un montón de funciones de biblioteca estándar para hacer que ese código sea más agradable.
Finalmente, otra ventaja es que a Maybe
/ Either
type es simplemente más simple. No es necesario extender el lenguaje con palabras clave adicionales o estructuras de control; todo es solo una biblioteca. Dado que son solo valores normales, hace que el sistema de tipos sea más simple: en Java, debe diferenciar entre tipos (por ejemplo, el tipo de retorno) y efectos (por ejemplo, throws
declaraciones) donde no usaría Maybe
. También se comportan como cualquier otro tipo definido por el usuario: no es necesario tener un código especial de manejo de errores integrado en el idioma.
Otra victoria es que Maybe
/ Either
son functors y mónadas, lo que significa que pueden aprovechar las funciones de flujo de control de mónada existentes (de las cuales hay un número justo) y, en general, jugar muy bien junto con otras mónadas.
Dicho esto, hay algunas advertencias. Por un lado, Maybe
ni Either
reemplazar las excepciones no marcadas. Querrá otra forma de manejar cosas como dividir entre 0 simplemente porque sería una molestia que cada división devuelva un Maybe
valor.
Otro problema es la devolución de múltiples tipos de errores (esto solo se aplica a Either
). Con excepciones, puede lanzar cualquier tipo diferente de excepciones en la misma función. con Either
, solo obtienes un tipo. Esto se puede superar con subtipado o un ADT que contiene todos los diferentes tipos de errores como constructores (este segundo enfoque es el que se usa generalmente en Haskell).
Aún así, sobre todo, prefiero el enfoque Maybe
/ Either
porque lo encuentro más simple y más flexible.
OpenFile()
puede lanzarFileNotFound
oNoPermission
oTooManyDescriptors
etc. A Ninguno no lleva esta información.if None return None
declaraciones de estilo.Lo más importante de todo es que una excepción y una mónada tal vez tienen diferentes propósitos: una excepción se usa para indicar un problema, mientras que una tal vez no.
"Enfermera, si hay un paciente en la habitación 5, ¿puedes pedirle que espere?"
(observe el "si", esto significa que el médico espera una Mónada Quizás)
fuente
None
los valores pueden simplemente ser propagado). Su punto 5 es único tipo de derecho ... la cuestión es: ¿qué situaciones son inequívocamente excepcional? Pues resulta que ... no muchos .bind
de una manera tal que las pruebas paraNone
no incurrir en una sobrecarga sintáctica sin embargo. Un ejemplo muy simple, C # simplemente sobrecarga losNullable
operadores adecuadamente. Sin comprobación deNone
necesaria, incluso cuando se utiliza el tipo. Por supuesto, el cheque se sigue haciendo (que es un tipo seguro), pero detrás de las escenas y no el desorden de su código. Lo mismo se aplica en cierto sentido a su objeción a mi objeción a (5), pero estoy de acuerdo en que no siempre se pueden aplicar.Maybe
como una mónada es hacer que la propagaciónNone
implícita. Esto significa que si desea volverNone
determinadoNone
, que no tienen que escribir ningún código especial. La única vez que necesite partido es que si quieres hacer algo especial enNone
. Nunca se necesitaif None then None
tipo de declaraciones.null
verificación exactamente así (por ejemploif Nothing then Nothing
) gratis porqueMaybe
es una mónada. Está codificado en la definición de bind (>>=
) paraMaybe
.Either
) que se comporta de la misma maneraMaybe
. Cambiar entre los dos es realmente bastante simple porque enMaybe
realidad es solo un caso especial deEither
. (En Haskell, se podría pensarMaybe
comoEither ()
.)"Quizás" no reemplaza las excepciones. Las excepciones están destinadas a ser utilizadas en casos excepcionales (por ejemplo: abrir una conexión db y el servidor db no está allí aunque debería estarlo). "Quizás" es para modelar una situación en la que puede o no tener un valor válido; digamos que está obteniendo un valor de un diccionario para una clave: puede estar allí o no; no hay nada "excepcional" en ninguno de estos resultados.
fuente
Respaldo la respuesta de Tikhon, pero creo que hay un punto práctico muy importante que todos faltan:
Either
mecanismo no está acoplado a hilos en absoluto.Entonces, algo que estamos viendo hoy en día en la vida real es que muchas soluciones de programación asíncrona están adoptando una variante del
Either
estilo de manejo de errores. Considere las promesas de Javascript , como se detalla en cualquiera de estos enlaces:El concepto de promesas le permite escribir código asincrónico como este (tomado del último enlace):
Básicamente, una promesa es un objeto que:
Básicamente, dado que el soporte de excepciones nativas del lenguaje no funciona cuando su cálculo está ocurriendo en varios subprocesos, una implementación prometedora debe proporcionar un mecanismo de manejo de errores, y estos resultan ser mónadas similares a los
Maybe
/Either
tipos de Haskell .fuente
El sistema de tipo Haskell requerirá que el usuario reconozca la posibilidad de un
Nothing
, mientras que los lenguajes de programación a menudo no requieren que se detecte una excepción. Eso significa que sabremos, en tiempo de compilación, que el usuario ha verificado un error.fuente
throws NPE
a cada firma ycatch(...) {throw ...}
a cada cuerpo de método. Pero sí creo que hay un mercado para lo verificado en el mismo sentido que con Maybe: la nulabilidad es opcional y se rastrea en el sistema de tipos.Tal vez la mónada es básicamente la misma que el uso de la comprobación de "nulo significa error" en la mayoría de los idiomas principales (excepto que requiere que se verifique el nulo), y tiene en gran medida las mismas ventajas y desventajas.
fuente
Maybe
números escribiendoa + b
sin la necesidad de verificarNone
, y el resultado es una vez más un valor opcional.Maybe
tipo , pero el usoMaybe
como mónada agrega azúcar de sintaxis que permite expresar la lógica nula de manera mucho más elegante.El manejo de excepciones puede ser un verdadero dolor para la factorización y las pruebas. Sé que Python proporciona una buena sintaxis "con" que le permite atrapar excepciones sin el bloque rígido "try ... catch". Pero en Java, por ejemplo, intente que los bloques catch sean grandes, repetitivos, ya sea detallados o extremadamente detallados, y difíciles de romper. Además de eso, Java agrega todo el ruido alrededor de las excepciones marcadas y no marcadas.
Si, en cambio, su mónada captura excepciones y las trata como una propiedad del espacio monádico (en lugar de alguna anomalía de procesamiento), entonces es libre de mezclar y combinar funciones que se unen a ese espacio independientemente de lo que arrojen o atrapen.
Si, mejor aún, su mónada previene condiciones en las que podrían ocurrir excepciones (como pasar un cheque nulo a Quizás), entonces aún mejor. si ... entonces es mucho más fácil factorizar y probar que intentar ... atrapar.
Por lo que he visto, Go está adoptando un enfoque similar al especificar que cada función devuelve (respuesta, error). Es lo mismo que "elevar" la función a un espacio de mónada donde el tipo de respuesta central está decorado con una indicación de error y, de manera efectiva, eludir y atrapar excepciones.
fuente