monad haskell functional-programming monads monad-transformers applicative

haskell monad operators



Los solicitantes componen, las mónadas no (5)

Los solicitantes componen, las mónadas no.

¿Qué significa la declaración anterior? ¿Y cuándo es preferible uno a otro?


Desafortunadamente, nuestro objetivo real, la composición de las mónadas, es bastante más difícil. De hecho, podemos probar que, en cierto sentido, no hay forma de construir una función de unión con el tipo anterior utilizando solo las operaciones de las dos mónadas (ver el apéndice para un resumen de la prueba). Se deduce que la única forma en que podemos esperar formar una composición es si hay algunas construcciones adicionales que unen los dos componentes.

Componer mónadas, http://web.cecs.pdx.edu/~mpj/pubs/RR-1004.pdf


Los solicitantes componen, las mónadas no.

Las mónadas componen, pero el resultado puede no ser una mónada. En contraste, la composición de dos aplicativos es necesariamente un aplicativo. Sospecho que la intención de la declaración original era que "La aplicabilidad se compone, mientras que la mónada no". Reformulado, " Applicative está cerrado por composición y Monad no".


La solución de ley distributiva l: MN -> NM es suficiente

para garantizar la monadicidad de NM. Para ver esto necesitas una unidad y un mult. me centraré en el mult (la unidad es unit_N unitM)

NMNM - l -> NNMM - mult_N mult_M -> NM

Esto no garantiza que MN sea una mónada.

Sin embargo, la observación crucial entra en juego cuando tienes soluciones de ley distributiva

l1 : ML -> LM l2 : NL -> LN l3 : NM -> MN

por lo tanto, LM, LN y MN son mónadas. Surge la pregunta si LMN es una mónada (ya sea por

(MN) L -> L (MN) o por N (LM) -> (LM) N

Tenemos suficiente estructura para hacer estos mapas. Sin embargo, como observa Eugenia Cheng , necesitamos una condición hexagonal (que equivale a una presentación de la ecuación de Yang-Baxter) para garantizar la monadicidad de cualquiera de las construcciones. De hecho, con la condición hexagonal, las dos mónadas diferentes coinciden.


Si comparamos los tipos

(<*>) :: Applicative a => a (s -> t) -> a s -> a t (>>=) :: Monad m => m s -> (s -> m t) -> m t

obtenemos una pista de lo que separa los dos conceptos. Eso (s -> mt) en el tipo de (>>=) muestra que un valor en s puede determinar el comportamiento de un cálculo en mt . Las mónadas permiten la interferencia entre el valor y las capas de cálculo. El operador (<*>) no permite tal interferencia: los cálculos de función y argumento no dependen de los valores. Esto realmente muerde Comparar

miffy :: Monad m => m Bool -> m x -> m x -> m x miffy mb mt mf = do b <- mb if b then mt else mf

que utiliza el resultado de algún efecto para decidir entre dos cálculos (por ejemplo, lanzar misiles y firmar un armisticio), mientras que

iffy :: Applicative a => a Bool -> a x -> a x -> a x iffy ab at af = pure cond <*> ab <*> at <*> af where cond b t f = if b then t else f

que usa el valor de ab para elegir entre los valores de dos cálculos at y af , habiendo llevado a cabo ambos, tal vez con un efecto trágico.

La versión monádica se basa esencialmente en la potencia extra de (>>=) para elegir un cálculo a partir de un valor, y eso puede ser importante. Sin embargo, apoyar ese poder hace que las mónadas sean difíciles de componer. Si tratamos de construir ''doble enlace''

(>>>>==) :: (Monad m, Monad n) => m (n s) -> (s -> m (n t)) -> m (n t) mns >>>>== f = mns >>-{-m-} / ns -> let nmnt = ns >>= (return . f) in ???

llegamos hasta aquí, pero ahora nuestras capas están mezcladas. Tenemos una n (m (nt)) , así que tenemos que deshacernos de la n externa. Como dice Alexandre C, podemos hacer eso si tenemos un

swap :: n (m t) -> m (n t)

permutar la n hacia adentro y join a la otra n .

La "aplicación doble" más débil es mucho más fácil de definir

(<<**>>) :: (Applicative a, Applicative b) => a (b (s -> t)) -> a (b s) -> a (b t) abf <<**>> abs = pure (<*>) <*> abf <*> abs

porque no hay interferencia entre las capas.

En consecuencia, es bueno reconocer cuándo realmente necesita el poder adicional de Monad , y cuándo puede salirse con la estructura de cálculo rígida que admite Applicative .

Tenga en cuenta, por cierto, que aunque componer mónadas es difícil, podría ser más de lo que necesita. El tipo m (nv) indica computación con m -efectos, luego computa con n -efectos a un valor v , donde los efectos -m terminan antes de que comiencen los -efectos n (de ahí la necesidad de swap ). Si solo quieres entrelazar m -efectos con n -efectos, ¡entonces la composición es quizás demasiado pedir!


Si tiene aplicativos A1 y A2 , entonces los data A3 a = A3 (A1 (A2 a)) tipo data A3 a = A3 (A1 (A2 a)) también son aplicables (puede escribir dicha instancia de forma genérica).

Por otro lado, si tiene mónadas M1 y M2 entonces el tipo de data M3 a = M3 (M1 (M2 a)) no es necesariamente una mónada (no existe una implementación genérica sensible para >>= o join para la composición).

Un ejemplo puede ser el tipo [String -> a] (aquí componemos un constructor de tipo [] con (->) String , que son mónadas). Puedes escribir fácilmente

app :: [String -> (a -> b)] -> [String -> a] -> [String -> b] app f x = (<*>) <$> f <*> x

Y eso se generaliza a cualquier aplicativo:

app :: (Applicative f, Applicative f1) => f (f1 (a -> b)) -> f (f1 a) -> f (f1 b)

Pero no hay una definición sensata de

join :: [String -> [String -> a]] -> [String -> a]