haskell - learn - Usos apropiados de Monad `fail` vs. MonadPlus` mzero`
monad haskell (2)
Esta es una pregunta que me ha surgido varias veces en el código de diseño, especialmente en las bibliotecas. Parece que hay cierto interés en él, así que pensé que podría ser una buena wiki comunitaria.
El método de fail
en la Mónada es considerado por algunos como una verruga; una adición algo arbitraria a la clase que no proviene de la teoría de la categoría original. Pero, por supuesto, en el estado actual de las cosas, muchos tipos de mónada tienen instancias de fail
lógicas y útiles.
La clase MonadPlus es una subclase de la mónada que proporciona un método mzero
que lógicamente encapsula la idea de falla en una mónada.
Por lo tanto, un diseñador de bibliotecas que quiera escribir algún código monádico que realice algún tipo de manejo de fallas puede optar por hacer que su código use el método fail
en Monad o restringir su código a la clase MonadPlus, solo para que pueda sentirse bien al usar mzero
. a pesar de que no le importa la operación monoidal que combina mplus
en absoluto.
Algunas discusiones sobre este tema se encuentran en esta página wiki sobre propuestas para reformar la clase MonadPlus.
Así que supongo que tengo una pregunta específica:
¿Qué instancias de mónada, si las hay, tienen un método de fail
natural, pero no pueden ser instancias de MonadPlus porque no tienen una implementación lógica para mplus
?
Pero estoy más interesado en una discusión sobre este tema. ¡Gracias!
EDIT : un último pensamiento se me ocurrió. Recientemente aprendí (aunque está justo ahí en los documentos para fail
) que la notación monádica de "do" está desugarada de tal manera que los patrones coinciden con fallas, como en (x:xs) <- return []
llama a fail
la monada.
Parece que los diseñadores de lenguajes deben haber estado fuertemente influenciados por la posibilidad de algún manejo automático de fallas incorporado a la sintaxis de haskell en su inclusión de fail
en la Monad.
En esta respuesta, quiero discutir el tema, ¿por qué fail
es un miembro de Monad
? No quiero agregar esto a mi otra respuesta , ya que cubre otro tema.
Aunque la definición matemática de una mónada no contiene fail
, los creadores de Haskell 98 la colocaron en la clase de tipos de Monad
. ¿Por qué?
Con el fin de facilitar el uso de las mónadas y hacer más fácil abstraer el uso de las mónadas, introdujeron la notación do
, una pieza de azúcar muy útil. Por ejemplo, este código:
do putStr "What''s your full name? "
[name,surname] <- getLine >>= return . words
putStr "How old are you? "
age <- getLine >>= return . read
if age >= 18
then putStrLn $ "Hello Mr / Ms " ++ surname
else putStrLn $ "Hello " ++ name
se traduce a
putStr "What''s your full name? " >>
getLine >>= return . words >>= /[name,surname] ->
putSr "How old are you? " >>
getLine >>= return . read >>= /age ->
if age >= 18
then putStrLn $ "Hello Mr / Ms " ++ surname
else putStrLn $ "Hello " ++ name
¿Cuál es el problema aquí? Imagina que tienes un nombre con un espacio en el medio, como Jon M. Doe . En este caso, todo el constructo será _|_
. Claro, puedes solucionar esto agregando alguna función ad-hoc con let
, pero esto es puramente repetitivo. En el momento en que se creó Haskell 98, no había un sistema de excepción como el de hoy, en el que simplemente se podía detectar la coincidencia de patrones fallida. Además, los patrones incompletos se consideran mal estilo de codificación.
¿Cual es la solución? Los creadores de Haskell 98 agregaron una función especial de fail
, llamada en el caso de una falta de coincidencia. El desugaring se ve algo así:
putStr "What''s your full name? " >> let
helper1 [name,surname] =
putSr "How old are you? " >> let
helper2 age =
if age >= 18
then putStrLn $ "Hello Mr / Ms " ++ surname
else putStrLn $ "Hello " ++ name
helper2 _ = fail "..."
in getLine >>= return . read >>= helper2
helper1 _ = fail "..."
in getLine >>= return . words >>= helper1
(No estoy seguro de si realmente hay un helper2
, pero creo que sí)
Si miras esto dos veces, descubrirás lo inteligente que es. Primero, nunca hay una coincidencia de patrón incompleta, y segundo, puede hacer que el fail
configurable. Para lograr esto, simplemente ponen fail
en la definición de mónadas. Por ejemplo, para Maybe
, fail
es simplemente Nothing
y para la instancia de Either String
, se deja. De esta manera, es fácil escribir código monádico independiente de la mónada. Por ejemplo, durante mucho tiempo, la lookup
se definió como (Eq a,Monad b) => a -> [(a, b)] -> mb
, y la lookup
devolvió un fail
si no había una coincidencia.
Ahora, todavía hay una gran pregunta en la comunidad de Haskell: ¿no es una mala idea agregar algo completamente independiente a la clase de tipos Monad? No puedo responder a esta pregunta, pero en mi humilde opinión esta decisión fue correcta, ya que otros lugares no son tan cómodos para fail
.
Piense en Either
de las Either
. Su instancia monádica se ve así:
{-# LANGUAGE FlexibleInstances #-}
instance Monad (Either String) where
(Left x) >>= _ = Left x
(Right a) >>= f = f a
return = Right
fail = Left
(Necesitamos FlexibleInstances para permitir una instancia como Either String
)
Así que es básicamente como Maybe
con un mensaje de error opcional si algo sucede. No puede volver a crear esto con mzero
, ya que no puede agregar un mensaje de error al error. Es algo diferente de fail
.
Cada instancia de mplus
debe satisfacer estas dos leyes:
mzero `mplus` a -> a
a `mplus` mzero -> a
Simple, ¿no es así? Pero estas leyes hacen que mplus
especial. Con ellos, es posible escribir una instancia de MonadPlus
razonable para ello:
instance MonadPlus (Either a) where
mzero = Left undefined
mplus (Left _) b = b
mplus a _ = a
¿Que es esto? Representa una elección. Si el primer cálculo es exitoso, será devuelto. Si no, mplus
devuelve el segundo cálculo. Observe en qué se diferencia de (>>)
, que no cumple con las leyes:
Left a >> Right b -> Left a
Left a `mplus` Right b -> Right b
(>>)
se detendrá en el primer cálculo, mientras que mplus
intentará el segundo en su lugar. []
también se comporta así:
[] >> [1..4] -> []
[] `mplus` [1..4] -> [1,2,3,4]
Esto es solo para discutir los aspectos de MonadPlus
y especialmente el aspecto de mplus
en contraste con (>>)
.