significa que monada leibniz espiritual españa oop functional-programming monads

oop - que - monada leibniz



¿Mónada en inglés sencillo?(Para el programador OOP sin fondo FP) (18)

En términos que un programador OOP entendería (sin ningún fondo de programación funcional), ¿qué es una mónada?

¿Qué problema resuelve y cuáles son los lugares más comunes que se utilizan?

EDITAR:

Para aclarar el tipo de comprensión que estaba buscando, digamos que estaba convirtiendo una aplicación de PF que tenía mónadas en una aplicación de POO. ¿Qué harías para trasladar las responsabilidades de las mónadas a la aplicación OOP?


En términos que un programador OOP entendería (sin ningún fondo de programación funcional), ¿qué es una mónada?

No hay tal explicación que yo sepa, y la sugerencia de que existe existe la arrogancia por parte de los programadores OOP.

¿Qué problema resuelve y cuáles son los lugares más comunes que se utilizan?

Las mónadas resuelven muchos problemas diferentes con una interfaz unificada, por lo que son útiles. Lo principal para lo que se usan es la pereza: permiten funciones perezosas con efectos secundarios, lo que es importante para FFI y similares. También se utilizan comúnmente para el manejo y análisis de errores.

Para aclarar el tipo de comprensión que estaba buscando, digamos que estaba convirtiendo una aplicación de PF que tenía mónadas en una aplicación de POO. ¿Qué harías para trasladar las responsabilidades de las mónadas a la aplicación OOP?

Dado que la pereza no es remotamente común en la POO, en su mayor parte serían irrelevantes. Sin embargo, usted podría portar el manejo de errores monádicos.


En términos que un programador OOP entendería (sin ningún fondo de programación funcional), ¿qué es una mónada?

¿Qué problema resuelve y cuáles son los lugares más comunes que se utilizan? ¿Cuáles son los lugares más comunes que se usan?

En términos de programación OO, una mónada es una interfaz (o más probablemente una mezcla), parametrizada por un tipo, con dos métodos, return y bind que describen:

  • Cómo inyectar un valor para obtener un valor monádico de ese tipo de valor inyectado;
  • Cómo usar una función que hace un valor monádico de uno no monádico, en un valor monádico.

El problema que resuelve es el mismo tipo de problema que se esperaría de cualquier interfaz, a saber, "Tengo un montón de clases diferentes que hacen cosas diferentes, pero parece que hacen esas cosas diferentes de una manera que tiene una similitud subyacente. Cómo ¿Puedo describir esa similitud entre ellos, incluso si las clases en sí mismas no son realmente subtipos de algo más cercano que la clase ''Objeto''? "

Más específicamente, la "interfaz" de IEnumerator es similar a IEnumerator o IIterator en que toma un tipo que a su vez toma un tipo. Sin embargo, el "punto" principal de Monad es poder conectar operaciones basadas en el tipo de interior, incluso hasta el punto de tener un nuevo "tipo interno", manteniendo, o incluso mejorando, la estructura de información de la clase principal.


¿Por qué necesitamos mónadas?

  1. Queremos programar solo utilizando funciones . ("Programación funcional" después de todo -FP).
  2. Entonces, tenemos un primer gran problema. Este es un programa:

    f(x) = 2 * x

    g(x,y) = x / y

    ¿Cómo podemos decir qué se va a ejecutar primero ? ¿Cómo podemos formar una secuencia ordenada de funciones (es decir, un programa ) usando nada más que funciones?

    Solución: componer funciones . Si quieres primero g luego f , simplemente escribe f(g(x,y)) . Bien pero ...

  3. Más problemas: algunas funciones pueden fallar (es decir, g(2,0) , dividir por 0). No tenemos "excepciones" en FP . ¿Como lo resolvemos?

    Solución: Permitamos que las funciones devuelvan dos tipos de cosas : en lugar de tener g : Real,Real -> Real (función de dos reales a real), permitamos que g : Real,Real -> Real | Nothing g : Real,Real -> Real | Nothing (función de dos reales en (real o nada)).

  4. Pero las funciones deben (para ser más simples) devolver solo una cosa .

    Solución: vamos a crear un nuevo tipo de datos para devolver, un " tipo de boxeo " que encierra tal vez un real o simplemente nada. Por lo tanto, podemos tener g : Real,Real -> Maybe Real . Bien pero ...

  5. ¿Qué pasa ahora con f(g(x,y)) ? f no está listo para consumir un Maybe Real . Y, no queremos cambiar todas las funciones que podríamos conectar con g para consumir un Maybe Real .

    Solución: tengamos una función especial para "conectar" / "componer" / "enlace" . De esa manera, podemos, detrás de las escenas, adaptar la salida de una función para alimentar a la siguiente.

    En nuestro caso: g >>= f (conectar / componer g a f ). Queremos >>= obtener la salida de g , inspeccionarla y, en caso de que sea Nothing simplemente no llame f y devuelva Nothing ; o por el contrario, extrae la caja Real y alimenta f con ella. (Este algoritmo es solo la implementación de >>= para el tipo Maybe ).

  6. Surgen muchos otros problemas que pueden resolverse utilizando este mismo patrón: 1. Use una "caja" para codificar / almacenar diferentes significados / valores, y tenga funciones como g que devuelven esos "valores en caja". 2. Tener compositores / enlazadores g >>= f para ayudar a conectar la salida de f la entrada de f , por lo que no tenemos que cambiar f en absoluto.

  7. Los problemas notables que se pueden resolver con esta técnica son:

    • teniendo un estado global que cada función en la secuencia de funciones ("el programa") puede compartir: solución StateMonad .

    • No nos gustan las "funciones impuras": funciones que producen resultados diferentes para la misma entrada. Por lo tanto, vamos a marcar esas funciones, haciendo que devuelvan un valor etiquetado / en caja: Mónada IO .

Felicidad total !!!!


ACTUALIZACIÓN: Esta pregunta fue el tema de una serie de blogs inmensamente larga, que puedes leer en Monads . ¡Gracias por la gran pregunta!

En términos que un programador OOP entendería (sin ningún fondo de programación funcional), ¿qué es una mónada?

Una mónada es un "amplificador" de tipos que obedecen ciertas reglas y que tiene determinadas operaciones proporcionadas .

Primero, ¿qué es un "amplificador de tipos"? Con eso me refiero a algún sistema que le permita tomar un tipo y convertirlo en un tipo más especial. Por ejemplo, en C # considere Nullable<T> . Este es un amplificador de tipos. Le permite tomar un tipo, digamos int , y agregar una nueva capacidad a ese tipo, a saber, que ahora puede ser nulo cuando antes no podía.

Como segundo ejemplo, considere IEnumerable<T> . Es un amplificador de tipos. Le permite tomar un tipo, digamos, una string , y agregar una nueva capacidad a ese tipo, es decir, que ahora puede hacer una secuencia de cadenas a partir de cualquier número de cadenas individuales.

¿Cuáles son las "ciertas reglas"? Brevemente, hay una forma razonable de que las funciones en el tipo subyacente funcionen en el tipo amplificado de manera que sigan las reglas normales de la composición funcional. Por ejemplo, si tiene una función en enteros, diga

int M(int x) { return x + N(x * 2); }

luego, la función correspondiente en Nullable<int> puede hacer que todos los operadores y llamadas trabajen juntos "de la misma manera" que antes.

(Eso es increíblemente vago e impreciso; usted pidió una explicación que no asumiera nada sobre el conocimiento de la composición funcional).

¿Qué son las "operaciones"?

  1. Existe una operación de "unidad" (a veces llamada confusamente la operación de "retorno") que toma un valor de un tipo simple y crea el valor monádico equivalente. Esto, en esencia, proporciona una manera de tomar un valor de un tipo no amplificado y convertirlo en un valor del tipo amplificado. Podría implementarse como un constructor en un lenguaje OO.

  2. Hay una operación de "vinculación" que toma un valor monádico y una función que puede transformar el valor y devuelve un nuevo valor monádico. Bind es la operación clave que define la semántica de la mónada. Nos permite transformar las operaciones en el tipo no amplificado en operaciones en el tipo amplificado, que obedece a las reglas de composición funcional mencionadas anteriormente.

  3. A menudo hay una manera de obtener el tipo sin amplificar de nuevo del tipo amplificado. Estrictamente hablando, esta operación no requiere tener una mónada. (Aunque es necesario si desea tener un comonad . No consideraremos esto más adelante en este artículo).

De nuevo, tome Nullable<T> como ejemplo. Puede convertir un int en un Nullable<int> con el constructor. El compilador de C # se encarga de la mayoría de los "levantamientos" que pueden contener nulos, pero si no fue así, la transformación del levantamiento es sencilla: una operación, por ejemplo,

int M(int x) { whatever }

se transforma en

Nullable<int> M(Nullable<int> x) { if (x == null) return null; else return new Nullable<int>(whatever); }

Y la conversión de un Nullable<int> en un int se realiza con la propiedad Value .

Es la transformación de la función que es el bit clave. Observe cómo la semántica real de la operación anulable (que una operación en un null propaga el null ) se captura en la transformación. Podemos generalizar esto.

Supongamos que tiene una función de int a int , como nuestra M original. Puede convertir eso fácilmente en una función que toma un int y devuelve un Nullable<int> porque simplemente puede ejecutar el resultado a través del constructor que acepta nul. Ahora suponga que tiene este método de orden superior:

static Nullable<T> Bind<T>(Nullable<T> amplified, Func<T, Nullable<T>> func) { if (amplified == null) return null; else return func(amplified.Value); }

¿Ves lo que puedes hacer con eso? Cualquier método que tome un int y devuelva un int , o tome un int y devuelva un Nullable<int> anulable ahora puede tener aplicada la semántica anulable .

Además: supongamos que tienes dos métodos.

Nullable<int> X(int q) { ... } Nullable<int> Y(int r) { ... }

Y tú quieres componerlos:

Nullable<int> Z(int s) { return X(Y(s)); }

Es decir, Z es la composición de X e Y Pero no puede hacerlo porque X toma un int , e Y devuelve un Nullable<int> . Pero como tiene la operación de "vinculación", puede hacer que esto funcione:

Nullable<int> Z(int s) { return Bind(Y(s), X); }

La operación de enlace en una mónada es lo que hace que funcione la composición de funciones en tipos amplificados. Las "reglas" que mencioné anteriormente son que la mónada conserva las reglas de la composición de la función normal; esa composición con funciones de identidad da como resultado la función original, esa composición es asociativa, etc.

En C #, "Bind" se llama "SelectMany". Echa un vistazo a cómo funciona en la secuencia mónada. Necesitamos tener dos cosas: convertir un valor en una secuencia y vincular operaciones en secuencias. Como beneficio adicional, también tenemos "convertir una secuencia en un valor". Esas operaciones son:

static IEnumerable<T> MakeSequence<T>(T item) { yield return item; } // Extract a value static T First<T>(IEnumerable<T> sequence) { // let''s just take the first one foreach(T item in sequence) return item; throw new Exception("No first item"); } // "Bind" is called "SelectMany" static IEnumerable<T> SelectMany<T>(IEnumerable<T> seq, Func<T, IEnumerable<T>> func) { foreach(T item in seq) foreach(T result in func(item)) yield return result; }

La regla de la mónada nulable era "combinar dos funciones que producen nulasbles juntas, verifique si la interna da lugar a nulo; si lo hace, produzca nula, si no lo hace, llame a la externa con el resultado". Esa es la semántica deseada de nullable.

La regla de la mónada de secuencia es "combinar dos funciones que producen secuencias juntas, aplicar la función externa a cada elemento producido por la función interna y luego concatenar todas las secuencias resultantes juntas". La semántica fundamental de las mónadas se captura en los métodos Bind / SelectMany ; este es el método que te dice lo que realmente significa la mónada.

Podemos hacerlo aún mejor. Supongamos que tiene una secuencia de ints y un método que toma ints y da como resultado secuencias de cadenas. Podríamos generalizar la operación de enlace para permitir la composición de funciones que toman y devuelven diferentes tipos amplificados, siempre que las entradas de una coincidan con las salidas de la otra:

static IEnumerable<U> SelectMany<T,U>(IEnumerable<T> seq, Func<T, IEnumerable<U>> func) { foreach(T item in seq) foreach(U result in func(item)) yield return result; }

Así que ahora podemos decir "amplificar este grupo de enteros individuales en una secuencia de números enteros. Transformar este número entero particular en un grupo de cadenas, amplificado a una secuencia de cadenas. Ahora poner ambas operaciones juntas: amplificar este grupo de números enteros en la concatenación de Todas las secuencias de cuerdas ". Las mónadas te permiten componer tus amplificaciones.

¿Qué problema resuelve y cuáles son los lugares más comunes que se utilizan?

Es más bien como preguntar "¿qué problemas resuelve el patrón de singleton?", Pero lo intentaré.

Las mónadas se utilizan normalmente para resolver problemas como:

  • Necesito crear nuevas capacidades para este tipo y aun así combinar funciones antiguas en este tipo para usar las nuevas capacidades.
  • Necesito capturar un montón de operaciones en tipos y representar esas operaciones como objetos compositivos, crear composiciones cada vez más grandes hasta que tenga representadas las series correctas de operaciones, y luego necesito comenzar a obtener resultados.
  • Necesito representar operaciones de efectos secundarios de manera limpia en un lenguaje que odie los efectos secundarios

C # usa mónadas en su diseño. Como ya se mencionó, el patrón anulable es muy similar a la "quizás mónada". LINQ está construido enteramente de mónadas; SelectMany método SelectMany es lo que hace el trabajo semántico de composición de operaciones. (A Erik Meijer le gusta señalar que cada función LINQ podría ser implementada por SelectMany ; todo lo demás es solo una conveniencia).

Para aclarar el tipo de comprensión que estaba buscando, digamos que estaba convirtiendo una aplicación de PF que tenía mónadas en una aplicación de POO. ¿Qué harías para trasladar las responsabilidades de las mónadas a la aplicación OOP?

La mayoría de los lenguajes OOP no tienen un sistema de tipos lo suficientemente rico como para representar directamente el patrón de la mónada; necesita un sistema de tipos que admita tipos que sean tipos superiores a los tipos genéricos. Así que no trataría de hacer eso. Más bien, implementaría tipos genéricos que representan cada mónada e implementaría métodos que representan las tres operaciones que necesita: convertir un valor en un valor amplificado, (tal vez) convertir un valor amplificado en un valor, y transformar una función en valores no amplificados en Una función sobre valores amplificados.

Un buen lugar para comenzar es cómo implementamos LINQ en C #. Estudie el método SelectMany ; es la clave para entender cómo funciona la secuencia de la mónada en C #. Es un método muy simple, pero muy poderoso!

Sugerido, lectura adicional:

  1. Para una explicación más profunda y teóricamente sólida de las mónadas en C #, recomiendo altamente el artículo de mi colega ( Eric Lippert ), Wes Dyer, sobre el tema. Este artículo es lo que me explicó las mónadas cuando finalmente "hicieron clic" para mí.
  2. Una buena ilustración de por qué es posible que desee una mónada (utiliza Haskell en sus ejemplos) .
  3. Tipo de, "traducción" del artículo anterior a JavaScript.

He escrito un breve artículo que compara el código estándar de Python OOP con el código monádico de python que demuestra el proceso de cálculo subyacente con diagramas. No se asume ningún conocimiento previo de FP. Espero que lo encuentre útil - http://nikgrozev.com/2013/12/10/monads-in-15-minutes/


Para respetar a los lectores rápidos, primero comienzo con una definición precisa, continúo con una explicación más rápida y sencilla y luego paso a los ejemplos.

Aquí hay una definición tanto concisa como precisa, ligeramente redactada:

Una mónada (en informática) es formalmente un mapa que:

  • envía cada tipo X de algún lenguaje de programación dado a un nuevo tipo T(X) (denominado "tipo de T computaciones con valores en X ");

  • equipado con una regla para componer dos funciones de la forma f:X->T(Y) y g:Y->T(Z) a una función g∘f:X->T(Z) ;

  • de una manera que es asociativa en el sentido evidente y unital con respecto a una función de unidad dada llamada pure_X:X->T(X) , que debe considerarse como tomar un valor al cálculo puro que simplemente devuelve ese valor.

Entonces, en palabras simples, una mónada es una regla para pasar de cualquier tipo X a otro tipo T(X) , y una regla para pasar desde dos funciones f:X->T(Y) y g:Y->T(Z) (que le gustaría componer pero no puede) a una nueva función h:X->T(Z) . Lo que, sin embargo, no es la composición en sentido matemático estricto. Básicamente estamos "doblando" la composición de la función o redefinimos cómo se componen las funciones.

Además, requerimos la regla de composición de la mónada para satisfacer los axiomas matemáticos "obvios":

  • Asociatividad : componer f con g y luego con h (desde afuera) debe ser lo mismo que componer g con h y luego con f (desde adentro).
  • Propiedad unital : la composición de f con la función de identidad en cada lado debe producir f .

Nuevamente, en palabras simples, no podemos simplemente volvernos locos al redefinir nuestra composición de funciones como nos gusta:

  • Primero necesitamos la asociatividad para poder componer varias funciones en una fila, por ejemplo, f(g(h(k(x))) , y no preocuparnos por especificar los pares de funciones que componen el orden. Como la regla de la mónada solo prescribe cómo componer un par de funciones , sin ese axioma, necesitaríamos saber qué par se compone primero y así sucesivamente. (Tenga en cuenta que es diferente de la propiedad de conmutación que f compuesta con g era la misma que g compuesta con f , que no es necesaria ).
  • Y segundo, necesitamos la propiedad unital, que es simplemente decir que las identidades se componen de manera trivial de la forma en que las esperamos. Por lo tanto, podemos refactorizar las funciones de forma segura siempre que se puedan extraer esas identidades.

Así que de nuevo en resumen: una mónada es la regla de las funciones de extensión y composición de tipos que satisfacen los dos axiomas: asociatividad y propiedad unital.

En términos prácticos, desea que la mónada sea implementada para usted por el lenguaje, compilador o marco que se encargue de componer las funciones por usted. Por lo tanto, puede concentrarse en escribir la lógica de su función en lugar de preocuparse de cómo se implementa su ejecución.

Eso es esencialmente eso, en pocas palabras.

Siendo matemático profesional, prefiero evitar llamar a la "composición" de f y g . Porque matemáticamente, no lo es. Llamarlo incorrectamente "composición" supone que h es la verdadera composición matemática, y no lo es. Ni siquiera está determinado únicamente por f y g . En cambio, es el resultado de la nueva "regla de composición" de nuestra mónada de las funciones. ¡Que puede ser totalmente diferente de la composición matemática real incluso si esta última existe!

¡La mónada no es un functor ! Un functor F es una regla para pasar del tipo X al tipo F(X) y funciones (morfismo) entre los tipos X e Y a las funciones entre F(X) y F(Y) (envío de objetos a objetos y sus morfismos a morfismos en teoría de la categoría). En cambio, una mónada envía un par de funciones f y g a una nueva h .

Para que sea menos seco, déjeme intentar ilustrarlo con un ejemplo que estoy anotando con pequeñas secciones, para que pueda ir directamente al grano.

Excepción de tirar como ejemplos de mónada.

Supongamos que queremos componer dos funciones:

f: x -> 1 / x g: y -> 2 * y

Pero f(0) no está definido, por lo que se lanza una excepción e . Entonces, ¿cómo puedes definir el valor de composición g(f(0)) ? ¡Lanza una excepción otra vez, por supuesto! Tal vez la misma e . Tal vez una nueva excepción actualizada e1 .

¿Qué sucede precisamente aquí? Primero, necesitamos nuevos valores de excepción (diferentes o iguales). Puede llamarlos nothing o null o lo que sea, pero la esencia sigue siendo la misma: deberían ser valores nuevos, por ejemplo, no debería ser un number en nuestro ejemplo aquí. Prefiero no llamarlos null para evitar la confusión de cómo se puede implementar null en cualquier lenguaje específico. Igualmente, prefiero nothing evitar nothing porque a menudo se asocia con null , que, en principio, es lo que debería hacer null ; sin embargo, ese principio a menudo se dobla por razones prácticas.

¿Qué es exactamente la excepción?

Este es un asunto trivial para cualquier programador experimentado, pero me gustaría soltar algunas palabras para extinguir cualquier gusano de confusión:

La excepción es un objeto que encapsula información sobre cómo se produjo el resultado de la ejecución no válido.

Esto puede ir desde descartar cualquier detalle y devolver un solo valor global (como NaN o null ) o generar una larga lista de registros o lo que sucedió exactamente, enviarlo a una base de datos y replicarlo en toda la capa de almacenamiento de datos distribuidos;)

La diferencia importante entre estos dos ejemplos extremos de excepción es que en el primer caso no hay efectos secundarios . En el segundo hay. Lo que nos lleva a la pregunta (de mil dólares):

¿Se permiten excepciones en funciones puras?

Respuesta más breve : sí, pero solo cuando no producen efectos secundarios.

Respuesta más larga Para ser puro, la salida de su función debe ser determinada únicamente por su entrada. Así que enmendamos nuestra función f enviando 0 al nuevo valor abstracto e que llamamos excepción. Nos aseguramos de que el valor e no contenga información externa que no esté determinada únicamente por nuestra entrada, que es x . Así que aquí hay un ejemplo de excepción sin efecto secundario:

e = { type: error, message: ''I got error trying to divide 1 by 0'' }

Y aquí hay uno con efecto secundario:

e = { type: error, message: ''Our committee to decide what is 1/0 is currently away'' }

En realidad, solo tiene efectos secundarios si ese mensaje puede cambiar en el futuro. Pero si se garantiza que nunca cambiará, ese valor se vuelve únicamente predecible, por lo que no hay ningún efecto secundario.

Para hacerlo aún más tonto. Una función que devuelve 42 alguna vez es claramente pura. Pero si alguien loco decide hacer de 42 una variable que el valor pueda cambiar, la misma función deja de ser pura bajo las nuevas condiciones.

Tenga en cuenta que estoy usando la notación literal del objeto para simplificar y demostrar la esencia. Desafortunadamente, las cosas están desordenadas en lenguajes como JavaScript, donde el error no es un tipo que se comporte de la manera que queremos aquí con respecto a la composición de la función, mientras que los tipos reales como null o NaN no se comportan de esta manera, sino que pasan por algunos No siempre conversiones tipográficas intuitivas.

Extensión de tipo

Como queremos variar el mensaje dentro de nuestra excepción, realmente estamos declarando un nuevo tipo E para todo el objeto de excepción y luego Eso es lo que hace el maybe number , aparte de su nombre confuso, que debe ser de number de tipo o de the new exception type E , so it is really the union number | E number | Ede numbery E. En particular, depende de cómo queremos construir E, que no se sugiere ni se refleja en el nombre maybe number.

¿Qué es la composición funcional?

Es la operación matemática teniendo funciones f: X -> Yy g: Y -> Zy la construcción de su composición como la función h: X -> Zsatisfactoria h(x) = g(f(x)). El problema con esta definición ocurre cuando el resultado f(x)no está permitido como argumento de g.

En matemáticas esas funciones no pueden ser compuestas sin trabajo extra. La solución estrictamente matemático para nuestro ejemplo anterior de fy gconsiste en eliminar 0del conjunto de definición f. Con ese nuevo conjunto de definiciones (nuevo tipo más restrictivo de x), fse puede componer con g.

Sin embargo, no es muy práctico en la programación restringir el conjunto de definiciones de fese tipo. En su lugar, se pueden utilizar excepciones.

O como otro enfoque, los valores artificiales se crean como NaN, undefined, null, Infinityetc Por lo tanto se evalúa 1/0a Infinityy 1/-0a -Infinity. Y luego, vuelva a forzar el nuevo valor en su expresión en lugar de lanzar una excepción. Para obtener resultados que puede o no encontrar predecibles:

1/0 // => Infinity parseInt(Infinity) // => NaN NaN < 0 // => false false + 1 // => 1

Y estamos de vuelta a los números regulares listos para seguir adelante;)

JavaScript nos permite seguir ejecutando expresiones numéricas a cualquier costo sin generar errores como en el ejemplo anterior. Eso significa, también permite componer funciones. De eso se trata exactamente la mónada: es una regla componer funciones que satisfagan los axiomas definidos al principio de esta respuesta.

Pero, ¿es la regla de la función de composición, derivada de la implementación de JavaScript para tratar los errores numéricos, una mónada?

Para responder a esta pregunta, todo lo que necesita es verificar los axiomas (dejado como ejercicio como parte de la pregunta aquí).

¿Se puede usar la excepción de lanzamiento para construir una mónada?

De hecho, una mónada más útil sería, en cambio, la regla que prescribe que si se produce una fexcepción para algunos x, también lo hace su composición con cualquiera g. Además, haga la excepción Eúnica a nivel mundial con un solo valor posible ( objeto terminal en la teoría de categorías). Ahora los dos axiomas son verificables instantáneamente y obtenemos una mónada muy útil. Y el resultado es lo que es conocido como la mónada tal vez .


Usted tiene una presentación reciente " Monadologie - ayuda profesional en ansiedad de tipo " por Christopher League (12 de julio de 2010), que es bastante interesante en temas de continuación y mónada.
El video que va con esta presentación (slideshare) está realmente disponible en vimeo .
La parte de Monad comienza alrededor de 37 minutos, en este video de una hora, y comienza con la diapositiva 42 de su presentación de 58 diapositivas.

Se presenta como "el patrón de diseño principal para la programación funcional", pero el lenguaje utilizado en los ejemplos es Scala, que es a la vez OOP y funcional.
Puede leer más sobre Monad en Scala en la publicación del blog " Monads - Otra forma de resumir cálculos en Scala ", de Debasish Ghosh (27 de marzo de 2008).

Un constructor de tipo M es una mónada si admite estas operaciones:

# the return function def unit[A] (x: A): M[A] # called "bind" in Haskell def flatMap[A,B] (m: M[A]) (f: A => M[B]): M[B] # Other two can be written in term of the first two: def map[A,B] (m: M[A]) (f: A => B): M[B] = flatMap(m){ x => unit(f(x)) } def andThen[A,B] (ma: M[A]) (mb: M[B]): M[B] = flatMap(ma){ x => mb }

Así, por ejemplo (en Scala):

  • Option es una mónada.

def unit[A] (x: A): Option[A] = Some(x) def flatMap[A,B](m:Option[A])(f:A =>Option[B]): Option[B] = m match { case None => None case Some(x) => f(x) }

  • List es mónada

def unit[A] (x: A): List[A] = List(x) def flatMap[A,B](m:List[A])(f:A =>List[B]): List[B] = m match { case Nil => Nil case x::xs => f(x) ::: flatMap(xs)(f) }

Monad es un gran negocio en Scala debido a la práctica sintaxis creada para aprovechar las estructuras de Monad:

for comprensión en Scala :

for { i <- 1 to 4 j <- 1 to i k <- 1 to j } yield i*j*k

Es traducido por el compilador a:

(1 to 4).flatMap { i => (1 to i).flatMap { j => (1 to j).map { k => i*j*k }}}

La abstracción clave es el flatMap , que vincula el cálculo a través del encadenamiento.
Cada invocación de flatMap devuelve el mismo tipo de estructura de datos (pero de diferente valor), que sirve como entrada para el siguiente comando en cadena.

En el fragmento de código anterior, flatMap toma como entrada un cierre (SomeType) => List[AnotherType] y devuelve una List[AnotherType] . El punto importante a tener en cuenta es que todos los flatMaps toman el mismo tipo de cierre que la entrada y devuelven el mismo tipo que la salida.

Esto es lo que "une" el subproceso de cálculo: cada elemento de la secuencia en la comprensión debe cumplir con esta misma restricción de tipo.

Si realiza dos operaciones (que pueden fallar) y pasa el resultado a la tercera, como:

lookupVenue: String => Option[Venue] getLoggedInUser: SessionID => Option[User] reserveTable: (Venue, User) => Option[ConfNo]

pero sin tomar ventaja de Monad, obtienes un código OOP intrincado como:

val user = getLoggedInUser(session) val confirm = if(!user.isDefined) None else lookupVenue(name) match { case None => None case Some(venue) => val confno = reserveTable(venue, user.get) if(confno.isDefined) mailTo(confno.get, user.get) confno }

mientras que con Monad, puede trabajar con los tipos reales ( Venue , User ) como todas las operaciones, y mantener las cosas de verificación de opciones ocultas, todo debido a los planos de la sintaxis:

val confirm = for { venue <- lookupVenue(name) user <- getLoggedInUser(session) confno <- reserveTable(venue, user) } yield { mailTo(confno, user) confno }

La parte de rendimiento solo se ejecutará si las tres funciones tienen Some[X] ; Cualquier None sería devuelto directamente para confirm .

Asi que:

Las mónadas permiten el cálculo ordenado dentro de la Programación Funcional, que nos permite modelar la secuencia de acciones en una forma estructurada agradable, algo así como un DSL.

Y el mayor poder viene con la capacidad de componer mónadas que sirven para diferentes propósitos, en abstracciones extensibles dentro de una aplicación.

Esta secuenciación y secuencia de acciones por una mónada es realizada por el compilador de lenguaje que realiza la transformación a través de la magia de los cierres.

Por cierto, Monad no es solo el modelo de cálculo utilizado en FP: consulte esta publicación del blog .

La teoría de la categoría propone muchos modelos de computación. Entre ellos

  • El modelo de flecha de los cálculos.
  • El modelo de cálculo de la mónada.
  • El modelo de cálculo aplicativo.

Yo diría que la analogía OO más cercana a las mónadas es el " patrón de comando ".

En el patrón de comando, envuelve una declaración o expresión ordinaria en un objeto de comando . El objeto de comando expone un método de ejecución que ejecuta la instrucción envuelta. Así que la declaración se convierte en objetos de primera clase que pueden transmitirse y ejecutarse a voluntad. Los comandos se pueden componer para que pueda crear un objeto de programa encadenando y anidando objetos de comando.

Los comandos son ejecutados por un objeto separado, el invocador . La ventaja de usar el patrón de comando (en lugar de simplemente ejecutar una serie de declaraciones ordinarias) es que diferentes invocadores pueden aplicar una lógica diferente a cómo deben ejecutarse los comandos.

El patrón de comando podría utilizarse para agregar (o eliminar) características de idioma que no son compatibles con el idioma del host. Por ejemplo, en un lenguaje hipotético de OO sin excepciones, puede agregar una semántica de excepción exponiendo los métodos "probar" y "lanzar" a los comandos. Cuando se lanza un comando, el invocador retrocede a través de la lista (o árbol) de comandos hasta la última llamada "intentar". A la inversa, puede eliminar la excepción semántica de un idioma (si cree que las excepciones son malas ) capturando todas las excepciones lanzadas por cada comando individual y convirtiéndolas en códigos de error que luego se pasan al siguiente comando.

Incluso se pueden implementar semánticas de ejecución más sofisticadas, como transacciones, ejecuciones no deterministas o continuaciones, en un lenguaje que no lo admite de forma nativa. Es un patrón bastante poderoso si lo piensas.

Ahora, en realidad, los patrones de comando no se utilizan como una característica de lenguaje general como esta. La sobrecarga de convertir cada declaración en una clase separada conduciría a una cantidad insoportable de código repetitivo. Pero en principio puede usarse para resolver los mismos problemas que las mónadas para resolver en fp.


Una mónada es una serie de funciones.

(Pst: un conjunto de funciones es solo un cálculo).

En realidad, en lugar de una verdadera matriz (una función en una matriz de celdas) tiene esas funciones encadenadas por otra función >> =. El >> = permite adaptar los resultados de la función i a la función i + 1, realizar cálculos entre ellos o, incluso, no llamar a la función i + 1.

Los tipos utilizados aquí son "tipos con contexto". Esto es, un valor con una "etiqueta". Las funciones encadenadas deben tomar un "valor desnudo" y devolver un resultado etiquetado. Uno de los deberes de >> = es extraer un valor desnudo de su contexto. También está la función "return", que toma un valor desnudo y la coloca con una etiqueta.

Un ejemplo con Maybe . Vamos a usarlo para almacenar un entero simple en el que hacer cálculos.

-- a * b multiply :: Int -> Int -> Maybe Int multiply a b = return (a*b) -- divideBy 5 100 = 100 / 5 divideBy :: Int -> Int -> Maybe Int divideBy 0 _ = Nothing -- dividing by 0 gives NOTHING divideBy denom num = return (quot num denom) -- quotient of num / denom -- tagged value val1 = Just 160 -- array of functions feeded with val1 array1 = val1 >>= divideBy 2 >>= multiply 3 >>= divideBy 4 >>= multiply 3 -- array of funcionts created with the do notation -- equals array1 but for the feeded val1 array2 :: Int -> Maybe Int array2 n = do v <- divideBy 2 n v <- multiply 3 v v <- divideBy 4 v v <- multiply 3 v return v -- array of functions, -- the first >>= performs 160 / 0, returning Nothing -- the second >>= has to perform Nothing >>= multiply 3 .... -- and simply returns Nothing without calling multiply 3 .... array3 = val1 >>= divideBy 0 >>= multiply 3 >>= divideBy 4 >>= multiply 3 main = do print array1 print (array2 160) print array3

Solo para mostrar que las mónadas son una serie de funciones con operaciones de ayuda, considere el equivalente al ejemplo anterior, simplemente usando una serie real de funciones

type MyMonad = [Int -> Maybe Int] -- my monad as a real array of functions myArray1 = [divideBy 2, multiply 3, divideBy 4, multiply 3] -- function for the machinery of executing each function i with the result provided by function i-1 runMyMonad :: Maybe Int -> MyMonad -> Maybe Int runMyMonad val [] = val runMyMonad Nothing _ = Nothing runMyMonad (Just val) (f:fs) = runMyMonad (f val) fs

Y sería usado así:

print (runMyMonad (Just 160) myArray1)


En términos de OO, una mónada es un contenedor fluido.

El requisito mínimo es una definición de class <A> Somethingque admita un constructor Something(A a)y al menos un método.Something<B> flatMap(Function<A, Something<B>>)

Podría decirse que también cuenta si su clase de mónada tiene algún método con firma Something<B> work()que conserve las reglas de la clase: el compilador hornea en flatMap en tiempo de compilación.

¿Por qué es útil una mónada? Porque es un contenedor que permite operaciones encadenadas que preservan la semántica. Por ejemplo, Optional<?>conserva la semántica de isPresent para Optional<String>, Optional<Integer>, Optional<MyClass>, etc.

Como un ejemplo aproximado,

Something<Integer> i = new Something("a") .flatMap(doOneThing) .flatMap(doAnother) .flatMap(toInt)

Tenga en cuenta que comenzamos con una cadena y terminamos con un entero. Muy genial.

En OO, puede tomar un poco de saludo, pero cualquier método en Something que devuelve otra subclase de Something cumple el criterio de una función de contenedor que devuelve un contenedor del tipo original.

Así es como se conserva la semántica; es decir, el significado y las operaciones del contenedor no cambian, simplemente envuelven y realzan el objeto dentro del contenedor.


Las mónadas en uso típico son el equivalente funcional de los mecanismos de manejo de excepciones de programación de procedimientos.

En los lenguajes de procedimiento modernos, coloca un controlador de excepciones alrededor de una secuencia de declaraciones, cualquiera de las cuales puede generar una excepción. Si alguna de las declaraciones produce una excepción, la ejecución normal de la secuencia de instrucciones se detiene y transfiere a un controlador de excepciones.

Sin embargo, los lenguajes de programación funcionales evitan filosóficamente las funciones de manejo de excepciones debido a la naturaleza "goto" de ellos. La perspectiva de la programación funcional es que las funciones no deben tener "efectos secundarios" como excepciones que interrumpen el flujo del programa.

En realidad, los efectos secundarios no se pueden descartar en el mundo real debido principalmente a la E / S. Las mónadas en la programación funcional se utilizan para manejar esto tomando un conjunto de llamadas de función encadenadas (cualquiera de las cuales puede producir un resultado inesperado) y convirtiendo cualquier resultado inesperado en datos encapsulados que aún pueden fluir con seguridad a través de las llamadas de función restantes.

El flujo de control se conserva, pero el evento inesperado se encapsula y maneja de manera segura.


Trataré de hacer la definición más corta que pueda administrar usando términos OOP:

Una clase genérica CMonadic<T>es una mónada si define al menos los siguientes métodos:

class CMonadic<T> { static CMonadic<T> create(T t); // a.k.a., "return" in Haskell public CMonadic<U> flatMap<U>(Func<T, CMonadic<U>> f); // a.k.a. "bind" in Haskell }

y si las siguientes leyes se aplican a todos los tipos T y sus posibles valores t

identidad izquierda

CMonadic<T>.create(t).flatMap(f) == f(t)

identidad correcta

instance.flatMap(CMonadic<T>.create) == instance

asociatividad

instance.flatMap(f).flatMap(g) == instance.flatMap(t => f(t).flatMap(g))

Ejemplos :

Una mónada de la Lista puede tener:

List<int>.create(1) --> [1]

Y flatMap en la lista [1,2,3] podría funcionar así:

intList.flatMap(x => List<int>.makeFromTwoItems(x, x*10)) --> [1,10,2,20,3,30]

Iterables y Observables también pueden hacerse monádicos, así como Promesas y Tareas.

Comentario :

Las mónadas no son tan complicadas. La flatMapfunción es muy parecida a la más común map. Recibe un argumento de función (también conocido como delegado), que puede llamar (inmediatamente o más tarde, cero o más veces) con un valor proveniente de la clase genérica. Se espera que la función pasada también ajuste su valor de retorno en el mismo tipo de clase genérica. Para ayudar con eso, proporciona createun constructor que puede crear una instancia de esa clase genérica a partir de un valor. El resultado de retorno de flatMap también es una clase genérica del mismo tipo, que a menudo incluye los mismos valores que estaban contenidos en los resultados de retorno de una o más aplicaciones de flatMap a los valores contenidos previamente. Esto te permite encadenar flatMap todo lo que quieras:

intList.flatMap(x => List<int>.makeFromTwo(x, x*10)) .flatMap(x => x % 3 == 0 ? List<string>.create("x = " + x.toString()) : List<string>.empty())

Da la casualidad de que este tipo de clase genérica es útil como modelo base para una gran cantidad de cosas. Esto (junto con la jerga de la teoría de categorías) es la razón por la que las mónadas parecen tan difíciles de entender o explicar. Son algo muy abstracto y solo se vuelven obviamente útiles una vez que están especializados.

Por ejemplo, puede modelar excepciones usando contenedores monádicos. Cada contenedor contendrá el resultado de la operación o el error que haya ocurrido. La siguiente función (delegado) en la cadena de devoluciones de llamada de flatMap solo se llamará si la anterior incluyó un valor en el contenedor. De lo contrario, si se empaquetó un error, el error continuará propagándose a través de los contenedores encadenados hasta que se encuentre un contenedor que tenga una función de control de errores adjunta a través de un método llamado .orElse()(tal método sería una extensión permitida)

Notas : Los lenguajes funcionales le permiten escribir funciones que pueden operar en cualquier clase de clase genérica monádica. Para que esto funcione, uno tendría que escribir una interfaz genérica para las mónadas. No sé si es posible escribir una interfaz de este tipo en C #, pero por lo que sé, no lo es:

interface IMonad<T> { static IMonad<T> create(T t); // not allowed public IMonad<U> flatMap<U>(Func<T, IMonad<U>> f); // not specific enough, // because the function must return the same kind of monad, not just any monad }


Una mónada es un tipo de datos que encapsula un valor y al que, esencialmente, se pueden aplicar dos operaciones:

  • return x crea un valor del tipo de mónada que encapsula x
  • m >>= f(léalo como "el operador de enlace") aplica la función fal valor en la mónadam

Eso es lo que es una mónada. Hay algunos tecnicismos más , pero básicamente esas dos operaciones definen una mónada. La verdadera pregunta es: "¿Qué una mónada hace ?", Y que depende de la mónada - listas son mónadas, Maybes son mónadas, las operaciones de IO son mónadas. Todo lo que significa que cuando decimos que esas cosas son mónadas es que tienen la interfaz de mónada returny >>=.


De wikipedia :

En la programación funcional, una mónada es un tipo de tipo de datos abstractos que se utiliza para representar cálculos (en lugar de datos en el modelo de dominio). Las mónadas permiten al programador encadenar acciones para construir un conducto, en el que cada acción está decorada con reglas de procesamiento adicionales proporcionadas por la mónada. Los programas escritos en estilo funcional pueden hacer uso de mónadas para estructurar procedimientos que incluyen operaciones secuenciadas, wikipedia [2] o para definir flujos de control arbitrarios (como manejo de concurrencia, continuaciones o excepciones).

Formalmente, una mónada se construye definiendo dos operaciones (vinculación y retorno) y un constructor de tipo M que debe cumplir varias propiedades para permitir la composición correcta de las funciones monádicas (es decir, las funciones que usan valores de la mónada como sus argumentos). La operación de retorno toma un valor de un tipo simple y lo coloca en un contenedor monádico de tipo M. La operación de enlace realiza el proceso inverso, extrayendo el valor original del contenedor y pasándolo a la siguiente función asociada en la canalización.

Un programador compondrá funciones monádicas para definir un canal de procesamiento de datos. La mónada actúa como marco, ya que es un comportamiento reutilizable que decide el orden en que se llaman las funciones monádicas específicas en la tubería, y administra todo el trabajo encubierto requerido por el cálculo. Los operadores de enlace y retorno intercalados en la tubería se ejecutarán después de que cada función monádica devuelva el control, y se ocupará de los aspectos particulares manejados por la mónada.

Creo que lo explica muy bien.


Desde un punto de vista práctico (que resume lo que se ha dicho en muchas respuestas anteriores y artículos relacionados), me parece que uno de los "propósitos" (o utilidad) fundamentales de la mónada es aprovechar las dependencias implícitas en las invocaciones de métodos recursivos. composición de la función aka (es decir, cuando f1 llama a f2, f3, f3 debe evaluarse antes de f2 antes de f1) para representar la composición secuencial de manera natural, especialmente en el contexto de un modelo de evaluación perezoso (es decir, la composición secuencial como una secuencia simple , por ejemplo, "f3 (); f2 (); f1 ();" en C: el truco es especialmente obvio si piensa en un caso donde f3, f2 y f1 no devuelven nada [su encadenamiento como f1 (f2 (f3)) es artificial, puramente destinado a crear secuencia]).

Esto es especialmente relevante cuando se trata de efectos secundarios, es decir, cuando se altera algún estado (si f1, f2, f3 no tuvieran efectos secundarios, no importaría en qué orden se evalúan; lo que es una gran propiedad de la pureza lenguajes funcionales, para poder paralelizar esos cálculos por ejemplo). Las funciones más puras, mejor.

Creo que desde ese punto de vista estrecho, las mónadas pueden verse como azúcar sintáctica para los idiomas que favorecen la evaluación perezosa (que evalúa las cosas solo cuando es absolutamente necesario, siguiendo un orden que no se basa en la presentación del código) y que no tienen Otros medios para representar la composición secuencial. El resultado neto es que las secciones de código que son "impuras" (es decir, que tienen efectos secundarios) pueden presentarse de manera natural, de manera imperativa, pero están claramente separadas de las funciones puras (sin efectos secundarios), que pueden ser evaluado perezosamente.

Sin embargo, este es solo un aspecto, como se advierte here .



Si una mónada tiene una interpretación "natural" en OO depende de la mónada. En un lenguaje como Java, puede traducir la mónada tal vez al lenguaje para verificar los punteros nulos, de modo que los cálculos que fallan (es decir, producen Nothing en Haskell) emiten punteros nulos como resultado. Puede traducir la mónada de estado al lenguaje generado creando una variable mutable y métodos para cambiar su estado.

Una mónada es un monoide en la categoría de endofunctores.

La información que reúne la oración es muy profunda. Y trabajas en una mónada con cualquier lenguaje imperativo. Una mónada es un lenguaje específico de dominio "secuenciado". Satisface ciertas propiedades interesantes, que en conjunto hacen de la mónada un modelo matemático de "programación imperativa". Haskell facilita la definición de lenguajes imperativos pequeños (o grandes), que se pueden combinar de varias maneras.

Como programador de OO, utiliza la jerarquía de clases de su lenguaje para organizar los tipos de funciones o procedimientos que se pueden llamar en un contexto, lo que usted llama un objeto. Una mónada también es una abstracción de esta idea, en la medida en que diferentes mónadas se pueden combinar de manera arbitraria, "importando" de manera efectiva todos los métodos de la sub-mónada al alcance.

Arquitectónicamente, uno usa firmas de tipo para expresar explícitamente qué contextos pueden usarse para calcular un valor.

Se pueden usar transformadores de mónada para este propósito, y hay una colección de alta calidad de todas las mónadas "estándar":

  • Listas (cálculos no deterministas, al tratar una lista como un dominio)
  • Quizás (cálculos que pueden fallar, pero para los cuales la información no es importante)
  • Error (cálculos que pueden fallar y requieren manejo de excepciones)
  • Lector (cálculos que pueden representarse por composiciones de funciones sencillas de Haskell)
  • Escritor (cálculos con "rendering" / "logging" secuencial (a cadenas, html, etc.)
  • Cont (continuaciones)
  • IO (cálculos que dependen del sistema informático subyacente)
  • Estado (cálculos cuyo contexto contiene un valor modificable)

Con los correspondientes transformadores de mónada y clases de tipo. Las clases de tipos permiten un enfoque complementario para combinar las mónadas unificando sus interfaces, de modo que las mónadas concretas puedan implementar una interfaz estándar para el "tipo" de la mónada. Por ejemplo, el módulo Control.Monad.State contiene una clase MonadState sm, y (Estado s) es una instancia del formulario

instance MonadState s (State s) where put = ... get = ...

La larga historia es que una mónada es un funtor que adjunta "contexto" a un valor, que tiene una forma de inyectar un valor en la mónada, y que tiene una forma de evaluar valores con respecto al contexto que se le atribuye, al menos de forma restringida.

Asi que:

return :: a -> m a

es una función que inyecta un valor de tipo a en una "acción" de mónada de tipo m a.

(>>=) :: m a -> (a -> m b) -> m b

es una función que realiza una acción de mónada, evalúa su resultado y aplica una función al resultado. Lo bueno de (>> =) es que el resultado está en la misma mónada. En otras palabras, en m >> = f, (>> =) extrae el resultado de m, y lo une a f, de modo que el resultado esté en la mónada. (Alternativamente, podemos decir que (>> =) jala f en my lo aplica al resultado.) Como consecuencia, si tenemos f :: a -> mb, y g :: b -> mc, podemos Acciones de "secuencia":

m >>= f >>= g

O, usando "hacer notación"

do x <- m y <- f x g y

El tipo para (>>) podría ser iluminador. Es

(>>) :: m a -> m b -> m b

Corresponde al operador (;) en lenguajes de procedimiento como C. Permite hacer notaciones como:

m = do x <- someQuery someAction x theNextAction andSoOn

En lógica matemática y filosófica, tenemos marcos y modelos, que están "naturalmente" modelados con monadismo. Una interpretación es una función que examina el dominio del modelo y calcula el valor de verdad (o generalizaciones) de una proposición (o fórmula, bajo generalizaciones). En una lógica modal por necesidad, podríamos decir que una proposición es necesaria si es verdadera en "todos los mundos posibles", si es cierta con respecto a todos los dominios admisibles. Esto significa que un modelo en un idioma para una proposición puede ser reificado como un modelo cuyo dominio consiste en la colección de modelos distintos (uno correspondiente a cada mundo posible). Cada mónada tiene un método llamado "unirse" que aplana capas, lo que implica que cada acción de mónada cuyo resultado es una acción de mónada puede incrustarse en la mónada.

join :: m (m a) -> m a

Más importante aún, significa que la mónada se cierra bajo la operación de "apilamiento de capas". Así es como funcionan los transformadores de mónada: combinan mónadas al proporcionar métodos "de unión" para tipos como

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }

para que podamos transformar una acción en (MaybeT m) en una acción en m, colapsando efectivamente las capas. En este caso, runMaybeT :: MaybeT ma -> m (Quizás a) es nuestro método de unión. (MaybeT m) es una mónada, y MaybeT :: m (Maybe a) -> MaybeT ma es efectivamente un constructor para un nuevo tipo de acción de mónada en m.

Una mónada libre para un functor es la mónada generada al apilar f, con la implicación de que cada secuencia de constructores para f es un elemento de la mónada libre (o, más exactamente, algo con la misma forma que el árbol de secuencias de constructores para f. F). Las mónadas libres son una técnica útil para construir mónadas flexibles con una cantidad mínima de placa de caldera. En un programa de Haskell, podría usar mónadas gratuitas para definir mónadas simples para "programación de alto nivel del sistema" para ayudar a mantener la seguridad de tipos (solo estoy usando tipos y sus declaraciones. Las implementaciones son sencillas con el uso de combinadores):

data RandomF r a = GetRandom (r -> a) deriving Functor type Random r a = Free (RandomF r) a type RandomT m a = Random (m a) (m a) -- model randomness in a monad by computing random monad elements. getRandom :: Random r r runRandomIO :: Random r a -> IO a (use some kind of IO-based backend to run) runRandomIO'' :: Random r a -> IO a (use some other kind of IO-based backend) runRandomList :: Random r a -> [a] (some kind of list-based backend (for pseudo-randoms))

El monadismo es la arquitectura subyacente para lo que podríamos llamar el patrón de "intérprete" o "comando", abstraído a su forma más clara, ya que cada cálculo monádico debe "ejecutarse", al menos de forma trivial. (El sistema de tiempo de ejecución ejecuta la mónada IO para nosotros, y es el punto de entrada a cualquier programa Haskell. IO "controla" el resto de los cálculos, ejecutando las acciones IO en orden).

El tipo de unión también es donde obtenemos la afirmación de que una mónada es un monoide en la categoría de endofunctores. La unión es típicamente más importante para propósitos teóricos, en virtud de su tipo. Pero entender el tipo significa entender las mónadas. Los tipos de unión y unión de un tipo de transformador son efectivamente composiciones de endofunctores, en el sentido de la composición de la función. Para ponerlo en un pseudo-lenguaje similar a Haskell,

Foo :: m (ma) <-> (m. M) a


Vea mi answer a "¿Qué es una mónada?"

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

No asume ningún conocimiento de la programación funcional y utiliza pseudocódigo con function(argument) := expressionsintaxis con las expresiones más simples posibles.

Este programa en C ++ es una implementación de la mónada pseudocódigo. (Para referencia: Mes el constructor de tipo, feedes la operación de "vinculación" y wrapes la operación de "retorno".)

#include <iostream> #include <string> template <class A> class M { public: A val; std::string messages; }; template <class A, class B> M<B> feed(M<B> (*f)(A), M<A> x) { M<B> m = f(x.val); m.messages = x.messages + m.messages; return m; } template <class A> M<A> wrap(A x) { M<A> m; m.val = x; m.messages = ""; return m; } class T {}; class U {}; class V {}; M<U> g(V x) { M<U> m; m.messages = "called g./n"; return m; } M<T> f(U x) { M<T> m; m.messages = "called f./n"; return m; } int main() { V x; M<T> m = feed(f, feed(g, wrap(x))); std::cout << m.messages; }