you monad learn haskell monads typeclass

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 (>>) .