En términos que un programador de 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 en los que se usa?
EDITAR:
Para aclarar el tipo de comprensión que estaba buscando, digamos que estaba convirtiendo una aplicación FP que tenía mónadas en una aplicación OOP. ¿Qué harías para transferir las responsabilidades de las mónadas a la aplicación OOP?
Respuestas:
ACTUALIZACIÓN: Esta pregunta fue el tema de una serie de blogs inmensamente larga, que puedes leer en Monads . ¡Gracias por la gran pregunta!
Una mónada es un "amplificador" de tipos que obedece ciertas reglas y que tiene ciertas operaciones proporcionadas .
Primero, ¿qué es un "amplificador de tipos"? Con eso me refiero a un sistema que le permite 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, decirint
y agregar una nueva capacidad a ese tipo, a saber, que ahora puede ser nulo cuando no podía antes.Como segundo ejemplo, considérelo
IEnumerable<T>
. Es un amplificador de tipos. Le permite tomar un tipo, digamos,string
y agregar una nueva capacidad a ese tipo, a saber, que ahora puede hacer una secuencia de cadenas de cualquier número de cadenas individuales.¿Cuáles son las "ciertas reglas"? Brevemente, que hay una manera sensata para que las funciones en el tipo subyacente trabajen en el tipo amplificado de manera que sigan las reglas normales de composición funcional. Por ejemplo, si tiene una función en enteros, diga
entonces la función correspondiente en
Nullable<int>
puede hacer que todos los operadores y llamadas allí trabajen juntos "de la misma manera" que antes.(Eso es increíblemente vago e impreciso; solicitó una explicación que no suponía nada sobre el conocimiento de la composición funcional).
¿Qué son las "operaciones"?
Hay una operación de "unidad" (confusamente a veces llamada 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.
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. La vinculación es la operación clave que define la semántica de la mónada. Nos permite transformar operaciones en el tipo no amplificado en operaciones en el tipo amplificado, que obedece las reglas de composición funcional mencionadas anteriormente.
A menudo hay una manera de recuperar el tipo no amplificado del tipo amplificado. Estrictamente hablando, esta operación no requiere tener una mónada. (Aunque es necesario si desea tener una comonad . No los consideraremos más en este artículo).
Nuevamente, tome
Nullable<T>
como ejemplo. Puedes convertir unaint
en unaNullable<int>
con el constructor. El compilador de C # se encarga de la mayoría de los "levantamientos" anulables para usted, pero si no fuera así, la transformación de levantamiento es sencilla: una operación, por ejemplo,se transforma en
Y convertir una
Nullable<int>
copia de seguridad en unaint
se hace con laValue
propiedad.La transformación de la función es el bit clave. Observe cómo la semántica real de la operación anulable (que una operación en un
null
propaganull
) se captura en la transformación. Podemos generalizar esto.Supongamos que tiene una función de
int
aint
, como nuestro originalM
. Puede convertirlo fácilmente en una función que tome unint
y devuelva unNullable<int>
porque simplemente puede ejecutar el resultado a través del constructor anulable. Ahora suponga que tiene este método de orden superior:¿Ves lo que puedes hacer con eso? Cualquier método que tome un
int
y devuelva unint
, o tome unint
y devuelva unNullable<int>
puede ahora tener la semántica anulable aplicada .Además: suponga que tiene dos métodos
y quieres componerlos:
Es decir,
Z
es la composición deX
yY
. Pero no puede hacerlo porqueX
toma unint
yY
devuelve unNullable<int>
. Pero como tiene la operación "vincular", puede hacer que esto funcione:La operación de enlace en una mónada es lo que hace que la composición de funciones en tipos amplificados funcione. Las "reglas" que mencioné anteriormente son que la mónada preserva las reglas de la composición de la función normal; que componer con funciones de identidad da como resultado la función original, que la 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 nuevamente en un valor". Esas operaciones son:
La regla de mónada anulable era "combinar dos funciones que produzcan valores anulables juntos, verifique si el interno da como resultado nulo; si lo hace, produzca nulo, si no es así, llame al externo con el resultado". Esa es la semántica deseada de anulable.
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. Suponga que tiene una secuencia de entradas y un método que toma entradas 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:
Así que ahora podemos decir "amplifique este grupo de enteros individuales en una secuencia de números enteros. Transforme este número entero particular en un grupo de cadenas, amplificado en una secuencia de cadenas. Ahora junte ambas operaciones: amplifique este grupo de números enteros en la concatenación de todas las secuencias de cuerdas ". Las mónadas te permiten componer tus amplificaciones.
Es como preguntar "¿qué problemas resuelve el patrón singleton?", Pero lo intentaré.
Las mónadas se suelen utilizar para resolver problemas como:
C # utiliza 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á completamente construido de mónadas; El
SelectMany
método es lo que hace el trabajo semántico de composición de operaciones. (Erik Meijer es aficionado a señalar que todas las funciones de LINQ podrían implementarse realmenteSelectMany
; todo lo demás es solo una conveniencia).La mayoría de los lenguajes OOP no tienen un sistema de tipos lo suficientemente rico como para representar el patrón de mónada directamente; necesita un sistema de tipos que admita tipos superiores a los genéricos. Entonces 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 en valores amplificados.
Un buen lugar para comenzar es cómo implementamos LINQ en C #. Estudiar el
SelectMany
método; es la clave para entender cómo funciona la secuencia mónada en C #. ¡Es un método muy simple, pero muy poderoso!Sugerencia, lectura adicional:
fuente
¿Por qué necesitamos mónadas?
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 debe ejecutar primero ? ¿Cómo podemos formar una secuencia ordenada de funciones (es decir, un programa ) usando no más que funciones?
Solución: componer funciones . Si quieres primero
g
y luegof
, solo escribef(g(x,y))
. Bien pero ...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 en real), permitamosg : Real,Real -> Real | Nothing
(función de dos reales en (real o nada)).Pero las funciones deberían (para ser más simples) devolver solo una cosa .
Solución: creemos un nuevo tipo de datos para devolver, un " tipo de boxeo " que encierra quizás un real o simplemente nada. Por lo tanto, podemos tener
g : Real,Real -> Maybe Real
. Bien pero ...¿Qué pasa ahora con
f(g(x,y))
?f
no está listo para consumir aMaybe Real
. Y no queremos cambiar todas las funciones con las que podríamos conectarnosg
para consumir aMaybe Real
.Solución: tengamos una función especial para "conectar" / "componer" / "vincular" funciones . De esa manera, podemos, detrás de escena, adaptar la salida de una función para alimentar la siguiente.
En nuestro caso:
g >>= f
(conectar / componerg
af
). Queremos>>=
obtenerg
la salida, inspeccionarla y, en caso de que seaNothing
, no llamef
y regreseNothing
; o por el contrario, extraiga el cuadroReal
y alimentef
con él. (Este algoritmo es solo la implementación de>>=
para elMaybe
tipo).Surgen muchos otros problemas que pueden resolverse usando este mismo patrón: 1. Use una "caja" para codificar / almacenar diferentes significados / valores, y tener funciones como
g
esa devolver esos "valores en caja". 2. Tenga compositores / enlazadoresg >>= f
para ayudar a conectarg
la salida def
la entrada, de modo que no tengamos que cambiarf
en absoluto.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 diferentes resultados para la misma entrada. Por lo tanto, marquemos esas funciones, haciendo que devuelvan un valor etiquetado / encuadrado:
IO
mónada.Felicidad total !!!!
fuente
State
yIO
sin darles ninguno de ellos ni el significado exacto de "mónada".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. Por lo tanto, las declaraciones se convierten en objetos de primera clase que se pueden pasar y ejecutar 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 . El beneficio 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 usarse para agregar (o eliminar) funciones de idioma que no es compatible con el idioma del host. Por ejemplo, en un lenguaje OO hipotético sin excepciones, puede agregar una semántica de excepción al exponer los métodos "probar" y "lanzar" a los comandos. Cuando un comando llama lanzar, el invocador retrocede a través de la lista (o árbol) de comandos hasta la última llamada de "intento". Por el contrario, 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 de esta manera 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 usan 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, se puede usar para resolver los mismos problemas que las mónadas se usan para resolver en fp.
fuente
En términos de programación OO, una mónada es una interfaz (o más probablemente un mixin), parametrizada por un tipo, con dos métodos,
return
ybind
que describe:El problema que resuelve es el mismo tipo de problema que esperaría de cualquier interfaz, a saber, "Tengo un montón de clases diferentes que hacen cosas diferentes, pero parecen hacer esas cosas diferentes de una manera que tiene una similitud subyacente". ¿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 'el Objeto' en sí misma? "
Más específicamente, la
Monad
"interfaz" es similarIEnumerator
oIIterator
en que toma un tipo que en sí toma un tipo. SinMonad
embargo, el "punto" principal 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.fuente
return
en realidad no sería un método en la mónada, porque no toma una instancia de mónada como argumento. (es decir, no hay esto / yo mismo)Tiene una presentación reciente " Monadologie - ayuda profesional sobre la ansiedad de tipo " por Christopher League (12 de julio de 2010), que es bastante interesante sobre temas de continuación y mónada.
El video que acompaña 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 líder para la programación funcional", pero el lenguaje utilizado en los ejemplos es Scala, que es tanto OOP como funcional.
Puede leer más sobre Mónada en Scala en la publicación del blog " Mónadas: otra forma de hacer cálculos abstractos en Scala ", de Debasish Ghosh (27 de marzo de 2008).
Entonces, por ejemplo (en Scala):
Option
es una mónadaList
es mónadaMonad es un gran problema en Scala debido a la conveniente sintaxis creada para aprovechar las estructuras de Monad:
for
comprensión en Scala :es traducido por el compilador a:
La abstracción clave es el
flatMap
, que une 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 anterior, flatMap toma como entrada un cierre
(SomeType) => List[AnotherType]
y devuelve aList[AnotherType]
. El punto importante a tener en cuenta es que todos los flatMaps toman el mismo tipo de cierre como entrada y devuelven el mismo tipo como salida.Esto es lo que "une" el hilo de cálculo: cada elemento de la secuencia en la comprensión tiene que cumplir con esta misma restricción de tipo.
Si realiza dos operaciones (que pueden fallar) y pasa el resultado a la tercera, como:
pero sin aprovechar Monad, obtienes un código OOP complicado como:
mientras que con Monad, puede trabajar con los tipos reales (
Venue
,User
) como todas las operaciones funcionan, y mantener ocultas las cosas de verificación de Opciones, todo debido a los planos de la sintaxis for:La parte de rendimiento solo se ejecutará si las tres funciones tienen
Some[X]
; cualquieraNone
sería devuelto directamente aconfirm
.Entonces:
Por cierto, Monad no es solo un modelo de cálculo utilizado en FP:
fuente
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 ejemplos.
Aquí hay una definición concisa y precisa ligeramente reformulada:
Entonces, en palabras simples, una mónada es una regla para pasar de cualquier tipo
X
a otro tipoT(X)
, y una regla para pasar de dos funcionesf:X->T(Y)
yg:Y->T(Z)
(que le gustaría componer pero no puede) a una nueva funciónh:X->T(Z)
. 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 redefiniendo cómo se componen las funciones.Además, requerimos que la regla de composición de la mónada satisfaga los axiomas matemáticos "obvios":
f
cong
y luego conh
(desde afuera) debería ser lo mismo que componerg
conh
y luego conf
(desde adentro).f
con la función de identidad en cualquier lado debería rendirf
.Nuevamente, en palabras simples, no podemos volvernos locos redefiniendo nuestra composición de funciones como nos gusta:
f(g(h(k(x)))
, por ejemplo , y no tener que preocuparnos de especificar el orden que compone los pares de funciones. 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 conmutatividad quef
compuso cong
eran las mismas queg
compuestas conf
, lo cual no es obligatorio).En resumen, una mónada es la regla de extensión de tipo y funciones de composición que satisfacen los dos axiomas: asociatividad y propiedad unital.
En términos prácticos, desea que la mónada sea implementada por el lenguaje, el compilador o el marco que se encargaría 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
h
la "composición" def
yg
. Porque matemáticamente, no lo es. Llamarlo "composición" supone incorrectamente queh
es la verdadera composición matemática, que no lo es. Ni siquiera está determinado únicamente porf
yg
. En cambio, es el resultado de la nueva "regla de componer" las funciones de nuestra mónada. ¡Lo que puede ser totalmente diferente de la composición matemática real, incluso si existe esta última!Para hacerlo menos seco, permítame intentar ilustrarlo con un ejemplo que estoy anotando con secciones pequeñas, para que pueda saltar directamente al punto.
Lanzamiento de excepciones como ejemplos de Monad
Supongamos que queremos componer dos funciones:
Pero
f(0)
no está definido, por lo quee
se lanza una excepción . Entonces, ¿cómo puedes definir el valor compositivog(f(0))
? ¡Lanza una excepción de nuevo, por supuesto! Quizás lo mismoe
. Quizás una nueva excepción actualizadae1
.¿Qué sucede precisamente aquí? Primero, necesitamos nuevos valores de excepción (diferentes o iguales). Se les puede llamar
nothing
onull
o lo que sea, pero la esencia sigue siendo la misma - que deben ser valores nuevos, por ejemplo, que no debe ser unanumber
en nuestro ejemplo aquí. Prefiero no llamarlosnull
para evitar confusiones sobre cómonull
se puede implementar en un idioma específico. Igualmente, prefiero evitarlonothing
porque a menudo está asociado con lonull
que, en principio, es lo quenull
debería hacer, sin embargo, ese principio a menudo se dobla por cualquier razón práctica.¿Qué es la excepción exactamente?
Este es un asunto trivial para cualquier programador experimentado, pero me gustaría dejar caer algunas palabras solo para extinguir cualquier gusano de confusión:
La excepción es un objeto que encapsula información sobre cómo se produjo el resultado no válido de la ejecución.
Esto puede variar desde descartar cualquier detalle y devolver un único valor global (como
NaN
onull
) o generar una larga lista de registro 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 corta : Sí, pero solo cuando no conducen a efectos secundarios.
Respuesta larga Para ser puro, la salida de su función debe estar determinada únicamente por su entrada. Entonces modificamos nuestra función
f
enviando0
al nuevo valor abstractoe
que llamamos excepción. Nos aseguramos de que el valore
no contenga información externa que no esté determinada únicamente por nuestra entrada, que esx
. Así que aquí hay un ejemplo de excepción sin efectos secundarios:Y aquí hay uno con efectos secundarios:
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 predecible de manera única, por lo que no hay efectos secundarios.
Para hacerlo aún más tonto. Una función que vuelve
42
siempre es claramente pura. Pero si alguien loco decide hacer42
una variable cuyo valor podría cambiar, la misma función deja de ser pura en las nuevas condiciones.Tenga en cuenta que estoy usando la notación literal del objeto por simplicidad para demostrar la esencia. Desafortunadamente, las cosas están desordenadas en lenguajes como JavaScript, donde
error
no es un tipo que se comporta de la manera que queremos aquí con respecto a la composición de funciones, mientras que los tipos reales les gustanull
oNaN
no se comportan de esta manera, sino que pasan por algo artificial y no siempre intuitivo tipo de conversiones.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 quemaybe number
hace, aparte de su nombre confuso, que debe ser de tiponumber
o del nuevo tipo de excepciónE
, entonces es realmente la uniónnumber | E
denumber
yE
. En particular, depende de cómo queremos construirE
, que no se sugiere ni se refleja en el nombremaybe number
.¿Qué es la composición funcional?
Se trata de las funciones matemáticas toma de la operación
f: X -> Y
yg: Y -> Z
, y la construcción de su composición como función deh: X -> Z
la satisfacciónh(x) = g(f(x))
. El problema con esta definición ocurre cuando el resultadof(x)
no está permitido como argumento deg
.En matemáticas, esas funciones no se pueden componer sin un trabajo adicional. La solución estrictamente matemática para nuestro ejemplo anterior de
f
yg
es eliminar0
del conjunto de definición def
. Con ese nuevo conjunto de definición (nuevo tipo más restrictivox
),f
se puede componer cong
.Sin embargo, no es muy práctico en la programación restringir el conjunto de definiciones de
f
ese tipo. En cambio, se pueden usar excepciones.O como otro enfoque, los valores artificiales se crean como
NaN
,undefined
,null
,Infinity
etc Por lo tanto se evalúa1/0
aInfinity
y1/-0
a-Infinity
. Y luego fuerce el nuevo valor nuevamente en su expresión en lugar de lanzar una excepción. Llevar a resultados que puede o no encontrar predecibles:Y volvemos a los números regulares listos para seguir adelante;)
JavaScript nos permite seguir ejecutando expresiones numéricas a cualquier costo sin arrojar errores como en el ejemplo anterior. Eso significa que también permite componer funciones. De eso se trata exactamente la mónada: es una regla componer funciones que satisfagan los axiomas definidos al comienzo de esta respuesta.
Pero, ¿la regla de la función de composición, que surge de la implementación de JavaScript para tratar errores numéricos, es una mónada?
Para responder a esta pregunta, todo lo que necesita es verificar los axiomas (se deja 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 la regla que prescribe que si
f
arroja una excepción para algunosx
, también lo hace su composición con cualquierag
. Además, haga que la excepción seaE
globalmente única con un solo valor posible ( objeto terminal en la teoría de categorías). Ahora los dos axiomas son comprobables al instante y obtenemos una mónada muy útil. Y el resultado es lo que se conoce como quizás la mónada .fuente
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 mónada que encapsulax
m >>= f
(léalo como "el operador de enlace") aplica la funciónf
al valor en la mónadam
Eso 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 cuando decimos que esas cosas son mónadas es que tienen la interfaz de mónada
return
y>>=
.fuente
bind
función que debe definirse para cada tipo de mónada, ¿no es así? Esa sería una buena razón para no confundir el enlace con la composición, ya que existe una única definición para la composición, mientras que no puede haber una sola definición para una función de enlace, hay una por tipo monádico, si lo entiendo correctamente.De wikipedia :
Creo que lo explica muy bien.
fuente
Trataré de hacer la definición más corta que pueda manejar usando términos OOP:
Una clase genérica
CMonadic<T>
es una mónada si define al menos los siguientes métodos:y si las siguientes leyes se aplican a todos los tipos T y sus posibles valores t
identidad izquierda:
identidad correcta
asociatividad:
Ejemplos :
Una Mónada de Lista puede tener:
Y flatMap en la lista [1,2,3] podría funcionar así:
Iterables y Observables también se pueden hacer monádicos, así como Promesas y Tareas.
comentario :
Las mónadas no son tan complicadas. La
flatMap
función es muy parecida a la más comúnmap
. Recibe un argumento de función (también conocido como delegado), al que puede llamar (inmediatamente o más tarde, cero o más veces) con un valor proveniente de la clase genérica. 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, proporcionacreate
un constructor que puede crear una instancia de esa clase genérica a partir de un valor. El resultado de devolución de flatMap también es una clase genérica del mismo tipo, que a menudo empaqueta los mismos valores contenidos en los resultados de devolución de una o más aplicaciones de flatMap a los valores previamente contenidos. Esto le permite encadenar flatMap tanto como desee:Sucede que este tipo de clase genérica es útil como modelo base para una gran cantidad de cosas. Esta (junto con las jergas de la teoría de categorías) es la razón por la cual las mónadas parecen tan difíciles de entender o explicar. Son algo muy abstracto y solo se vuelven obviamente útiles una vez que se especializan.
Por ejemplo, puede modelar excepciones utilizando contenedores monádicos. Cada contenedor contendrá el resultado de la operación o el error que ha 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 controlador de errores adjuntada mediante un método llamado
.orElse()
(dicho 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 mónadas. No sé si es posible escribir una interfaz de este tipo en C #, pero que yo sepa no lo es:
fuente
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 quizás al lenguaje de verificación de punteros nulos, de modo que los cálculos que fallan (es decir, no producen nada en Haskell) emitan 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 una 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 pueden llamarse en un contexto, lo que llama un objeto. Una mónada es también una abstracción de esta idea, en la medida en que diferentes mónadas se pueden combinar de manera arbitraria, "importando" efectivamente todos los métodos de la submónada al alcance.
Arquitectónicamente, uno usa firmas de tipo para expresar explícitamente qué contextos pueden usarse para calcular un valor.
Uno puede usar transformadores de mónada para este propósito, y hay una colección de alta calidad de todas las mónadas "estándar":
con transformadores de mónada correspondientes y clases de tipos. Las clases de tipos permiten un enfoque complementario para combinar mónadas al unificar sus interfaces, de modo que las mónadas concretas puedan implementar una interfaz estándar para el "tipo" de mónada. Por ejemplo, el módulo Control.Monad.State contiene una clase MonadState sm y (State s) es una instancia del formulario
La larga historia es que una mónada es un functor que atribuye "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 adjunto, al menos de forma restringida
Entonces:
es una función que inyecta un valor de tipo a en una "acción" de mónada de tipo m a.
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 (>> =) extrae f en m y lo aplica al resultado.) Como consecuencia, si tenemos f :: a -> mb y g :: b -> mc, podemos acciones de "secuencia":
O, usando "hacer notación"
El tipo para (>>) puede ser esclarecedor. Está
Corresponde al operador (;) en lenguajes de procedimiento como C. Permite hacer anotaciones como:
En lógica matemática y filosófica, tenemos marcos y modelos, que son "naturalmente" modelados con el 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 de necesidad, podríamos decir que una proposición es necesaria si es cierta en "todos los mundos posibles", si es cierta con respecto a cada dominio admisible. Esto significa que un modelo en un lenguaje para una proposición puede reificarse 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 las capas, lo que implica que cada acción de mónada cuyo resultado es una acción de mónada puede integrarse en la mónada.
Más importante aún, significa que la mónada está cerrada 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
para que podamos transformar una acción en (Quizás T 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 (Quizás a) -> MaybeT ma es efectivamente un constructor de 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). 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 la "programación de sistemas de alto nivel" para ayudar a mantener la seguridad de los tipos (solo estoy usando los tipos y sus declaraciones. Las implementaciones son sencillas con el uso de combinadores):
El monadismo es la arquitectura subyacente de lo que se podría llamar el patrón de "intérprete" o "comando", abstraído en su forma más clara, ya que cada cálculo monádico debe "ejecutarse", al menos trivialmente. (El sistema de tiempo de ejecución ejecuta la mónada IO para nosotros y es el punto de entrada para cualquier programa Haskell. IO "impulsa" el resto de los cálculos, ejecutando acciones IO en orden).
El tipo para unirse 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 suele ser más importante para fines teóricos, en virtud de su tipo. Pero comprender el tipo significa comprender las mónadas. Los tipos de unión y transformador de mónada son efectivamente composiciones de endofunctores, en el sentido de composición de funciones. Para ponerlo en un pseudo-lenguaje tipo Haskell,
Foo :: m (ma) <-> (m. M) a
fuente
Una mónada es un conjunto de funciones.
(Pst: una matriz de funciones es solo un cálculo).
En realidad, en lugar de una matriz verdadera (una función en una matriz de celdas) tiene esas funciones encadenadas por otra función >> =. >> = permite adaptar los resultados de la función i a la función de alimentació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 que se encadenan deben tomar un "valor desnudo" y devolver un resultado etiquetado. Una de las tareas de >> = es extraer un valor desnudo de su contexto. También existe la función "return", que toma un valor desnudo y lo coloca con una etiqueta.
Un ejemplo con Quizás . Usémoslo para almacenar un número entero simple en el que hacer cálculos.
Solo para mostrar que las mónadas son un conjunto de funciones con operaciones auxiliares, considere el equivalente al ejemplo anterior, simplemente usando un conjunto real de funciones
Y se usaría así:
fuente
>>=
es un operador\x -> x >>= k >>= l >>= m
es un conjunto de funciones, también lo esh . g . f
, lo que no involucra a las mónadas en absoluto.En términos OO, una mónada es un contenedor fluido.
El requisito mínimo es una definición de
class <A> Something
que admite un constructorSomething(A a)
y al menos un métodoSomething<B> flatMap(Function<A, Something<B>>)
Podría decirse que también cuenta si su clase 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 el momento de la compilación.¿Por qué es útil una mónada? Porque es un contenedor que permite operaciones en cadena que preservan la semántica. Por ejemplo,
Optional<?>
conserva la semántica de isPresent paraOptional<String>
,Optional<Integer>
,Optional<MyClass>
, etc.Como un ejemplo aproximado,
Tenga en cuenta que comenzamos con una cadena y terminamos con un número entero. Muy genial.
En OO, puede tomar un pequeño movimiento de mano, pero cualquier método en Something que devuelva otra subclase de Something cumple con el criterio de una función contenedor que devuelve un contenedor del tipo original.
Así es como se preserva la semántica, es decir, el significado y las operaciones del contenedor no cambian, simplemente envuelven y mejoran el objeto dentro del contenedor.
fuente
Las mónadas en uso típico son el equivalente funcional de los mecanismos de manejo de excepciones de la 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 arroja una excepción, la ejecución normal de la secuencia de declaraciones se detiene y transfiere a un controlador de excepciones.
Sin embargo, los lenguajes de programación funcional evitan filosóficamente las funciones de manejo de excepciones debido a su naturaleza "goto". La perspectiva de la programación funcional es que las funciones no deberían 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 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 de manera segura 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 forma segura.
fuente
Una explicación simple de Monads con el estudio de caso de Marvel está aquí .
Las mónadas son abstracciones utilizadas para secuenciar funciones dependientes que son efectivas. Efectivo aquí significa que devuelven un tipo en forma F [A], por ejemplo, Opción [A] donde Opción es F, llamada constructor de tipo. Veamos esto en 2 simples pasos
Sin embargo, si la función devuelve un tipo de efecto como la Opción [A], es decir, A => F [B], la composición no funciona para ir a B, necesitamos A => B, pero tenemos A => F [B].
Necesitamos un operador especial, "bind" que sepa cómo fusionar estas funciones que devuelven F [A].
La función "vincular" se define para la F específica .
También hay "retorno" , de tipo A => F [A] para cualquier A , definido para esa F específica también. Para ser una mónada, F debe tener definidas estas dos funciones.
Por lo tanto, podemos construir una función efectiva A => F [B] a partir de cualquier función pura A => B ,
pero una F dada también puede definir sus propias funciones especiales "integradas" opacas de tales tipos que un usuario no puede definirse a sí mismo (en un lenguaje puro ), como
fuente
Estoy compartiendo mi comprensión de las mónadas, que puede no ser teóricamente perfecta. Las mónadas tratan sobre la propagación del contexto . Mónada es, usted define algún contexto para algunos datos (o tipos de datos), y luego define cómo se llevará ese contexto con los datos a lo largo de su canal de procesamiento. Y definir la propagación del contexto se trata principalmente de definir cómo fusionar múltiples contextos (del mismo tipo). Usar Monads también significa garantizar que estos contextos no se eliminen accidentalmente de los datos. Por otro lado, otros datos sin contexto pueden llevarse a un contexto nuevo o existente. Entonces, este concepto simple puede usarse para asegurar la corrección del tiempo de compilación de un programa.
fuente
Si alguna vez usó Powershell, los patrones que Eric describió deberían sonar familiares. Los cmdlets de Powershell son mónadas; La composición funcional está representada por una tubería .
La entrevista de Jeffrey Snover con Erik Meijer entra en más detalles.
fuente
Vea mi respuesta 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 programación funcional y utiliza pseudocódigo con
function(argument) := expression
sintaxis con las expresiones más simples posibles.Este programa C ++ es una implementación de la mónada pseudocódigo. (Para referencia:
M
es el constructor de tipo,feed
es la operación "vincular" ywrap
es la operación "retorno").fuente
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 también conocida como composición de funciones (es decir, cuando f1 llama a f2 llama a f3, f3 debe evaluarse antes de f2 antes de f1) para representar la composición secuencial de forma natural, especialmente en el contexto de un modelo de evaluación diferida (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 en el que f3, f2 y f1 realmente no devuelven nada [encadenados como f1 (f2 (f3)) es artificial, puramente destinado a crear secuencia]).
Esto es especialmente relevante cuando los efectos secundarios están involucrados, es decir, cuando se altera algún estado (si f1, f2, f3 no tuvieron efectos secundarios, no importaría en qué orden se evalúan; lo cual es una gran propiedad de puro lenguajes funcionales, para poder paralelizar esos cálculos, por ejemplo). Cuantas más funciones puras, mejor.
Creo que desde ese estrecho punto de vista, las mónadas podrían verse como azúcar sintáctica para los idiomas que favorecen la evaluación diferida (que evalúan 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) se pueden presentar de forma natural, de manera imperativa, pero se separan limpiamente de las funciones puras (sin efectos secundarios), que pueden ser evaluado perezosamente.
Sin embargo, este es solo un aspecto, como se advirtió aquí .
fuente
La explicación más simple que se me ocurre es que las mónadas son una forma de componer funciones con resultados embellecidos (también conocida como composición de Kleisli). Una función "embellecido" tiene la firma
a -> (b, smth)
, dondea
yb
son tipos (pensarInt
,Bool
) que pueden ser diferentes entre sí, pero no necesariamente - ysmth
es el "contexto" o el "embelishment".Este tipo de funciones también se puede escribir
a -> m b
dondem
es equivalente a la "embellecimiento"smth
. Entonces, estas son funciones que devuelven valores en contexto (piense en funciones que registran sus acciones, dóndesmth
está el mensaje de registro; o funciones que realizan entrada / salida y sus resultados dependen del resultado de la acción IO).Una mónada es una interfaz ("typeclass") que hace que el implementador le diga cómo componer dichas funciones. El implementador necesita definir una función de composición
(a -> m b) -> (b -> m c) -> (a -> m c)
para cualquier tipom
que quiera implementar la interfaz (esta es la composición de Kleisli).Entonces, si decimos que tenemos un tipo de tupla que
(Int, String)
representa los resultados de los cálculos enInt
s que también registran sus acciones,(_, String)
siendo el "embellecimiento" - el registro de la acción - y dos funcionesincrement :: Int -> (Int, String)
ytwoTimes :: Int -> (Int, String)
queremos obtener una funciónincrementThenDouble :: Int -> (Int, String)
que es la composición de las dos funciones que también tiene en cuenta los registros.En el ejemplo dado, una implementación de mónada de las dos funciones se aplica al valor entero 2
incrementThenDouble 2
(que es igual atwoTimes (increment 2)
) que arrojaría(6, " Adding 1. Doubling 3.")
resultados intermediosincrement 2
iguales(3, " Adding 1.")
etwoTimes 3
iguales a(6, " Doubling 3.")
De esta función de composición de Kleisli se pueden derivar las funciones monádicas habituales.
fuente