¿Qué es una mónada?


1415

Habiendo mirado brevemente a Haskell recientemente, ¿cuál sería un explicación breve, sucinta y práctica de lo que esencialmente es una mónada?

La mayoría de las explicaciones que he encontrado son bastante inaccesibles y carecen de detalles prácticos.


12
Eric Lippert escribió una respuesta a estas preguntas ( stackoverflow.com/questions/2704652/… ), que se debe a algunos problemas que se encuentran en una página separada.
P Shved

70
Aquí hay una nueva introducción usando JavaScript: me pareció muy legible.
Benjol



2
Una mónada es una matriz de funciones con operaciones auxiliares. Ver esta respuesta
cibercitizen1

Respuestas:


1060

Primero: el término mónada es un poco vacío si no eres matemático. Un término alternativo es generador de cómputo, que es un poco más descriptivo de para qué son realmente útiles.

Pides ejemplos prácticos:

Ejemplo 1: comprensión de la lista :

[x*2 | x<-[1..10], odd x]

Esta expresión devuelve los dobles de todos los números impares en el rango de 1 a 10. ¡Muy útil!

Resulta que esto es solo azúcar sintáctico para algunas operaciones dentro de la mónada List. La misma lista de comprensión se puede escribir como:

do
   x <- [1..10]
   guard (odd x)
   return (x * 2)

O incluso:

[1..10] >>= (\x -> guard (odd x) >> return (x*2))

Ejemplo 2: Entrada / Salida :

do
   putStrLn "What is your name?"
   name <- getLine
   putStrLn ("Welcome, " ++ name ++ "!")

Ambos ejemplos usan mónadas, constructores de cálculo AKA. El tema común es que la mónada encadena las operaciones de alguna manera específica y útil. En la comprensión de la lista, las operaciones se encadenan de tal manera que si una operación devuelve una lista, las siguientes operaciones se realizan en cada elemento de la lista. Por otro lado, la mónada IO realiza las operaciones secuencialmente, pero pasa una "variable oculta", que representa "el estado del mundo", que nos permite escribir código de E / S de una manera puramente funcional.

Resulta que el patrón de operaciones de encadenamiento es bastante útil y se usa para muchas cosas diferentes en Haskell.

Otro ejemplo son las excepciones: al usar la Errormónada, las operaciones se encadenan de tal manera que se realizan de forma secuencial, excepto si se produce un error, en cuyo caso se abandona el resto de la cadena.

Tanto la sintaxis de comprensión de lista como la notación de do son azúcar sintáctica para encadenar operaciones utilizando el >>=operador. Una mónada es básicamente solo un tipo que admite el >>=operador.

Ejemplo 3: un analizador sintáctico

Este es un analizador muy simple que analiza una cadena entre comillas o un número:

parseExpr = parseString <|> parseNumber

parseString = do
        char '"'
        x <- many (noneOf "\"")
        char '"'
        return (StringValue x)

parseNumber = do
    num <- many1 digit
    return (NumberValue (read num))

Las operaciones char, digitetc. son bastante simples. O coinciden o no coinciden. La magia es la mónada que gestiona el flujo de control: las operaciones se realizan de forma secuencial hasta que falla una coincidencia, en cuyo caso la mónada retrocede a la última <|>y prueba la siguiente opción. Una vez más, una forma de encadenar operaciones con algunas semánticas adicionales útiles.

Ejemplo 4: programación asincrónica

Los ejemplos anteriores están en Haskell, pero resulta que F # también admite mónadas. Este ejemplo es robado de Don Syme :

let AsyncHttp(url:string) =
    async {  let req = WebRequest.Create(url)
             let! rsp = req.GetResponseAsync()
             use stream = rsp.GetResponseStream()
             use reader = new System.IO.StreamReader(stream)
             return reader.ReadToEnd() }

Este método busca una página web. El uso de la línea de perforación es GetResponseAsync: realmente espera la respuesta en un hilo separado, mientras que el hilo principal regresa de la función. Las últimas tres líneas se ejecutan en el hilo generado cuando se ha recibido la respuesta.

En la mayoría de los otros idiomas, tendría que crear explícitamente una función separada para las líneas que manejan la respuesta. La asyncmónada puede "dividir" el bloque por sí sola y posponer la ejecución de la segunda mitad. (La async {}sintaxis indica que el flujo de control en el bloque está definido por la asyncmónada).

Cómo trabajan ellos

Entonces, ¿cómo puede una mónada hacer todas estas cosas elegantes de flujo de control? Lo que realmente sucede en un do-block (o una expresión de cálculo como se los llama en F #), es que cada operación (básicamente cada línea) está envuelta en una función anónima separada. Estas funciones se combinan utilizando el bindoperador (escrito >>=en Haskell). Dado que la bindoperación combina funciones, puede ejecutarlas como mejor le parezca: secuencialmente, varias veces, a la inversa, descarte algunas, ejecute algunas en un hilo separado cuando lo desee, etc.

Como ejemplo, esta es la versión ampliada del código IO del ejemplo 2:

putStrLn "What is your name?"
>>= (\_ -> getLine)
>>= (\name -> putStrLn ("Welcome, " ++ name ++ "!"))

Esto es más feo, pero también es más obvio lo que realmente está sucediendo. El >>=operador es el ingrediente mágico: toma un valor (en el lado izquierdo) y lo combina con una función (en el lado derecho), para producir un nuevo valor. Este nuevo valor es tomado por el siguiente >>=operador y nuevamente combinado con una función para producir un nuevo valor. >>=puede ser visto como un mini evaluador.

Tenga en cuenta que >>=está sobrecargado para diferentes tipos, por lo que cada mónada tiene su propia implementación de >>=. (Sin embargo, todas las operaciones en la cadena deben ser del tipo de la misma mónada; de lo contrario, el >>=operador no funcionará).

La implementación más simple posible de >>=simplemente toma el valor a la izquierda y lo aplica a la función a la derecha y devuelve el resultado, pero como se dijo antes, lo que hace que todo el patrón sea útil es cuando hay algo extra en la implementación de la mónada de >>=.

Existe cierta inteligencia adicional en cómo se pasan los valores de una operación a la siguiente, pero esto requiere una explicación más profunda del sistema de tipo Haskell.

Resumiendo

En términos de Haskell, una mónada es un tipo parametrizado que es una instancia de la clase de tipo Mónada, que define >>=junto con algunos otros operadores. En términos simples, una mónada es solo un tipo para el que >>=se define la operación.

En sí mismo >>=es solo una forma engorrosa de encadenar funciones, pero con la presencia de la notación que oculta la "fontanería", las operaciones monádicas resultan ser una abstracción muy agradable y útil, útil en muchos lugares del lenguaje y útil. para crear tus propios mini idiomas en el idioma.

¿Por qué son duras las mónadas?

Para muchos estudiantes de Haskell, las mónadas son un obstáculo que golpean como una pared de ladrillos. No es que las mónadas en sí mismas sean complejas, sino que la implementación se basa en muchas otras características avanzadas de Haskell como tipos parametrizados, clases de tipos, etc. El problema es que Haskell I / O se basa en mónadas, y I / O es probablemente una de las primeras cosas que desea comprender al aprender un nuevo idioma; después de todo, no es muy divertido crear programas que no producen salida. No tengo una solución inmediata para este problema de huevo y gallina, excepto tratar la E / S como "la magia sucede aquí" hasta que tenga suficiente experiencia con otras partes del lenguaje. Lo siento.

Excelente blog sobre mónadas: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html


66
Como alguien que ha tenido muchos problemas para entender a las mónadas, puedo decir que esta respuesta ayudó ... un poco. Sin embargo, todavía hay algunas cosas que no entiendo. ¿De qué manera la comprensión de la lista es una mónada? ¿Hay una forma expandida de ese ejemplo? Otra cosa que realmente me molesta de la mayoría de las explicaciones de la mónada, incluida esta, es que siguen mezclando "¿qué es una mónada?" con "¿para qué sirve una mónada?" y "¿Cómo se implementa una mónada?". saltaste ese tiburón cuando escribiste "Una mónada es básicamente un tipo que admite el operador >> =". Lo que me acaba de hacer ...
Breton

83
Tampoco estoy de acuerdo con tu conclusión sobre por qué las mónadas son difíciles. Si las mónadas no son complejas, entonces deberías poder explicar qué son sin un montón de equipaje. No quiero saber acerca de la implementación cuando hago la pregunta "¿Qué es una mónada?", Quiero saber qué picazón debe estar rascando. Hasta ahora parece que la respuesta es "Debido a que los autores de Haskell son sadomasoquistas y decidieron que debes hacer algo estúpidamente complejo para lograr cosas simples, por lo que TIENES que aprender mónadas para usar Haskell, no porque sean de alguna manera útiles en ellos mismos "...
Breton

70
Pero ... eso no puede ser correcto, ¿verdad? Creo que las mónadas son difíciles porque parece que nadie puede descubrir cómo explicarlas sin quedar atrapado en detalles confusos de implementación. Quiero decir ... ¿qué es un autobús escolar? Es una plataforma metálica con un dispositivo en la parte delantera que consume un producto refinado de petróleo para conducir en un ciclo algunos pistones metálicos, que a su vez giran un cigüeñal conectado a algunos engranajes que impulsan algunas ruedas. Las ruedas tienen bolsas de goma infladas a su alrededor que se conectan con una superficie de asfalto para hacer que una colección de asientos avance. Los asientos avanzan porque ...
Breton

130
Leí todo esto y todavía no sé qué es una mónada, aparte del hecho de que es algo que los programadores de Haskell no entienden lo suficientemente bien como para explicarlo. Los ejemplos no ayudan mucho, dado que estas son todas las cosas que uno puede hacer sin mónadas, y esta respuesta no deja en claro cómo las mónadas las hacen más fáciles, solo más confusas. La única parte de esta respuesta que estuvo cerca de ser útil fue donde se eliminó el azúcar sintáctico del ejemplo # 2. Digo que estuvo cerca porque, aparte de la primera línea, la expansión no tiene ningún parecido real con el original.
Laurence Gonsalves

81
Otro problema que parece ser endémico a las explicaciones de las mónadas es que está escrito en Haskell. No digo que Haskell sea un mal lenguaje, digo que es un mal lenguaje para explicar las mónadas. Si conociera a Haskell, ya entendería mónadas, así que si quieres explicarlas, comienza por usar un lenguaje que las personas que no conocen mónadas tengan más probabilidades de entender. Si debe usar Haskell, no use el azúcar sintáctico en absoluto: use el subconjunto más pequeño y simple del lenguaje que pueda y no asuma una comprensión de Haskell IO.
Laurence Gonsalves

712

Explicar "¿Qué es una mónada?" Es un poco como decir "¿Qué es un número?" Usamos números todo el tiempo. Pero imagina que conociste a alguien que no sabía nada sobre números. ¿Cómo diablos explicarías qué son los números? ¿Y cómo comenzarías a describir por qué eso podría ser útil?

¿Qué es una mónada? La respuesta corta: es una forma específica de encadenar operaciones juntas.

En esencia, está escribiendo pasos de ejecución y vinculándolos con la "función de enlace". (En Haskell, se llama así >>=). Puede escribir las llamadas al operador de enlace usted mismo, o puede usar la sintaxis sugar que hace que el compilador inserte esas llamadas de función por usted. Pero de cualquier manera, cada paso está separado por una llamada a esta función de enlace.

Entonces la función de enlace es como un punto y coma; Separa los pasos en un proceso. El trabajo de la función de vinculación es tomar la salida del paso anterior y pasarla al siguiente paso.

Eso no suena demasiado difícil, ¿verdad? Pero hay más de un tipo de mónada. ¿Por qué? ¿Cómo?

Bueno, la función de vinculación solo puede tomar el resultado de un paso y pasarlo al siguiente. Pero si eso es "todo" que hace la mónada ... eso en realidad no es muy útil. Y eso es importante de entender: cada mónada útil hace algo más además de ser una mónada. Cada mónada útil tiene un "poder especial", que lo hace único.

(Una mónada que no hace nada especial se llama "mónada de identidad". Más bien como la función de identidad, esto suena como algo completamente inútil, pero resulta que no es ... Pero esa es otra historia ™).

Básicamente, cada mónada tiene su propia implementación de la función de vinculación. Y puede escribir una función de enlace de modo que haga cosas entre los pasos de ejecución. Por ejemplo:

  • Si cada paso devuelve un indicador de éxito / fracaso, puede hacer que Bind ejecute el siguiente paso solo si el anterior tuvo éxito. De esta manera, un paso fallido anula la secuencia completa "automáticamente", sin ninguna prueba condicional de su parte. (La falla Mónada .)

  • Extendiendo esta idea, puede implementar "excepciones". (La Mónada de Error o Mónada de Excepción ). Debido a que usted los define a usted mismo en lugar de ser una característica del lenguaje, puede definir cómo funcionan. (Por ejemplo, tal vez quieras ignorar las dos primeras excepciones y solo abortar cuando se lanza una tercera excepción).

  • Puede hacer que cada paso devuelva múltiples resultados , y hacer que la función de enlace se repita sobre ellos, alimentando cada uno en el siguiente paso para usted. De esta manera, no tiene que seguir escribiendo bucles por todas partes cuando se trata de múltiples resultados. La función de enlace "automáticamente" hace todo eso por usted. (La Lista Mónada ).

  • Además de pasar un "resultado" de un paso a otro, también puede hacer que la función de enlace pase datos adicionales . Estos datos ahora no aparecen en su código fuente, pero aún puede acceder a ellos desde cualquier lugar, sin tener que pasarlos manualmente a todas las funciones. (El lector Monad .)

  • Puede hacerlo para que se puedan reemplazar los "datos adicionales". Esto le permite simular actualizaciones destructivas , sin hacer actualizaciones destructivas. (La mónada estatal y su primo el escritor mónada ).

  • Debido a que solo está simulando actualizaciones destructivas, puede hacer cosas triviales que serían imposibles con actualizaciones destructivas reales . Por ejemplo, puede deshacer la última actualización o volver a una versión anterior .

  • Puede hacer una mónada donde los cálculos se pueden pausar , por lo que puede pausar su programa, entrar y jugar con los datos de estado internos, y luego reanudarlo.

  • Puede implementar "continuaciones" como una mónada. ¡Esto te permite romper las mentes de las personas!

Todo esto y más es posible con las mónadas. Por supuesto, todo esto también es perfectamente posible sin mónadas. Es drásticamente más fácil usar mónadas.


13
Agradezco su respuesta, especialmente la concesión final de que todo esto también es posible sin mónadas. Un punto a destacar es que es más fácil con las mónadas, pero a menudo no es tan eficiente como hacerlo sin ellas. Una vez que necesite involucrar a los transformadores, la estratificación adicional de las llamadas a funciones (y los objetos de función creados) tiene un costo que es difícil de ver y controlar, que se vuelve invisible por una sintaxis inteligente.
seh

1
Al menos en Haskell, el optimizador elimina la mayor parte de los gastos generales de las mónadas. Por lo tanto, el único "costo" real es el poder cerebral requerido. (Esto no es insignificante si la "mantenibilidad" es algo que le interesa). Pero, por lo general, las mónadas hacen las cosas más fáciles , no más difíciles. (De lo contrario, ¿por qué te molestas?)
MathematicalOrchid

No estoy seguro de si Haskell lo admite o no, pero matemáticamente puedes definir una mónada ya sea en términos de >> = y return o join y ap. >> = y return son los que hacen que las mónadas sean prácticamente útiles, pero unirse y dar una comprensión más intuitiva de lo que es una mónada.
Jeremy List

15
Viniendo de un fondo de programación no matemático, no funcional, esta respuesta tenía más sentido para mí.
jrahhali

10
Esta es la primera respuesta que realmente me dio una idea de qué demonios es una mónada. ¡Gracias por encontrar una manera de explicarlo!
robotmay

186

En realidad, contrario a la comprensión común de las mónadas, no tienen nada que ver con el estado. Las mónadas son simplemente una forma de envolver cosas y proporcionar métodos para realizar operaciones en las cosas envueltas sin desenvolverlas.

Por ejemplo, puede crear un tipo para envolver otro, en Haskell:

data Wrapped a = Wrap a

Para envolver cosas definimos

return :: a -> Wrapped a
return x = Wrap x

Para realizar operaciones sin desenvolver, digamos que tiene una función f :: a -> b, luego puede hacer esto para levantar esa función para actuar sobre valores envueltos:

fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)

Eso es todo lo que hay que entender. Sin embargo, resulta que hay una función más general para hacer este levantamiento , que es bind:

bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x

bindpuede hacer un poco más que fmap, pero no al revés. En realidad, solo fmapse puede definir en términos de bindy return. Entonces, al definir una mónada ... le das su tipo (aquí estaba Wrapped a) y luego dices cómo esreturn y bindoperaciones de trabajo.

Lo bueno es que esto resulta ser un patrón tan general que aparece por todas partes, el estado de encapsulación de manera pura es solo uno de ellos.

Para un buen artículo sobre cómo se pueden usar las mónadas para introducir dependencias funcionales y así controlar el orden de evaluación, como se usa en la mónada IO de Haskell, consulte IO Inside .

En cuanto a la comprensión de las mónadas, no te preocupes demasiado por eso. Lea sobre ellos lo que le parezca interesante y no se preocupe si no entiende de inmediato. Entonces simplemente bucear en un idioma como Haskell es el camino a seguir. Las mónadas son una de estas cosas en las que la comprensión se infiltra en tu cerebro mediante la práctica, un día de repente te das cuenta de que las entiendes.


-> es una aplicación de función de reflejo asociativa a la derecha, que es asociativa a la izquierda, por lo que dejar los paréntesis no hace una diferencia aquí.
Matthias Benkard

1
No creo que esta sea una muy buena explicación. Las mónadas son simplemente un camino? ¿de qué manera? ¿Por qué no encapsularía usando una clase en lugar de una mónada?
bretón

44
@ mb21: en caso de que solo esté señalando que hay demasiados corchetes, tenga en cuenta que a-> b-> c en realidad solo es corto para a -> (b-> c). Escribir este ejemplo en particular como (a -> b) -> (Ta -> Tb) es estrictamente hablando solo agregar caracteres innecesarios, pero es moralmente "lo correcto" ya que enfatiza que fmap asigna una función de tipo a -> b a una función de tipo Ta -> Tb. Y originalmente, eso es lo que hacen los functors en la teoría de categorías y de ahí provienen las mónadas.
Nikolaj-K

1
Esta respuesta es engañosa. Algunas mónadas no tienen un "envoltorio" en absoluto, tales funciones de un valor fijo.

1
@DanMandel Monads son patrones de diseño que proporcionan su propio contenedor de datos. Las mónadas están diseñadas para abstraer el código repetitivo. Entonces, cuando llamas a una Mónada en tu código, hace cosas detrás de escena de las que no quieres preocuparte. Piense en Nullable <T> o IEnumerable <T>, ¿qué hacen detrás de escena? Eso es mónada.
sksallaj

168

Pero, ¡podrías haber inventado mónadas!

sigfpe dice:

Pero todo esto introduce a las mónadas como algo esotérico que necesita explicación. Pero lo que quiero argumentar es que no son esotéricos en absoluto. De hecho, ante varios problemas en la programación funcional, habría sido llevado, inexorablemente, a ciertas soluciones, todas las cuales son ejemplos de mónadas. De hecho, espero que los inventes ahora si aún no lo has hecho. Entonces es un pequeño paso notar que todas estas soluciones son, de hecho, la misma solución disfrazada. Y después de leer esto, es posible que esté en una mejor posición para comprender otros documentos sobre mónadas porque reconocerá todo lo que ve como algo que ya ha inventado.

Muchos de los problemas que las mónadas intentan resolver están relacionados con el tema de los efectos secundarios. Entonces comenzaremos con ellos. (Tenga en cuenta que las mónadas le permiten hacer más que manejar los efectos secundarios, en particular muchos tipos de objeto contenedor pueden verse como mónadas. Algunas de las introducciones a las mónadas encuentran difícil conciliar estos dos usos diferentes de las mónadas y concentrarse en una sola o el otro.)

En un lenguaje de programación imperativo como C ++, las funciones no se comportan como las funciones de las matemáticas. Por ejemplo, supongamos que tenemos una función C ++ que toma un único argumento de coma flotante y devuelve un resultado de coma flotante. Superficialmente, puede parecer un poco como una función matemática que asigna reales a reales, pero una función C ++ puede hacer más que devolver un número que depende de sus argumentos. Puede leer y escribir los valores de las variables globales, así como escribir la salida en la pantalla y recibir la entrada del usuario. Sin embargo, en un lenguaje funcional puro, una función solo puede leer lo que se le proporciona en sus argumentos y la única forma en que puede tener un efecto en el mundo es a través de los valores que devuelve.


99
... la mejor manera no solo en Internet, sino en cualquier lugar. (Las mónadas de papel originales de Wadler para la programación funcional que mencioné en mi respuesta a continuación también son buenas). Ninguno de los millones de tutoriales por analogía se acerca.
ShreevatsaR

13
Esta traducción de JavaScript de la publicación de Sigfpe es la nueva mejor manera de aprender mónadas, ¡para las personas que aún no han asimilado Haskell avanzado!
Sam Watkins el

1
Así es como aprendí lo que es una mónada. A menudo, guiar al lector por el proceso de inventar un concepto es la mejor manera de enseñarlo.
Jordan

Sin embargo, una función que acepte el objeto de pantalla como argumento y devuelva su copia con el texto modificado sería pura.
Dmitri Zaitsev

87

Una mónada es un tipo de datos que tiene dos operaciones: >>=(aka bind) y return(aka unit). returntoma un valor arbitrario y crea una instancia de la mónada con él. >>=toma una instancia de la mónada y asigna una función sobre ella. (Ya puede ver que una mónada es un tipo de datos extraño, ya que en la mayoría de los lenguajes de programación no se puede escribir una función que tome un valor arbitrario y cree un tipo a partir de ella. Las mónadas usan un tipo de polimorfismo paramétrico ).

En notación Haskell, la interfaz de mónada está escrita

class Monad m where
  return :: a -> m a
  (>>=) :: forall a b . m a -> (a -> m b) -> m b

Se supone que estas operaciones obedecen ciertas "leyes", pero eso no es extremadamente importante: las "leyes" simplemente codifican la forma en que las implementaciones sensatas de las operaciones deberían comportarse (básicamente, eso >>=y returndeberían estar de acuerdo sobre cómo los valores se transforman en instancias de mónada y eso >>=es asociativo).

Las mónadas no son solo sobre estado y E / S: resumen un patrón común de cómputo que incluye trabajar con estado, E / S, excepciones y no determinismo. Probablemente las mónadas más simples de entender son las listas y los tipos de opciones:

instance Monad [ ] where
    []     >>= k = []
    (x:xs) >>= k = k x ++ (xs >>= k)
    return x     = [x]

instance Monad Maybe where
    Just x  >>= k = k x
    Nothing >>= k = Nothing
    return x      = Just x

donde []y :son los constructores de la lista, ++es el operador de concatenación Justy Nothingson los Maybeconstructores. Ambas mónadas encapsulan patrones de cálculo comunes y útiles en sus respectivos tipos de datos (tenga en cuenta que ninguno tiene nada que ver con los efectos secundarios o E / S).

Realmente tienes que jugar escribiendo un código Haskell no trivial para apreciar de qué se tratan las mónadas y por qué son útiles.


¿Qué quiere decir exactamente con "asigna una función sobre él"?
Casebash

Casebash, estoy siendo deliberadamente informal en la introducción. Vea los ejemplos cerca del final para tener una idea de lo que implica "mapear una función".
Chris Conway

3
Monad no es un tipo de datos. Es una regla de composición de funciones: stackoverflow.com/a/37345315/1614973
Dmitri Zaitsev

@DmitriZaitsev tiene razón, las mónadas en realidad proporcionan su propio tipo de datos, las mónadas no son tipos de datos
sksallaj

78

Primero debes entender qué es un functor. Antes de eso, comprenda las funciones de orden superior.

Una función de orden superior es simplemente una función que toma una función como argumento.

Un functor es cualquier construcción de tipo Tpara la que existe una función de orden superior, llamada map, que transforma una función de tipo a -> b(dados dos tipos ay b) en una función T a -> T b. Esta mapfunción también debe obedecer las leyes de identidad y composición de manera que las siguientes expresiones devuelvan verdaderas para todos py q(notación Haskell):

map id = id
map (p . q) = map p . map q

Por ejemplo, un constructor de tipo llamado Listes un functor si viene equipado con una función de tipo (a -> b) -> List a -> List bque obedece las leyes anteriores. La única implementación práctica es obvia. La List a -> List bfunción resultante itera sobre la lista dada, llamando a la (a -> b)función para cada elemento y devuelve la lista de resultados.

Una mónada es esencialmente sólo un funtor Tcon dos métodos adicionales, join, de tipo T (T a) -> T a, y unit(a veces llamado return, forko pure) de tipo a -> T a. Para listas en Haskell:

join :: [[a]] -> [a]
pure :: a -> [a]

¿Por qué es útil? Porque podría, por ejemplo, mapsobre una lista con una función que devuelve una lista. Jointoma la lista resultante de listas y las concatena. Listes una mónada porque esto es posible.

Puedes escribir una función que sí map, entonces join. Esta función se llama bind, o flatMap, o (>>=), o (=<<). Así es normalmente como se da una instancia de mónada en Haskell.

Una mónada tiene que cumplir ciertas leyes, a saber, que joindeben ser asociativas. Esto significa que si tiene un valor xde tipo [[[a]]], join (join x)debería ser igual join (map join x). Y puredebe ser una identidad para joineso join (pure x) == x.


3
ligera adición a def de 'función de orden superior': pueden tomar funciones OR RETURN. Es por eso que son "superiores" porque hacen cosas consigo mismos.
Kevin ganó el

99
Según esa definición, la suma es una función de orden superior. Toma un número y devuelve una función que agrega ese número a otro. Entonces, no, las funciones de orden superior son estrictamente funciones cuyo dominio consiste en funciones.
Apocalisp

El video ' Brian Beckman: No temas a la mónada ' sigue esta misma línea de lógica.
icc97

48

[Descargo de responsabilidad: todavía estoy tratando de asimilar por completo a las mónadas. Lo siguiente es justo lo que he entendido hasta ahora. Si está mal, espero que alguien con conocimiento me llame a la alfombra.]

Arnar escribió:

Las mónadas son simplemente una forma de envolver cosas y proporcionar métodos para realizar operaciones en las cosas envueltas sin desenvolverlas.

Eso es precisamente eso. La idea es así:

  1. Tomas algún tipo de valor y lo envuelves con información adicional. Al igual que el valor es de cierto tipo (por ejemplo, un entero o una cadena), la información adicional es de cierto tipo.

    Por ejemplo, esa información adicional podría ser una Maybeo una IO.

  2. Luego tiene algunos operadores que le permiten operar con los datos empaquetados mientras transporta esa información adicional. Estos operadores usan la información adicional para decidir cómo cambiar el comportamiento de la operación en el valor envuelto.

    Por ejemplo, a Maybe Intpuede ser a Just Into Nothing. Ahora, si agrega un Maybe Inta a Maybe Int, el operador verificará si ambos están Just Intdentro de s, y si es así, desenvolverá los Ints, pasará el operador de suma, volverá a envolver el resultado Inten un nuevo Just Int(que es válido Maybe Int) y, por lo tanto, devuelve a Maybe Int. Pero si uno de ellos estaba Nothingdentro, este operador regresará inmediatamente Nothing, lo que nuevamente es válido Maybe Int. De esa manera, puede fingir que sus Maybe Intnúmeros son solo números normales y realizar cálculos matemáticos regulares sobre ellos. Si tuviera que obtener una Nothing, sus ecuaciones aún producirán el resultado correcto, sin que tenga que tirar cheques por Nothingtodas partes .

Pero el ejemplo es justo lo que sucede Maybe. Si la información adicional fuera un IO, entonces IOse llamaría a ese operador especial definido para s, y podría hacer algo totalmente diferente antes de realizar la adición. (OK, agregar dos IO Ints juntos probablemente no tenga sentido, aún no estoy seguro.) (Además, si prestó atención al Maybeejemplo, habrá notado que "envolver un valor con cosas adicionales" no siempre es correcto. Pero es difícil ser exacto, correcto y preciso sin ser inescrutable).

Básicamente, "mónada" significa más o menos "patrón" . Pero en lugar de un libro lleno de Patrones explicados informalmente y específicamente nombrados, ahora tiene una construcción de lenguaje - sintaxis y todo - que le permite declarar nuevos patrones como elementos en su programa . (La imprecisión aquí es que todos los patrones tienen que seguir una forma particular, por lo que una mónada no es tan genérica como un patrón. Pero creo que ese es el término más cercano que la mayoría de la gente conoce y entiende).

Y es por eso que las personas encuentran a las mónadas tan confusas: porque son un concepto tan genérico. Preguntar qué hace que algo sea una mónada es igualmente vago como preguntar qué hace que algo sea un patrón.

Pero piense en las implicaciones de tener un soporte sintáctico en el lenguaje para la idea de un patrón: en lugar de tener que leer el libro Gang of Four y memorizar la construcción de un patrón en particular, simplemente escriba código que implemente este patrón de manera agnóstica, forma genérica una vez y ya está! Luego puede reutilizar este patrón, como Visitor o Strategy o Façade o lo que sea, simplemente decorando las operaciones en su código con él, ¡sin tener que volver a implementarlo una y otra vez!

Es por eso que las personas que entienden las mónadas las encuentran tan útiles. : no es un concepto de torre de marfil del que los snobs intelectuales se enorgullecen de comprender (OK, eso también, por supuesto, teehee), sino que en realidad simplifica el código.


12
A veces, una explicación de un "alumno" (como usted) es más relevante para otro alumno que una explicación de un experto. Los alumnos piensan igual :)
Adrian

Lo que hace que algo sea una mónada es la existencia de una función con tipo M (M a) -> M a. El hecho de que puede convertir eso en uno de tipo M a -> (a -> M b) -> M bes lo que los hace útiles.
Jeremy List

"mónada" significa más o menos "patrón" ... no.
Gracias

44

Después de mucho esfuerzo, creo que finalmente entiendo la mónada. Después de releer mi larga crítica de la respuesta abrumadoramente votada, ofreceré esta explicación.

Hay tres preguntas que deben responderse para comprender las mónadas:

  1. ¿Por qué necesitas una mónada?
  2. ¿Qué es una mónada?
  3. ¿Cómo se implementa una mónada?

Como señalé en mis comentarios originales, demasiadas explicaciones de mónada quedan atrapadas en la pregunta número 3, sin, y antes de realmente cubrir adecuadamente la pregunta 2 o la pregunta 1.

¿Por qué necesitas una mónada?

Los lenguajes funcionales puros como Haskell son diferentes de los lenguajes imperativos como C o Java en que un programa funcional puro no se ejecuta necesariamente en un orden específico, un paso a la vez. Un programa Haskell es más parecido a una función matemática, en la que puede resolver la "ecuación" en cualquier número de órdenes potenciales. Esto confiere una serie de beneficios, entre los que se encuentra que elimina la posibilidad de ciertos tipos de errores, particularmente aquellos relacionados con cosas como "estado".

Sin embargo, hay ciertos problemas que no son tan fáciles de resolver con este estilo de programación. Algunas cosas, como la programación de la consola y la E / S de archivos, necesitan que las cosas sucedan en un orden particular, o necesitan mantener el estado. Una forma de abordar este problema es crear un tipo de objeto que represente el estado de un cálculo y una serie de funciones que toman un objeto de estado como entrada y devuelven un nuevo objeto de estado modificado.

Entonces, creemos un valor hipotético de "estado", que represente el estado de una pantalla de consola. exactamente cómo se construye este valor no es importante, pero digamos que es una matriz de caracteres ASCII de longitud de bytes que representa lo que está visible actualmente en la pantalla, y una matriz que representa la última línea de entrada ingresada por el usuario, en pseudocódigo. Hemos definido algunas funciones que toman el estado de la consola, la modifican y devuelven un nuevo estado de la consola.

consolestate MyConsole = new consolestate;

Entonces, para hacer la programación de la consola, pero de una manera puramente funcional, necesitaría anidar muchas llamadas de función dentro de cada una.

consolestate FinalConsole = print(input(print(myconsole, "Hello, what's your name?")),"hello, %inputbuffer%!");

La programación de esta manera mantiene el estilo funcional "puro", al tiempo que obliga a que se realicen cambios en la consola en un orden particular. Pero probablemente querremos hacer más que unas pocas operaciones a la vez, como en el ejemplo anterior. Las funciones de anidamiento de esa manera comenzarán a ser torpes. Lo que queremos es un código que haga esencialmente lo mismo que el anterior, pero que esté escrito un poco más así:

consolestate FinalConsole = myconsole:
                            print("Hello, what's your name?"):
                            input():
                            print("hello, %inputbuffer%!");

De hecho, esta sería una forma más conveniente de escribirlo. ¿Cómo hacemos eso sin embargo?

¿Qué es una mónada?

Una vez que tenga un tipo (como consolestate) que defina junto con un conjunto de funciones diseñadas específicamente para operar en ese tipo, puede convertir todo el paquete de estas cosas en una "mónada" definiendo un operador como :(vincular) que automáticamente alimenta los valores de retorno a su izquierda, a los parámetros de función a su derecha, y unlift operador que convierte las funciones normales, en funciones que funcionan con ese tipo específico de operador de enlace.

¿Cómo se implementa una mónada?

Vea otras respuestas, que parecen bastante libres para entrar en detalles.


La secuenciación no es la única razón para definir una mónada. Una mónada es cualquier functor que tiene enlace y retorno. El enlace y el retorno te dan secuencia. Pero también dan otras cosas. Además, tenga en cuenta que su lenguaje imperativo favorito es efectivamente una mónada IO elegante con clases OO. Facilitar la definición de mónadas significa que es fácil usar el patrón de intérprete: ¡defina un dsl como mónada e intégrelo!
nomen


38

Después de responder a esta pregunta hace unos años, creo que puedo mejorar y simplificar esa respuesta con ...

Una mónada es una técnica de composición de funciones que externaliza el tratamiento para algunos escenarios de entrada utilizando una función de composición, bind , para preprocesar la entrada durante la composición.

En composición normal, la función, compose (>>)se usa para aplicar la función compuesta al resultado de su predecesor en secuencia. Es importante destacar que la función que se compone es necesaria para manejar todos los escenarios de su entrada.

(x -> y) >> (y -> z)

Este diseño puede mejorarse mediante la reestructuración de la entrada para que los estados relevantes sean más fácilmente interrogados. Entonces, en lugar de simplemente yel valor puede convertirse Mben, por ejemplo, (is_OK, b)si se yincluye una noción de validez.

Por ejemplo, cuando la entrada es solamente posiblemente un número, en lugar de devolver una cadena que puede contener diligentemente contener un número o no, usted podría reestructurar el tipo en un boolindicando la presencia de un número válido y un número en tupla tal como, bool * float. Las funciones compuestas ahora ya no necesitarían analizar una cadena de entrada para determinar si existe un número, sino que simplemente podrían inspeccionar la boolparte de una tupla.

(Ma -> Mb) >> (Mb -> Mc)

Aquí, una vez más, la composición ocurre naturalmente con, composepor lo que cada función debe manejar todos los escenarios de su entrada individualmente, aunque hacerlo ahora es mucho más fácil.

Sin embargo, ¿qué pasaría si pudiéramos externalizar el esfuerzo de interrogación para aquellos momentos en los que manejar un escenario es una rutina? Por ejemplo, ¿qué pasa si nuestro programa no hace nada cuando la entrada no está bien como en cuando lo is_OKestá false? Si eso se hiciera, las funciones compuestas no tendrían que manejar ese escenario por sí mismas, simplificando drásticamente su código y logrando otro nivel de reutilización.

Para lograr esta externalización podríamos usar una función bind (>>=),, para realizar el en compositionlugar de compose. Como tal, en lugar de simplemente transferir valores de la salida de una función a la entrada de otra Bind, inspeccionaría la Mporción May decidiría si aplicar la función compuesta a la función compuesta y cómo a. Por supuesto, la función bindse definiría específicamente para nuestro particular a Mfin de poder inspeccionar su estructura y realizar cualquier tipo de aplicación que queramos. Sin embargo, apuede ser cualquier cosa, ya que bindsimplemente pasa laa inspeccionado a la función compuesta cuando determina la aplicación necesaria. Además, las funciones compuestas en sí mismas ya no necesitan lidiar conMparte de la estructura de entrada tampoco, simplificándolas. Por lo tanto...

(a -> Mb) >>= (b -> Mc) o más sucintamente Mb >>= (b -> Mc)

En resumen, una mónada se externaliza y, por lo tanto, proporciona un comportamiento estándar alrededor del tratamiento de ciertos escenarios de entrada una vez que la entrada se diseña para exponerlos suficientemente. Este diseño es un shell and contentmodelo en el que el shell contiene datos relevantes para la aplicación de la función compuesta y es interrogado y solo está disponible para la bindfunción.

Por lo tanto, una mónada es tres cosas:

  1. un Mcaparazón para guardar información relevante de la mónada,
  2. una bindfunción implementada para hacer uso de esta información de shell en su aplicación de las funciones compuestas a los valores de contenido que encuentra dentro del shell, y
  3. funciones componibles de la forma, a -> Mbproduciendo resultados que incluyen datos de gestión monádica.

En términos generales, la entrada a una función es mucho más restrictiva que su salida, lo que puede incluir cosas como condiciones de error; por lo tanto, la Mbestructura de resultados es generalmente muy útil. Por ejemplo, el operador de división no devuelve un número cuando el divisor es 0.

Además, monads puede incluir funciones de ajuste que envuelven valores, aen el tipo monádico Ma, y funciones generales a -> b, en funciones monádicas a -> Mb, ajustando sus resultados después de la aplicación. Por supuesto, como bind, tales funciones de ajuste son específicas de M. Un ejemplo:

let return a = [a]
let lift f a = return (f a)

El diseño de la bindfunción supone estructuras de datos inmutables y funciones puras, otras cosas se vuelven complejas y no se pueden hacer garantías. Como tal, hay leyes monádicas:

Dado...

M_ 
return = (a -> Ma)
f = (a -> Mb)
g = (b -> Mc)

Entonces...

Left Identity  : (return a) >>= f === f a
Right Identity : Ma >>= return    === Ma
Associative    : Ma >>= (f >>= g) === Ma >>= ((fun x -> f x) >>= g)

Associativitysignifica que bindconserva el orden de evaluación independientemente de cuándo bindse aplique. Es decir, en la definición de Associativityarriba, la fuerza de la evaluación temprana del paréntesis bindingde fy gsolo dará como resultado una función que se espera Mapara completar el bind. Por lo tanto, la evaluación de Madebe determinarse antes de que su valor pueda aplicarse fy ese resultado a su vez se aplique g.


"... pero espero que otros lo encuentren útil" fue realmente útil para mí, a pesar de todas las oraciones enfatizadas: D

Esta es la explicación más concisa y clara de las mónadas que he leído / visto / escuchado. ¡Gracias!
James

Hay una diferencia importante entre Monad y Monoid. Monad es una regla para "componer" funciones entre diferentes tipos, por lo que no forman una operación binaria como se requiere para los monoides, consulte aquí para más detalles: stackoverflow.com/questions/2704652/…
Dmitri Zaitsev

Si. Estás en lo correcto. Tu artículo estaba sobre mi cabeza :). Sin embargo, este tratamiento me pareció muy útil (y lo agregué al mío como una dirección para otros). Gracias a todos: stackoverflow.com/a/7829607/1612190
George

2
Es posible que haya confundido la teoría del grupo algebraico con la teoría de la categoría de donde proviene Monad. La primera es la teoría de los grupos algebraicos, que no está relacionada.
Dmitri Zaitsev

37

Una mónada es, efectivamente, una forma de "operador de tipo". Hará tres cosas. Primero "envolverá" (o convertirá) un valor de un tipo en otro tipo (típicamente llamado "tipo monádico"). En segundo lugar, hará que todas las operaciones (o funciones) estén disponibles en el tipo subyacente disponible en el tipo monádico. Finalmente proporcionará soporte para combinarse con otra mónada para producir una mónada compuesta.

La "quizás mónada" es esencialmente el equivalente de "tipos anulables" en Visual Basic / C #. Toma un tipo "T" no anulable y lo convierte en un "Nullable <T>", y luego define lo que significan todos los operadores binarios en un Nullable <T>.

Los efectos secundarios se representan de manera similar. Se crea una estructura que contiene descripciones de los efectos secundarios junto con el valor de retorno de una función. Las operaciones "levantadas" luego copian los efectos secundarios a medida que se pasan valores entre funciones.

Se llaman "mónadas" en lugar del nombre más fácil de entender de "operadores de tipo" por varias razones:

  1. Las mónadas tienen restricciones sobre lo que pueden hacer (ver la definición para más detalles).
  2. Esas restricciones, junto con el hecho de que hay tres operaciones involucradas, se ajustan a la estructura de algo llamado mónada en la teoría de la categoría, que es una rama oscura de las matemáticas.
  3. Fueron diseñados por defensores de lenguajes funcionales "puros"
  4. Los defensores de lenguajes funcionales puros como ramas oscuras de las matemáticas.
  5. Debido a que las matemáticas son oscuras y las mónadas están asociadas con estilos particulares de programación, las personas tienden a usar la palabra mónada como una especie de apretón de manos secreto. Debido a esto, nadie se ha molestado en invertir en un mejor nombre.

1
Las mónadas no fueron 'diseñadas', se aplicaron de un dominio (teoría de categorías) a otro (E / S en lenguajes de programación puramente funcionales). ¿Newton 'diseñó' el cálculo?
Jared Updike

1
Los puntos 1 y 2 anteriores son correctos y útiles. Los puntos 4 y 5 son una especie de anuncio hominem, incluso si es más o menos cierto. Realmente no ayudan a explicar las mónadas.
Jared Updike

13
Re: 4, 5: Lo del "apretón de manos secreto" es un arenque rojo. La programación está llena de jerga. Haskell simplemente llama a las cosas como son sin pretender redescubrir algo. Si ya existe en matemáticas, ¿por qué inventarle un nuevo nombre? El nombre no es realmente la razón por la cual las personas no reciben mónadas; Son un concepto sutil. La persona promedio probablemente comprende la suma y la multiplicación, ¿por qué no entiende el concepto de un grupo abeliano? Porque es más abstracto y general y esa persona no ha hecho el trabajo de entender el concepto. Un cambio de nombre no ayudaría.
Jared Updike

16
Suspiro ... No estoy atacando a Haskell ... Estaba haciendo una broma. Entonces, realmente no entiendo nada acerca de ser "ad hominem". Sí, el cálculo fue "diseñado". Es por eso que, por ejemplo, a los estudiantes de cálculo se les enseña la notación de Leibniz, en lugar de las cosas repugnantes que usaba Netwton. Mejor diseño Los buenos nombres ayudan a entender mucho. Si llamé a los Grupos Abelianos "vainas de arrugas distendidas", es posible que tenga problemas para comprenderme. Puede que estés diciendo "pero ese nombre no tiene sentido", nadie los llamaría así. Para las personas que nunca han oído hablar de la teoría de categorías, "mónada" parece una tontería.
Scott Wisniewski el

44
@ Scott: perdón si mis extensos comentarios hicieron que pareciera que me estaba poniendo a la defensiva sobre Haskell. Disfruto de tu humor sobre el apretón de manos secreto y notarás que dije que es más o menos cierto. :-) Si llamaras a los Grupos Abelianos "vainas de arrugas distendidas" estarías cometiendo el mismo error al tratar de dar a las mónadas un "mejor nombre" (cf. F # "expresiones de cálculo"): el término existe y las personas que se preocupan saben qué mónadas son, pero no lo que son "cosas difusas cálidas" (o "expresiones de cálculo"). Si entiendo su uso del término "operador de tipo" correctamente, hay muchos otros operadores de tipo que las mónadas.
Jared Updike

35

(Ver también las respuestas en ¿Qué es una mónada? )

¡Una buena motivación para las mónadas es sigfpe (Dan Piponi), ¡ Podrías haber inventado mónadas! (Y tal vez ya lo tengas) . Hay MUCHOS otros tutoriales de mónadas , muchos de los cuales tratan erróneamente de explicar las mónadas en "términos simples" usando varias analogías: esta es la falacia del tutorial de mónada ; Evítales.

Como dice DR MacIver en Cuéntanos por qué apesta tu idioma :

Entonces, cosas que odio de Haskell:

Comencemos con lo obvio. Mónada tutoriales. No, no mónadas. Específicamente los tutoriales. Son interminables, exagerados y querido Dios, son tediosos. Además, nunca he visto ninguna evidencia convincente de que realmente ayuden. Lea la definición de clase, escriba un código, supere el nombre aterrador.

¿Dices que entiendes a la Mónada Quizás? Bien, ya estás en camino. Simplemente comience a usar otras mónadas y, tarde o temprano, comprenderá qué son las mónadas en general.

[Si está orientado matemáticamente, es posible que desee ignorar las docenas de tutoriales y aprender la definición, o seguir conferencias en teoría de categorías :) La parte principal de la definición es que una Mónada M involucra un "constructor de tipos" que define para cada tipo existente "T", un nuevo tipo "MT", y algunas formas de ir y venir entre tipos "normales" y tipos "M".]

Además, sorprendentemente, una de las mejores introducciones a las mónadas es en realidad uno de los primeros trabajos académicos que presenta a las mónadas, las mónadas de Philip Wadler para la programación funcional . En realidad, tiene ejemplos prácticos y no triviales de motivación, a diferencia de muchos de los tutoriales artificiales que existen.


2
El único problema con el artículo de Wadler es que la notación es diferente, pero estoy de acuerdo en que el artículo es bastante convincente y una motivación clara y concisa para aplicar mónadas.
Jared Updike

+1 para la "falacia del tutorial de mónada". Los tutoriales sobre mónadas son similares a tener varios tutoriales que intentan explicar el concepto de números enteros. Un tutorial diría, "1 es similar a una manzana"; otro tutorial dice: "2 es como una pera"; un tercero dice: "3 es básicamente una naranja". Pero nunca obtienes la imagen completa de un tutorial individual. Lo que he sacado de eso es que las mónadas son un concepto abstracto que puede usarse para muchos propósitos muy diferentes.
stakx - ya no contribuye el

@stakx: Sí, cierto. Pero no quise decir que las mónadas son una abstracción que no puedes aprender o no debes aprender; solo que es mejor aprenderlo después de haber visto suficientes ejemplos concretos para percibir una sola abstracción subyacente. Vea mi otra respuesta aquí .
ShreevatsaR

55
A veces siento que hay tantos tutoriales que intentan convencer al lector de que las mónadas son útiles mediante el uso de código que hace cosas complicadas o útiles. Eso dificultó mi comprensión durante meses. No aprendo de esa manera. Prefiero ver un código extremadamente simple, hacer algo estúpido por lo que pueda pasar mentalmente y no pude encontrar este tipo de ejemplo. No puedo saber si el primer ejemplo es una mónada para analizar una gramática complicada. Puedo saber si es una mónada sumar enteros.
Rafael S. Calsaverini

Mencionar solo el constructor de tipos está incompleto: stackoverflow.com/a/37345315/1614973
Dmitri Zaitsev

23

Las mónadas deben controlar el flujo de los tipos de datos abstractos para los datos.

En otras palabras, muchos desarrolladores se sienten cómodos con la idea de conjuntos, listas, diccionarios (o hashes o mapas) y árboles. Dentro de esos tipos de datos hay muchos casos especiales (por ejemplo, InsertionOrderPreservingIdentityHashMap).

Sin embargo, cuando se enfrentan con el "flujo" del programa, muchos desarrolladores no han estado expuestos a muchas más construcciones que if, switch / case, do, while, goto (grr) y (quizás) cierres.

Entonces, una mónada es simplemente una construcción de flujo de control. Una mejor frase para reemplazar a la mónada sería 'tipo de control'.

Como tal, una mónada tiene ranuras para la lógica de control, o declaraciones o funciones: el equivalente en las estructuras de datos sería decir que algunas estructuras de datos le permiten agregar datos y eliminarlos.

Por ejemplo, la mónada "if":

if( clause ) then block

en su forma más simple tiene dos espacios: una cláusula y un bloque. losif mónada generalmente se construye para evaluar el resultado de la cláusula y, si no es falso, evaluar el bloque. Muchos desarrolladores no son introducidos a las mónadas cuando aprenden 'si', y simplemente no es necesario entender a las mónadas para escribir una lógica efectiva.

Las mónadas pueden volverse más complicadas, de la misma manera que las estructuras de datos pueden volverse más complicadas, pero hay muchas categorías amplias de mónadas que pueden tener una semántica similar, pero diferentes implementaciones y sintaxis.

Por supuesto, de la misma manera que las estructuras de datos pueden iterarse o atravesarse, pueden evaluarse las mónadas.

Los compiladores pueden o no tener soporte para mónadas definidas por el usuario. Haskell ciertamente lo hace. Ioke tiene algunas capacidades similares, aunque el término mónada no se usa en el lenguaje.


14

Mi tutorial favorito de Monad:

http://www.haskell.org/haskellwiki/All_About_Monads

(¡de 170,000 visitas en una búsqueda en Google de "mónada tutorial"!)

@Stu: El objetivo de las mónadas es permitirle agregar (generalmente) semántica secuencial a un código puro; incluso puede componer mónadas (usando Transformadores de mónada) y obtener una semántica combinada más interesante y complicada, como el análisis con manejo de errores, estado compartido y registro, por ejemplo. Todo esto es posible en código puro, las mónadas solo le permiten abstraerlo y reutilizarlo en bibliotecas modulares (siempre bueno en programación), así como proporcionar una sintaxis conveniente para que parezca imprescindible.

Haskell ya tiene una sobrecarga de operadores [1]: utiliza clases de tipos de la misma manera que uno podría usar interfaces en Java o C #, pero resulta que Haskell también permite tokens no alfanuméricos como + && y> como identificadores de infijo. Es solo sobrecarga del operador en su forma de verlo si quiere decir "sobrecargar el punto y coma" [2]. Suena como magia negra y pide problemas para "sobrecargar el punto y coma" (los piratas informáticos emprendedores de Perl se enteran de esta idea) pero el punto es que sin mónadas no hay punto y coma, ya que el código puramente funcional no requiere ni permite una secuencia explícita.

Todo esto suena mucho más complicado de lo necesario. El artículo de sigfpe es bastante bueno, pero utiliza a Haskell para explicarlo, lo que no logra resolver el problema del huevo y la gallina de entender a Haskell para mimar a las mónadas y entender a las mónadas para que las mimeticen.

[1] Este es un problema separado de las mónadas, pero las mónadas utilizan la función de sobrecarga del operador de Haskell.

[2] Esto también es una simplificación excesiva ya que el operador para encadenar acciones monádicas es >> = (pronunciado "bind") pero hay un azúcar sintáctico ("do") que le permite usar llaves y puntos y comas y / o sangría y líneas nuevas.


9

He estado pensando en las mónadas de una manera diferente, últimamente. He estado pensando en ellos como abstrayendo el orden de ejecución de una manera matemática, lo que hace posible nuevos tipos de polimorfismo.

Si está utilizando un lenguaje imperativo y escribe algunas expresiones en orden, el código SIEMPRE se ejecuta exactamente en ese orden.

Y en el caso simple, cuando usa una mónada, se siente igual: define una lista de expresiones que suceden en orden. Excepto que, dependiendo de la mónada que use, su código podría ejecutarse en orden (como en la mónada IO), en paralelo sobre varios elementos a la vez (como en la mónada Lista), podría detenerse a la mitad (como en la mónada Quizás) , podría detenerse a medio camino para reanudarse más tarde (como en una mónada Reanudación), podría rebobinarse y comenzar desde el principio (como en una mónada de Transacción), o podría rebobinarse parcialmente para probar otras opciones (como en una mónada Lógica) .

Y debido a que las mónadas son polimórficas, es posible ejecutar el mismo código en diferentes mónadas, según sus necesidades.

Además, en algunos casos, es posible combinar mónadas (con transformadores de mónada) para obtener múltiples funciones al mismo tiempo.


9

Todavía soy nuevo en las mónadas, pero pensé que compartiría un enlace que encontré que se sintió realmente bien para leer (¡CON FOTOS!): Http://www.matusiak.eu/numerodix/blog/2012/3/11/ mónadas para el laico / (sin afiliación)

Básicamente, el concepto cálido y difuso que obtuve del artículo fue el concepto de que las mónadas son básicamente adaptadores que permiten que funciones dispares funcionen de una manera composable, es decir, ser capaz de agrupar múltiples funciones y mezclarlas y combinarlas sin preocuparse por un retorno inconsistente tipos y tal. Entonces, la función BIND se encarga de mantener las manzanas con manzanas y las naranjas con naranjas cuando intentamos hacer estos adaptadores. Y la función LIFT se encarga de tomar las funciones de "nivel inferior" y "actualizarlas" para que funcionen con las funciones BIND y también sean componibles.

Espero haberlo entendido bien y, lo que es más importante, espero que el artículo tenga una visión válida sobre las mónadas. Por lo menos, este artículo me ayudó a despertar mi apetito por aprender más sobre las mónadas.


¡Los ejemplos de Python lo hicieron fácil de comprender! Gracias por compartir.
Ryan Efendy

8

Además de las excelentes respuestas anteriores, permítame ofrecerle un enlace al siguiente artículo (de Patrick Thomson) que explica las mónadas relacionando el concepto con la biblioteca JavaScript jQuery (y su forma de usar el "encadenamiento de métodos" para manipular el DOM) : jQuery es una mónada

La documentación de jQuery en sí misma no se refiere al término "mónada", sino que habla sobre el "patrón de construcción", que probablemente sea más familiar. Esto no cambia el hecho de que tienes una mónada adecuada allí, incluso sin darte cuenta.


Si usa jQuery, esta explicación puede ser muy útil, especialmente si su Haskell no es fuerte
byteclub

10
JQuery no es enfáticamente una mónada. El artículo vinculado está equivocado.
Tony Morris

1
Ser "enfático" no es muy convincente. Para una discusión útil sobre el tema, vea ¿Es jQuery una mónada? - Desbordamiento de pila
nealmcb

1
Ver también de Douglas Crackford Google Talk mónadas y gónadas y su código Javascript para hacer modads, ampliando el comportamiento similar de las bibliotecas y promesas AJAX: douglascrockford / mónada · GitHub
nealmcb


7

Una mónada es una forma de combinar cálculos que comparten un contexto común. Es como construir una red de tuberías. Al construir la red, no fluyen datos a través de ella. Pero cuando he terminado de unir todos los bits con 'bind' y 'return', invoco algo así runMyMonad monad datay los datos fluyen a través de las tuberías.


1
Eso es más como Aplicativo que Mónada. Con Monads, debe obtener datos de las tuberías antes de poder elegir la siguiente tubería para conectar.
Peaker

Sí, usted describe el Aplicativo, no la Mónada. Monad está construyendo el siguiente segmento de tubería en el lugar, dependiendo de los datos que llegaron a ese punto, dentro de la tubería.
Will Ness

6

En la práctica, monad es una implementación personalizada del operador de composición de funciones que se encarga de los efectos secundarios y los valores de entrada y retorno incompatibles (para el encadenamiento).



5

Las dos cosas que me ayudaron mejor al aprender allí fueron:

Capítulo 8, "Analizadores funcionales", del libro de Graham Hutton Programming in Haskell . En realidad, esto no menciona a las mónadas, pero si puede trabajar en el capítulo y comprender realmente todo lo que contiene, particularmente cómo se evalúa una secuencia de operaciones de enlace, comprenderá las partes internas de las mónadas. Espere que esto tome varios intentos.

El tutorial Todo sobre las mónadas . Esto da varios buenos ejemplos de su uso, y debo decir que la analogía en Appendex funcionó para mí.


5

Monoid parece ser algo que garantiza que todas las operaciones definidas en un Monoid y un tipo compatible siempre devolverán un tipo compatible dentro del Monoid. Por ejemplo, Cualquier número + Cualquier número = Un número, sin errores.

Mientras que la división acepta dos fraccionarios, y devuelve un fraccionario, que definió la división por cero como Infinito en haskell somewhy (que resulta ser fraccional somewhy) ...

En cualquier caso, parece que las mónadas son solo una forma de garantizar que su cadena de operaciones se comporte de una manera predecible, y una función que dice ser Num -> Num, compuesta con otra función de Num-> Num llamada con x no digamos, dispara los misiles.

Por otro lado, si tenemos una función que dispara los misiles, podemos componerla con otras funciones que también disparan los misiles, porque nuestra intención es clara: queremos disparar los misiles, pero no lo intentaremos imprimiendo "Hello World" por alguna extraña razón.

En Haskell, main es de tipo IO (), o IO [()], la discreción es extraña y no lo discutiré, pero esto es lo que creo que sucede:

Si tengo main, quiero que haga una cadena de acciones, la razón por la que ejecuto el programa es para producir un efecto, generalmente a través de IO. Por lo tanto, puedo encadenar las operaciones de IO en main para - hacer IO, nada más.

Si trato de hacer algo que no "devuelve IO", el programa se quejará de que la cadena no fluye, o básicamente "¿Cómo se relaciona esto con lo que estamos tratando de hacer? Una acción de IO", parece forzar el programador debe mantener su línea de pensamiento, sin desviarse y sin pensar en disparar los misiles, mientras crea algoritmos para la clasificación, que no fluye.

Básicamente, las mónadas parecen ser una sugerencia para el compilador que "oye, conoces esta función que devuelve un número aquí, en realidad no siempre funciona, a veces puede producir un número, y a veces nada en absoluto, solo mantén esto en mente". Sabiendo esto, si intentas afirmar una acción monádica, la acción monádica puede actuar como una excepción de tiempo de compilación diciendo "oye, esto no es realmente un número, PUEDE ser un número, pero no puedes asumir esto, haz algo para garantizar que el flujo sea aceptable ". lo que evita un comportamiento impredecible del programa, en buena medida.

Parece que las mónadas no tienen que ver con la pureza, ni con el control, sino con el mantenimiento de una identidad de una categoría en la que todo comportamiento es predecible y definido, o no se compila. No puede hacer nada cuando se espera que haga algo, y no puede hacer algo si se espera que no haga nada (visible).

La razón más importante por la que se me ocurre para Monads es: mira el código de procedimiento / OOP, y notarás que no sabes dónde comienza o termina el programa, todo lo que ves es un montón de saltos y muchas matemáticas , magia y misiles. No podrá mantenerlo, y si puede, pasará mucho tiempo concentrando su mente en todo el programa antes de que pueda comprender cualquier parte del mismo, porque la modularidad en este contexto se basa en "secciones" interdependientes de código, donde el código está optimizado para estar lo más relacionado posible para la promesa de eficiencia / interrelación. Las mónadas son muy concretas y están bien definidas por definición, y aseguran que el flujo del programa sea posible de analizar, y aislar partes que son difíciles de analizar, ya que ellas mismas son mónadas. Una mónada parece ser un " o destruir el universo o incluso distorsionar el tiempo: no tenemos ni idea ni garantías de que ES LO QUE ES. Una mónada GARANTIZA QUE ES LO QUE ES. que es muy poderoso o destruir el universo o incluso distorsionar el tiempo: no tenemos ni idea ni garantías de que ES LO QUE ES. Una mónada GARANTIZA QUE ES LO QUE ES. que es muy poderoso

Todas las cosas en el "mundo real" parecen ser mónadas, en el sentido de que está sujeto a leyes observables definidas que evitan la confusión. Esto no significa que tengamos que imitar todas las operaciones de este objeto para crear clases, sino que simplemente podemos decir "un cuadrado es un cuadrado", nada más que un cuadrado, ni siquiera un rectángulo ni un círculo, y "un cuadrado tiene área de la longitud de una de sus dimensiones existentes multiplicada por sí misma. No importa qué cuadrado tenga, si es un cuadrado en el espacio 2D, su área no puede ser más que su longitud al cuadrado, es casi trivial probarlo. Esto es muy poderoso porque no necesitamos hacer afirmaciones para asegurarnos de que nuestro mundo sea como es, solo usamos las implicaciones de la realidad para evitar que nuestros programas se salgan del camino.

Estoy casi seguro de estar equivocado, pero creo que esto podría ayudar a alguien, así que espero que ayude a alguien.


5

En el contexto de Scala, encontrará que la siguiente es la definición más simple. Básicamente flatMap (o enlace) es 'asociativo' y existe una identidad.

trait M[+A] {
  def flatMap[B](f: A => M[B]): M[B] // AKA bind

  // Pseudo Meta Code
  def isValidMonad: Boolean = {
    // for every parameter the following holds
    def isAssociativeOn[X, Y, Z](x: M[X], f: X => M[Y], g: Y => M[Z]): Boolean =
      x.flatMap(f).flatMap(g) == x.flatMap(f(_).flatMap(g))

    // for every parameter X and x, there exists an id
    // such that the following holds
    def isAnIdentity[X](x: M[X], id: X => M[X]): Boolean =
      x.flatMap(id) == x
  }
}

P.ej

// These could be any functions
val f: Int => Option[String] = number => if (number == 7) Some("hello") else None
val g: String => Option[Double] = string => Some(3.14)

// Observe these are identical. Since Option is a Monad 
// they will always be identical no matter what the functions are
scala> Some(7).flatMap(f).flatMap(g)
res211: Option[Double] = Some(3.14)

scala> Some(7).flatMap(f(_).flatMap(g))
res212: Option[Double] = Some(3.14)


// As Option is a Monad, there exists an identity:
val id: Int => Option[Int] = x => Some(x)

// Observe these are identical
scala> Some(7).flatMap(id)
res213: Option[Int] = Some(7)

scala> Some(7)
res214: Some[Int] = Some(7)

NOTA Estrictamente hablando, la definición de una mónada en la programación funcional no es la misma que la definición de una mónada en la teoría de categorías , que se define en turnos de mapy flatten. Aunque son una especie de equivalente bajo ciertas asignaciones. Esta presentación es muy buena: http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category


5

Esta respuesta comienza con un ejemplo motivador, funciona a través del ejemplo, deriva un ejemplo de una mónada y define formalmente "mónada".

Considere estas tres funciones en pseudocódigo:

f(<x, messages>) := <x, messages "called f. ">
g(<x, messages>) := <x, messages "called g. ">
wrap(x)          := <x, "">

ftoma un par ordenado del formulario <x, messages>y devuelve un par ordenado. Deja el primer elemento intacto y se agrega "called f. "al segundo elemento. Lo mismo con g.

Puede componer estas funciones y obtener su valor original, junto con una cadena que muestra en qué orden se llamaron las funciones:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<x, "called g. ">)
= <x, "called g. called f. ">

Que no le gusta el hecho de que fy ges responsable de sus propios añadiendo mensajes de registro de la información de registro anterior. (Solo imagine por el argumento de que en lugar de agregar cadenas, fyg debe realizar una lógica complicada en el segundo elemento del par. Sería un dolor repetir esa lógica complicada en dos, o más, funciones diferentes).

Prefieres escribir funciones más simples:

f(x)    := <x, "called f. ">
g(x)    := <x, "called g. ">
wrap(x) := <x, "">

Pero mira lo que sucede cuando los compones:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<<x, "">, "called g. ">)
= <<<x, "">, "called g. ">, "called f. ">

El problema es que pasar un par a una función no te da lo que quieres. Pero, ¿y si pudieras alimentar un par en una función?

  feed(f, feed(g, wrap(x)))
= feed(f, feed(g, <x, "">))
= feed(f, <x, "called g. ">)
= <x, "called g. called f. ">

Leer feed(f, m)como "alimentar men f". Para alimentar a una pareja <x, messages>en una función fes a pasar x en f, obtener <y, message>fuera de f, y volver <y, messages message>.

feed(f, <x, messages>) := let <y, message> = f(x)
                          in  <y, messages message>

Observe lo que sucede cuando hace tres cosas con sus funciones:

Primero: si ajusta un valor y luego alimenta el par resultante en una función:

  feed(f, wrap(x))
= feed(f, <x, "">)
= let <y, message> = f(x)
  in  <y, "" message>
= let <y, message> = <x, "called f. ">
  in  <y, "" message>
= <x, "" "called f. ">
= <x, "called f. ">
= f(x)

Eso es lo mismo que pasar el valor a la función.

Segundo: si alimentas un par en wrap:

  feed(wrap, <x, messages>)
= let <y, message> = wrap(x)
  in  <y, messages message>
= let <y, message> = <x, "">
  in  <y, messages message>
= <x, messages "">
= <x, messages>

Eso no cambia la pareja.

Tercero: si se define una función que toma xy se alimenta g(x)en f:

h(x) := feed(f, g(x))

y alimentar un par en él:

  feed(h, <x, messages>)
= let <y, message> = h(x)
  in  <y, messages message>
= let <y, message> = feed(f, g(x))
  in  <y, messages message>
= let <y, message> = feed(f, <x, "called g. ">)
  in  <y, messages message>
= let <y, message> = let <z, msg> = f(x)
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = let <z, msg> = <x, "called f. ">
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = <x, "called g. " "called f. ">
  in <y, messages message>
= <x, messages "called g. " "called f. ">
= feed(f, <x, messages "called g. ">)
= feed(f, feed(g, <x, messages>))

Es lo mismo que alimentar al par gy alimentar al par resultante f.

Tienes la mayor parte de una mónada. Ahora solo necesita saber acerca de los tipos de datos en su programa.

¿Qué tipo de valor es <x, "called f. ">? Bueno, eso depende de qué tipo de valor xsea. Si xes de tipo t, entonces su par es un valor de tipo "par de ty cadena". Llama a ese tipo M t.

Mes un constructor de tipos: Msolo no se refiere a un tipo, sino que se M _refiere a un tipo una vez que completa el espacio en blanco con un tipo. Un M intes un par de int y una cadena. Un M stringes un par de una cadena y una cadena. Etc.

¡Felicitaciones, has creado una mónada!

Formalmente, tu mónada es la tupla <M, feed, wrap>.

Una mónada es una tupla <M, feed, wrap>donde:

  • M es un constructor de tipos.
  • feedtoma una (función que toma una ty devuelve una M u) y una M ty devuelve una M u.
  • wraptoma un vy devuelve un M v.

t, uy vson tres tipos que pueden ser o no iguales. Una mónada satisface las tres propiedades que probó para su mónada específica:

  • Alimentar una función envuelta ten una función es lo mismo que pasar la función no envuelta ta la función.

    Formalmente: feed(f, wrap(x)) = f(x)

  • La alimentación de un M ta wrapno hace nada para el M t.

    Formalmente: feed(wrap, m) = m

  • Alimentando un M t(llámelo m) en una función que

    • pasa talg
    • recibe un M u(llámalo n) deg
    • alimenta nenf

    es lo mismo que

    • alimentando mag
    • obteniendo ndeg
    • alimentando naf

    Formalmente: feed(h, m) = feed(f, feed(g, m))dondeh(x) := feed(f, g(x))

Por lo general, feedse llama bind(AKA >>=en Haskell) y wrapse llama return.


5

Trataré de explicar Monaden el contexto de Haskell.

En la programación funcional, la composición de funciones es importante. Permite que nuestro programa consista en funciones pequeñas y fáciles de leer.

Digamos que tenemos dos funciones: g :: Int -> Stringy f :: String -> Bool.

Podemos hacer (f . g) x, que es lo mismo que f (g x), donde xhay un Intvalor.

Al hacer composición / aplicar el resultado de una función a otra, es importante que los tipos coincidan. En el caso anterior, el tipo del resultado devuelto por gdebe ser el mismo que el tipo aceptado por f.

Pero a veces los valores están en contextos, y esto hace que sea un poco menos fácil alinear tipos. (Tener valores en contextos es muy útil. Por ejemplo, el Maybe Inttipo representa un Intvalor que puede no estar allí, el IO Stringtipo representa un Stringvalor que está allí como resultado de realizar algunos efectos secundarios).

Digamos que ahora tenemos g1 :: Int -> Maybe Stringy f1 :: String -> Maybe Bool. g1y f1son muy similares a gy frespectivamente.

No podemos hacer (f1 . g1) xo f1 (g1 x), donde xhay un Intvalor. El tipo de resultado devuelto por g1no es el que se f1espera.

Podríamos componer fy gcon el .operador, pero ahora no podemos componer f1y g1con .. El problema es que no podemos pasar directamente un valor en un contexto a una función que espera un valor que no está en un contexto.

¿No sería bueno si presentamos un operador para componer g1y f1, de modo que podamos escribir (f1 OPERATOR g1) x? g1devuelve un valor en un contexto. El valor se sacará de contexto y se aplicará a f1. Y sí, tenemos un operador así. Es <=<.

También tenemos el >>= operador que hace por nosotros exactamente lo mismo, aunque en una sintaxis ligeramente diferente.

Escribimos: g1 x >>= f1. g1 xes un Maybe Intvalor El >>=operador ayuda a sacar ese Intvalor del contexto de "quizás no existe" y aplicarlo f1. El resultado de f1, que es a Maybe Bool, será el resultado de toda la >>=operación.

Y finalmente, ¿por qué es Monadútil? Porque Monades la clase de tipo que define el >>=operador, muy similar a la Eqclase de tipo que define los operadores ==y /=.

Para concluir, la Monadclase de tipo define el >>=operador que nos permite pasar valores en un contexto (los denominamos valores monádicos) a funciones que no esperan valores en un contexto. El contexto será atendido.

Si hay algo que recordar aquí, es que Monadpermite la composición de funciones que involucra valores en contextos .



IOW, Monad es un protocolo de llamada de función generalizada.
Will Ness

Su respuesta es la más útil en mi opinión. Aunque tengo que decir que creo que el énfasis debe estar en el hecho de que las funciones a las que se refiere no solo involucran valores en contextos, sino que ponen valores activamente en contextos. Entonces, por ejemplo, una función, f :: ma -> mb se compondría muy fácilmente con otra función, g :: mb -> m c. Pero las mónadas (enlace específicamente) nos permiten componer perpetuamente funciones que ponen su entrada en el mismo contexto, sin que primero tengamos que sacar el valor de ese contexto (lo que eliminaría efectivamente la información del valor)
James

@ James ¿Creo que ese debería ser el énfasis para los functors?
Jonas

@Jonas, supongo que no te expliqué adecuadamente. Cuando digo que las funciones ponen valores en contextos, quiero decir que tienen tipo (a -> mb). Estos son muy útiles ya que poner un valor en un contexto agrega nueva información, pero generalmente sería difícil encadenar a (a -> mb) y a (b -> mc) juntos, ya que no podemos simplemente extraer el valor del contexto. Por lo tanto, tendríamos que usar un proceso complicado para encadenar estas funciones de una manera sensata, dependiendo del contexto específico y las mónadas solo nos permiten hacer esto de manera consistente, independientemente del contexto.
James

5

tl; dr

{-# LANGUAGE InstanceSigs #-}

newtype Id t = Id t

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

Prólogo

El operador $de la aplicación de funciones

forall a b. a -> b

se define canónicamente

($) :: (a -> b) -> a -> b
f $ x = f x

infixr 0 $

en términos de aplicación de función primitiva Haskell f x( infixl 10).

La composición .se define en términos de $como

(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x

infixr 9 .

y satisface las equivalencias forall f g h.

     f . id  =  f            :: c -> d   Right identity
     id . g  =  g            :: b -> c   Left identity
(f . g) . h  =  f . (g . h)  :: a -> d   Associativity

.es asociativo, y ides su identidad derecha e izquierda.

El triple de Kleisli

En programación, una mónada es un constructor de tipo functor con una instancia de la clase de tipo mónada. Hay varias variantes equivalentes de definición e implementación, cada una con intuiciones ligeramente diferentes sobre la abstracción de la mónada.

Un functor es un constructor fde tipo de tipo * -> *con una instancia de la clase de tipo functor.

{-# LANGUAGE KindSignatures #-}

class Functor (f :: * -> *) where
   map :: (a -> b) -> (f a -> f b)

Además de seguir el protocolo de tipo forzado estáticamente, las instancias de la clase de tipo functor deben obedecer las leyes de funge algebraicas forall f g.

       map id  =  id           :: f t -> f t   Identity
map f . map g  =  map (f . g)  :: f a -> f c   Composition / short cut fusion

Los cálculos de functor tienen el tipo

forall f t. Functor f => f t

Un cálculo c rconsiste en resultados r dentro del contexto c .

Las funciones monádicas unarias o las flechas de Kleisli tienen el tipo

forall m a b. Functor m => a -> m b

Las flechas de Kleisi son funciones que toman un argumento ay devuelven un cálculo monádico m b.

Las mónadas se definen canónicamente en términos del triple de Kleisli forall m. Functor m =>

(m, return, (=<<))

implementado como la clase de tipo

class Functor m => Monad m where
   return :: t -> m t
   (=<<)  :: (a -> m b) -> m a -> m b

infixr 1 =<<

La identidad de Kleisli return es una flecha de Kleisli que promueve un valor ten un contexto monádico m. La aplicación de extensión o Kleisli=<< aplica una flecha de Kleisli a -> m ba los resultados de un cálculo m a.

La composición de Kleisli <=< se define en términos de extensión como

(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f <=< g = \ x -> f =<< g x

infixr 1 <=<

<=< compone dos flechas Kleisli, aplicando la flecha izquierda a los resultados de la aplicación de la flecha derecha.

Las instancias de la clase de tipo mónada deben obedecer las leyes de mónada , más elegantemente expresadas en términos de composición de Kleisli:forall f g h.

   f <=< return  =  f                :: c -> m d   Right identity
   return <=< g  =  g                :: b -> m c   Left identity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d   Associativity

<=<es asociativo, y returnes su identidad derecha e izquierda.

Identidad

El tipo de identidad

type Id t = t

es la función de identidad en tipos

Id :: * -> *

Interpretado como un functor,

   return :: t -> Id t
=      id :: t ->    t

    (=<<) :: (a -> Id b) -> Id a -> Id b
=     ($) :: (a ->    b) ->    a ->    b

    (<=<) :: (b -> Id c) -> (a -> Id b) -> (a -> Id c)
=     (.) :: (b ->    c) -> (a ->    b) -> (a ->    c)

En Haskell canónico, se define la mónada de identidad

newtype Id t = Id t

instance Functor Id where
   map :: (a -> b) -> Id a -> Id b
   map f (Id x) = Id (f x)

instance Monad Id where
   return :: t -> Id t
   return = Id

   (=<<) :: (a -> Id b) -> Id a -> Id b
   f =<< (Id x) = f x

Opción

Un tipo de opción

data Maybe t = Nothing | Just t

codifica la computación Maybe tque no necesariamente produce un resultado t, computación que puede "fallar". La opción mónada está definida

instance Functor Maybe where
   map :: (a -> b) -> (Maybe a -> Maybe b)
   map f (Just x) = Just (f x)
   map _ Nothing  = Nothing

instance Monad Maybe where
   return :: t -> Maybe t
   return = Just

   (=<<) :: (a -> Maybe b) -> Maybe a -> Maybe b
   f =<< (Just x) = f x
   _ =<< Nothing  = Nothing

a -> Maybe bse aplica a un resultado solo si Maybe aproduce un resultado.

newtype Nat = Nat Int

Los números naturales pueden codificarse como aquellos enteros mayores o iguales a cero.

toNat :: Int -> Maybe Nat
toNat i | i >= 0    = Just (Nat i)
        | otherwise = Nothing

Los números naturales no se cierran bajo resta.

(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)

infixl 6 -?

La opción mónada cubre una forma básica de manejo de excepciones.

(-? 20) <=< toNat :: Int -> Maybe Nat

Lista

La mónada de la lista, sobre el tipo de lista

data [] t = [] | t : [t]

infixr 5 :

y su operación monoide aditiva "agregar"

(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[]       ++ ys = ys

infixr 5 ++

codifica el cálculo no lineal que[t] produce una cantidad natural 0, 1, ...de resultados t.

instance Functor [] where
   map :: (a -> b) -> ([a] -> [b])
   map f (x : xs) = f x : map f xs
   map _ []       = []

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> [a] -> [b]
   f =<< (x : xs) = f x ++ (f =<< xs)
   _ =<< []       = []

La extensión =<<concatena ++todas las listas [b]resultantes de las aplicaciones f xde una flecha de Kleisli a -> [b]a elementos de [a]una sola lista de resultados [b].

Deje que los divisores propios de un entero positivo nsean

divisors :: Integral t => t -> [t]
divisors n = filter (`divides` n) [2 .. n - 1]

divides :: Integral t => t -> t -> Bool
(`divides` n) = (== 0) . (n `rem`)

entonces

forall n.  let { f = f <=< divisors } in f n   =   []

Al definir la clase de tipo mónada, en lugar de extensión =<<, el estándar Haskell usa su operador flip, el enlace>>= .

class Applicative m => Monad m where
   (>>=) :: forall a b. m a -> (a -> m b) -> m b

   (>>) :: forall a b. m a -> m b -> m b
   m >> k = m >>= \ _ -> k
   {-# INLINE (>>) #-}

   return :: a -> m a
   return = pure

Por simplicidad, esta explicación usa la jerarquía de clases de tipos

class              Functor f
class Functor m => Monad m

En Haskell, la jerarquía estándar actual es

class                  Functor f
class Functor p     => Applicative p
class Applicative m => Monad m

porque no solo cada mónada es functor, sino que cada aplicativo es un ficticio y cada mónada también es un aplicativo.

Usando la lista mónada, el pseudocódigo imperativo

for a in (1, ..., 10)
   for b in (1, ..., 10)
      p <- a * b
      if even(p)
         yield p

se traduce aproximadamente al bloque do ,

do a <- [1 .. 10]
   b <- [1 .. 10]
   let p = a * b
   guard (even p)
   return p

la comprensión de mónada equivalente ,

[ p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p ]

y la expresión

[1 .. 10] >>= (\ a ->
   [1 .. 10] >>= (\ b ->
      let p = a * b in
         guard (even p) >>       -- [ () | even p ] >>
            return p
      )
   )

La comprensión de notación y mónada son azúcar sintáctica para expresiones de enlace anidadas. El operador de enlace se usa para el enlace de nombre local de resultados monádicos.

let x = v in e    =   (\ x -> e)  $  v   =   v  &  (\ x -> e)
do { r <- m; c }  =   (\ r -> c) =<< m   =   m >>= (\ r -> c)

dónde

(&) :: a -> (a -> b) -> b
(&) = flip ($)

infixl 0 &

La función de guardia está definida

guard :: Additive m => Bool -> m ()
guard True  = return ()
guard False = fail

donde el tipo de unidad o "tupla vacía"

data () = ()

Las mónadas aditivas que admiten elección y falla pueden abstraerse utilizando una clase de tipo

class Monad m => Additive m where
   fail  :: m t
   (<|>) :: m t -> m t -> m t

infixl 3 <|>

instance Additive Maybe where
   fail = Nothing

   Nothing <|> m = m
   m       <|> _ = m

instance Additive [] where
   fail = []
   (<|>) = (++)

donde faily <|>formar un monoideforall k l m.

     k <|> fail  =  k
     fail <|> l  =  l
(k <|> l) <|> m  =  k <|> (l <|> m)

y failes el elemento cero absorbente / aniquilador de mónadas aditivas

_ =<< fail  =  fail

Si en

guard (even p) >> return p

even pes cierto, entonces el guardia produce [()]y, por definición de >>, la función constante local

\ _ -> return p

se aplica al resultado (). Si es falso, el guardia produce la lista mónada fail( []), que no produce ningún resultado para que se aplique una flecha de Kleisli >>, por lo que estop se omite.

Estado

Infamemente, las mónadas se utilizan para codificar la computación con estado.

Un procesador de estado es una función.

forall st t. st -> (t, st)

que transiciona un estado sty produce un resultado t. El estado st puede ser cualquier cosa. Nada, bandera, conteo, matriz, mango, máquina, mundo.

El tipo de procesadores de estado generalmente se llama

type State st t = st -> (t, st)

La mónada del procesador de estado es el * -> *functor amable State st. Las flechas de Kleisli de la mónada del procesador de estado son funciones

forall st a b. a -> (State st) b

En Haskell canónico, se define la versión perezosa de la mónada del procesador de estado

newtype State st t = State { stateProc :: st -> (t, st) }

instance Functor (State st) where
   map :: (a -> b) -> ((State st) a -> (State st) b)
   map f (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  (f x, s1)

instance Monad (State st) where
   return :: t -> (State st) t
   return x = State $ \ s -> (x, s)

   (=<<) :: (a -> (State st) b) -> (State st) a -> (State st) b
   f =<< (State p) = State $ \ s0 -> let (x, s1) = p s0
                                     in  stateProc (f x) s1

Un procesador de estado se ejecuta proporcionando un estado inicial:

run :: State st t -> st -> (t, st)
run = stateProc

eval :: State st t -> st -> t
eval = fst . run

exec :: State st t -> st -> st
exec = snd . run

El acceso estatal es proporcionado por primitivas gety putmétodos de abstracción sobre mónadas con estado :

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

class Monad m => Stateful m st m -> st where
   get :: m st
   put :: st -> m ()

m -> stdeclara una dependencia funcional del tipo de estado sten la mónada m; que a State t, por ejemplo, determinará que el tipo de estado sea texclusivo.

instance Stateful (State st) st where
   get :: State st st
   get = State $ \ s -> (s, s)

   put :: st -> State st ()
   put s = State $ \ _ -> ((), s)

con el tipo de unidad utilizado de forma análoga a voiden C.

modify :: Stateful m st => (st -> st) -> m ()
modify f = do
   s <- get
   put (f s)

gets :: Stateful m st => (st -> t) -> m t
gets f = do
   s <- get
   return (f s)

gets A menudo se utiliza con los accesos de campo de registro.

El estado de la mónada equivalente del subproceso variable

let s0 = 34
    s1 = (+ 1) s0
    n = (* 12) s1
    s2 = (+ 7) s1
in  (show n, s2)

donde s0 :: Int, es el igualmente referencialmente transparente, pero infinitamente más elegante y práctico

(flip run) 34
   (do
      modify (+ 1)
      n <- gets (* 12)
      modify (+ 7)
      return (show n)
   )

modify (+ 1)es un cálculo de tipo State Int (), excepto por su efecto equivalente a return ().

(flip run) 34
   (modify (+ 1) >>
      gets (* 12) >>= (\ n ->
         modify (+ 7) >>
            return (show n)
      )
   )

La ley de asociatividad de la mónada se puede escribir en términos de >>= forall m f g.

(m >>= f) >>= g  =  m >>= (\ x -> f x >>= g)

o

do {                 do {                   do {
   r1 <- do {           x <- m;                r0 <- m;
      r0 <- m;   =      do {            =      r1 <- f r0;
      f r0                 r1 <- f x;          g r1
   };                      g r1             }
   g r1                 }
}                    }

Al igual que en la programación orientada a la expresión (por ejemplo, Rust), la última declaración de un bloque representa su rendimiento. El operador de enlace a veces se denomina "punto y coma programable".

Las primitivas de la estructura de control de iteración de la programación imperativa estructurada se emulan monádicamente.

for :: Monad m => (a -> m b) -> [a] -> m ()
for f = foldr ((>>) . f) (return ())

while :: Monad m => m Bool -> m t -> m ()
while c m = do
   b <- c
   if b then m >> while c m
        else return ()

forever :: Monad m => m t
forever m = m >> forever m

De entrada y salida

data World

La mónada del procesador de estado mundial de E / S es una reconciliación de Haskell puro y el mundo real, de semántica operativa imperativa y denotativa funcional. Un análogo cercano de la implementación estricta real:

type IO t = World -> (t, World)

La interacción es facilitada por primitivas impuras

getChar         :: IO Char
putChar         :: Char -> IO ()
readFile        :: FilePath -> IO String
writeFile       :: FilePath -> String -> IO ()
hSetBuffering   :: Handle -> BufferMode -> IO ()
hTell           :: Handle -> IO Integer
. . .              . . .

La impureza del código que usa IOprimitivas está permanentemente protocolizada por el sistema de tipos. Debido a que la pureza es asombrosa, lo que sucede en IO, se queda adentro IO.

unsafePerformIO :: IO t -> t

O, al menos, debería.

La firma tipo de un programa Haskell

main :: IO ()
main = putStrLn "Hello, World!"

se expande a

World -> ((), World)

Una función que transforma un mundo.

Epílogo

La categoría cuyos objetos son tipos de Haskell y qué morfismos son funciones entre los tipos de Haskell es, "rápido y suelto", la categoría Hask .

Un functor Tes un mapeo de una categoría Ca una categoría D; para cada objeto en Cun objeto enD

Tobj :  Obj(C) -> Obj(D)
   f :: *      -> *

y para cada morfismo en Cun morfismo enD

Tmor :  HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
 map :: (a -> b)   -> (f a -> f b)

donde X, Yson objetos en C. HomC(X, Y)es la clase de homomorfismo de todos los morfismos X -> Yen C. El functor debe preservar la identidad y composición del morfismo, la "estructura" de C, en D.

                    Tmor    Tobj

      T(id)  =  id        : T(X) -> T(X)   Identity
T(f) . T(g)  =  T(f . g)  : T(X) -> T(Z)   Composition

La categoría Kleisli de una categoría Cestá dada por un triple Kleisli

<T, eta, _*>

de un endofunctor

T : C -> C

( f), un morfismo de identidad eta( return) y un operador de extensión *( =<<).

Cada morfismo de Kleisli en Hask

      f :  X -> T(Y)
      f :: a -> m b

por el operador de extensión

   (_)* :  Hom(X, T(Y)) -> Hom(T(X), T(Y))
  (=<<) :: (a -> m b)   -> (m a -> m b)

se le da un morfismo en Haskla categoría de Kleisli

     f* :  T(X) -> T(Y)
(f =<<) :: m a  -> m b

La composición en la categoría Kleisli .Tse da en términos de extensión

 f .T g  =  f* . g       :  X -> T(Z)
f <=< g  =  (f =<<) . g  :: a -> m c

y satisface los axiomas de categoría

       eta .T g  =  g                :  Y -> T(Z)   Left identity
   return <=< g  =  g                :: b -> m c

       f .T eta  =  f                :  Z -> T(U)   Right identity
   f <=< return  =  f                :: c -> m d

  (f .T g) .T h  =  f .T (g .T h)    :  X -> T(U)   Associativity
(f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d

que, aplicando las transformaciones de equivalencia

     eta .T g  =  g
     eta* . g  =  g               By definition of .T
     eta* . g  =  id . g          forall f.  id . f  =  f
         eta*  =  id              forall f g h.  f . h  =  g . h  ==>  f  =  g

(f .T g) .T h  =  f .T (g .T h)
(f* . g)* . h  =  f* . (g* . h)   By definition of .T
(f* . g)* . h  =  f* . g* . h     . is associative
    (f* . g)*  =  f* . g*         forall f g h.  f . h  =  g . h  ==>  f  =  g

en términos de extensión se dan canónicamente

               eta*  =  id                 :  T(X) -> T(X)   Left identity
       (return =<<)  =  id                 :: m t -> m t

           f* . eta  =  f                  :  Z -> T(U)      Right identity
   (f =<<) . return  =  f                  :: c -> m d

          (f* . g)*  =  f* . g*            :  T(X) -> T(Z)   Associativity
(((f =<<) . g) =<<)  =  (f =<<) . (g =<<)  :: m a -> m c

Las mónadas también se pueden definir en términos no de extensión de Kleislian, sino de una transformación natural mu, en la programación llamada join. Una mónada se define en términos de muun triple sobre una categoría C, de un endofunctor

     T :  C -> C
     f :: * -> *

y dos transformaciones naturales

   eta :  Id -> T
return :: t  -> f t

    mu :  T . T   -> T
  join :: f (f t) -> f t

satisfaciendo las equivalencias

       mu . T(mu)  =  mu . mu               :  T . T . T -> T . T   Associativity
  join . map join  =  join . join           :: f (f (f t)) -> f t

      mu . T(eta)  =  mu . eta       =  id  :  T -> T               Identity
join . map return  =  join . return  =  id  :: f t -> f t

La clase de tipo mónada se define entonces

class Functor m => Monad m where
   return :: t -> m t
   join   :: m (m t) -> m t

La muimplementación canónica de la opción mónada:

instance Monad Maybe where
   return = Just

   join (Just m) = m
   join Nothing  = Nothing

La concatfunción

concat :: [[a]] -> [a]
concat (x : xs) = x ++ concat xs
concat []       = []

es el joinde la lista mónada.

instance Monad [] where
   return :: t -> [t]
   return = (: [])

   (=<<) :: (a -> [b]) -> ([a] -> [b])
   (f =<<) = concat . map f

Las implementaciones de joinse pueden traducir del formulario de extensión utilizando la equivalencia

     mu  =  id*           :  T . T -> T
   join  =  (id =<<)      :: m (m t) -> m t

La traducción inversa de mua forma de extensión viene dada por

     f*  =  mu . T(f)     :  T(X) -> T(Y)
(f =<<)  =  join . map f  :: m a -> m b

Pero, ¿por qué una teoría tan abstracta podría ser útil para la programación?

La respuesta es simple: como informáticos, ¡ valoramos la abstracción ! Cuando diseñamos la interfaz para un componente de software, queremos que revele lo menos posible sobre la implementación. Queremos poder reemplazar la implementación con muchas alternativas, muchas otras 'instancias' del mismo 'concepto'. Cuando diseñamos una interfaz genérica para muchas bibliotecas de programas, es aún más importante que la interfaz que elijamos tenga una variedad de implementaciones. Es la generalidad del concepto de mónada lo que valoramos tanto, es porque teoría de categorías es tan abstracta que sus conceptos son tan útiles para la programación.

No es sorprendente, entonces, que la generalización de las mónadas que presentamos a continuación también tenga una estrecha conexión con la teoría de categorías. Pero hacemos hincapié en que nuestro propósito es muy práctico: no es 'implementar la teoría de categorías', es encontrar una forma más general de estructurar las bibliotecas combinadoras. ¡Es simplemente nuestra buena suerte que los matemáticos ya hayan hecho gran parte del trabajo por nosotros!

de generalizar mónadas a flechas por John Hughes


4

Lo que el mundo necesita es otra publicación de blog de mónada, pero creo que esto es útil para identificar mónadas existentes en la naturaleza.

Triángulo de Sierpinski

Lo anterior es un fractal llamado triángulo de Sierpinski, el único fractal que puedo recordar dibujar. Los fractales son estructuras auto-similares como el triángulo anterior, en el cual las partes son similares al todo (en este caso, exactamente la mitad de la escala como triángulo padre).

Las mónadas son fractales. Dada una estructura de datos monádica, sus valores se pueden componer para formar otro valor de la estructura de datos. Es por eso que es útil para la programación, y es por eso que ocurre en muchas situaciones.


3
¿Quieres decir "lo que el mundo no necesita ..."? Buena analogía sin embargo!
groverboy

@ icc97 tienes razón: el significado es lo suficientemente claro. Sarcasmo involuntario, disculpas al autor.
groverboy

Lo que el mundo necesita es otro hilo de comentarios que confirme un sarcasmo, pero si lo leí con cuidado, lo he escrito, pero eso debería aclararlo.
Eugene Yokota


4

Deje que el " {| a |m}" siguiente represente algún dato monádico. Un tipo de datos que anuncia un a:

        (I got an a!)
          /        
    {| a |m}

La función, fsabe cómo crear una mónada, si solo tuviera un a:

       (Hi f! What should I be?)
                      /
(You?. Oh, you'll be /
 that data there.)  /
 /                 /  (I got a b.)
|    --------------      |
|  /                     |
f a                      |
  |--later->       {| b |m}

Aquí vemos la función, fintenta evaluar una mónada pero es reprendido.

(Hmm, how do I get that a?)
 o       (Get lost buddy.
o         Wrong type.)
o       /
f {| a |m}

Funtion, fencuentra una forma de extraer el amediante >>=.

        (Muaahaha. How you 
         like me now!?)       
    (Better.)      \
        |     (Give me that a.)
(Fine, well ok.)    |
         \          |
   {| a |m}   >>=   f

Poco fsabe, la mónada y >>=están en colusión.

            (Yah got an a for me?)       
(Yeah, but hey    | 
 listen. I got    |
 something to     |
 tell you first   |
 ...)   \        /
         |      /
   {| a |m}   >>=   f

¿Pero de qué hablan realmente? Bueno, eso depende de la mónada. Hablar únicamente en abstracto tiene un uso limitado; tienes que tener alguna experiencia con mónadas particulares para desarrollar el entendimiento.

Por ejemplo, el tipo de datos Quizás

 data Maybe a = Nothing | Just a

tiene una instancia de mónada que actuará de la siguiente manera ...

En donde, si el caso es Just a

            (Yah what is it?)       
(... hm? Oh,      |
forget about it.  |
Hey a, yr up.)    | 
            \     |
(Evaluation  \    |
time already? \   |
Hows my hair?) |  |
      |       /   |
      |  (It's    |
      |  fine.)  /
      |   /     /    
   {| a |m}   >>=   f

Pero para el caso de Nothing

        (Yah what is it?)       
(... There      |
is no a. )      |
  |        (No a?)
(No a.)         |
  |        (Ok, I'll deal
  |         with this.)
   \            |
    \      (Hey f, get lost.) 
     \          |   ( Where's my a? 
      \         |     I evaluate a)
       \    (Not any more  |
        \    you don't.    |
         |   We're returning
         |   Nothing.)   /
         |      |       /
         |      |      /
         |      |     /
   {| a |m}   >>=   f      (I got a b.)
                    |  (This is   \
                    |   such a     \
                    |   sham.) o o  \
                    |               o|
                    |--later-> {| b |m}

Entonces, la mónada tal vez permite que un cálculo continúe si realmente contiene lo aque anuncia, pero aborta el cálculo si no lo hace. El resultado, sin embargo, sigue siendo un dato monádico, aunque no el resultado de f. Por esta razón, la mónada Quizás se usa para representar el contexto del fracaso.

Las diferentes mónadas se comportan de manera diferente. Las listas son otros tipos de datos con instancias monádicas. Se comportan de la siguiente manera:

(Ok, here's your a. Well, its
 a bunch of them, actually.)
  |
  |    (Thanks, no problem. Ok
  |     f, here you go, an a.)
  |       |
  |       |        (Thank's. See
  |       |         you later.)
  |  (Whoa. Hold up f,      |
  |   I got another         |
  |   a for you.)           |
  |       |      (What? No, sorry.
  |       |       Can't do it. I 
  |       |       have my hands full
  |       |       with all these "b" 
  |       |       I just made.) 
  |  (I'll hold those,      |
  |   you take this, and   /
  |   come back for more  /
  |   when you're done   / 
  |   and we'll do it   / 
  |   again.)          /
   \      |  ( Uhhh. All right.)
    \     |       /    
     \    \      /
{| a |m}   >>=  f  

En este caso, la función sabía cómo hacer una lista a partir de su entrada, pero no sabía qué hacer con entradas adicionales y listas adicionales. El enlace >>=, ayudado fmediante la combinación de las múltiples salidas. Incluyo este ejemplo para mostrar que si bien >>=es responsable de la extracción a, también tiene acceso a la salida eventual de f. De hecho, nunca extraerá ninguno a amenos que sepa que el resultado final tiene el mismo tipo de contexto.

Hay otras mónadas que se utilizan para representar diferentes contextos. Aquí hay algunas caracterizaciones de algunas más. La IOmónada en realidad no tiene un a, pero conoce a un chico y lo conseguirá apor ti. La State stmónada tiene un alijo secreto de stque pasará fdebajo de la mesa, a pesar de que facaba de llegar pidiendo un a. La Reader rmónada es similar a State st, a pesar de que sólo deja fver r.

El punto en todo esto es que cualquier tipo de datos que se declare a sí mismo como una mónada está declarando algún tipo de contexto en torno a la extracción de un valor de la mónada. ¿La gran ganancia de todo esto? Bueno, es bastante fácil armar un cálculo con algún tipo de contexto. Sin embargo, puede volverse desordenado al unir varios cálculos cargados de contexto. Las operaciones de mónada se encargan de resolver las interacciones de contexto para que el programador no tenga que hacerlo.

Tenga en cuenta que el uso de la >>=facilita un desastre al quitarle algo de autonomía f. Es decir, en el caso anterior, Nothingpor ejemplo, fya no se puede decidir qué hacer en el caso de Nothing; se codifica en >>=. Esta es la compensación. Si fue necesario fdecidir qué hacer en el caso de Nothing, entonces fdebería haber sido una función desde Maybe ahasta Maybe b. En este caso, Maybeser una mónada es irrelevante.

Tenga en cuenta, sin embargo, que a veces un tipo de datos no exporta sus constructores (mirándolo IO), y si queremos trabajar con el valor anunciado, tenemos pocas opciones más que trabajar con su interfaz monádica.


3

Una mónada es una cosa utilizada para encapsular objetos que tienen un estado cambiante. Se encuentra con mayor frecuencia en idiomas que, de lo contrario, no le permiten tener un estado modificable (por ejemplo, Haskell).

Un ejemplo sería para el archivo de E / S.

Podrías usar una mónada para E / S de archivo para aislar la naturaleza de estado cambiante solo para el código que usó la mónada. El código dentro de la mónada puede ignorar efectivamente el estado cambiante del mundo fuera de la mónada; esto hace que sea mucho más fácil razonar sobre el efecto general de su programa.


3
Según tengo entendido, las mónadas son más que eso. Encapsular el estado mutable en un lenguaje funcional "puro" es solo una aplicación de mónadas.
thSoft
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.