¿En qué se diferencian las mónadas del encadenamiento?

Si está usando la palabra de la forma en que creo que está, el encadenamiento no es más que un objeto que devuelve algún objeto al que se le puede aplicar otro método que le permite hacer una cadena de métodos. Esto tiene poco que ver con las mónadas. En el mundo de la programación funcional, lo más parecido a esto es componer un montón de funciones: f (g (h (i (j (kx))))) .

Pero puedes acercarte a las mónadas desde la perspectiva de generalizar la composición. Suponga que tiene una función f llevar objetos de tipo a objetos de tipo b , escritos como f:a -> b . Ahora suponga que tiene otra función g:b -> c . Puedes componerlos para obtener g . f : a -> c g . f : a -> c . Puede componerlos porque f asigna a un tipo que g acepta como argumento. Puede encadenar cualquier secuencia de funciones como esta si el encabezado de cada función coincide con la cola de la siguiente.

Ahora suponga que tiene una función en el nivel de tipo que asigna tipos a tipos. Puede sonar un poco desconocido pero es compatible con muchos idiomas. Por ejemplo, en C ++ puedes usar plantillas. Entonces, dado el tipo int , podemos convertirlo en el tipo de listas de int s así: list . Por lo tanto, puede pensar en la list como una función de nivel de tipo que asigna objetos a listas de objetos. Java tiene genéricos y estoy seguro de que otros lenguajes tienen cosas similares.

Ahora suponga que tiene una función de tipo f:a - > mb y una función de tipo g:b -> mc , donde m es una de las funciones de nivel de tipo que mencioné. Por ejemplo, en C ++ podríamos elegir m como list para que f asigne objetos de tipo a listas de objetos de tipo b . Ya no podemos componer estas funciones de la manera obvia porque la cabeza de f no es el mismo tipo de cola de g .

Pero el hecho de que la regla de composición habitual no funcione no significa que no podamos inventar nuestra propia función de composición. Supongamos que m es la list . ¿Cómo podríamos componer f y g ? Bueno, g necesita argumentos de tipo b , y aunque f no produce argumentos de tipo b , produce una lista de ellos. Por lo tanto, no podemos aplicar g al tipo de retorno de f , pero podemos aplicarlo a cada uno de los elementos del tipo de retorno. Eso nos da una lista de listas. Y dada una lista de listas, podemos aplanarla a una simple lista concatenando todas las listas juntas. En otras palabras, he descrito una forma de tomar f:a - >mb g:b -> mc para hacer una nueva función de tipo f:a - >mc . Entonces, si usamos la nueva regla de composición, podemos encadenar tantas funciones de esta ‘forma’ como queramos. Esta regla de composición se puede implementar como una función que llamaré ‘función de encadenamiento’ solo por esta respuesta.

Una mónada es una función de nivel de tipo como m junto con una función de encadenamiento y un conjunto de reglas que esta función de encadenamiento debe cumplir. Hay muchas cosas diferentes con las que podríamos reemplazar m además de list pero, sorprendentemente, muchas de las útiles cumplen el mismo conjunto de reglas.

Descargo de responsabilidad: supongo que te refieres a la composición de funciones cuando usas la palabra “encadenamiento”.

Veamos la composición de funciones:

Si tenemos una función f :: a -> b significa que la función f toma argumentos del tipo a y devuelve un valor de tipo b .

También tenemos una función g :: b -> c , que toma como argumento un valor de tipo b y devuelve un valor de tipo c .

Debido a que la salida de la función f tiene el mismo tipo que la entrada de la función g , podemos componer estas funciones: g . f :: a -> c g . f :: a -> c .

Ahora echemos un vistazo a las mónadas:

Tenemos una función f' :: a -> mb , lo que significa que la función f' toma como argumento un valor de tipo a y devuelve un valor de tipo mb , donde m es un contexto (puede tomar el contexto como el adicional información adjunta al valor de tipo b , lo explicaré más adelante) en el que envolvemos un valor de tipo b .

También tenemos una función g' :: b -> mc , que toma como argumento un valor de tipo b y devuelve un valor de tipo mc .

No podemos componer las funciones f' g' usando la composición de funciones g' . f' g' . f' , porque el tipo de salida de la función f' no es el mismo que el tipo de entrada de la función g' y obtendrá un error de tipo .

Pero podemos utilizar para componer estas dos funciones operador Kleisli monádico (>=>) :: Monad m => (a -> mb) -> (b -> mc) -> a -> mc . Como puede ver, se necesitan 3 argumentos: función a -> mb , función b -> mc y valor de tipo a – ¡y es exactamente nuestro caso! Entonces, llegamos a:

  (f '> => g') x = hacer
     entrada <- f 'x
     g 'entrada 

Por lo tanto, podemos decir que las mónadas ayudan a "encadenar" la función f' con la función g' si la entrada de la función g' tiene el mismo tipo que la salida de la función f' , pero envuelta en el contexto m .

Un poco más sobre qué es el contexto m :

El resultado de funciones puras en matemáticas y en Haskell depende solo de sus argumentos. Significa que si proporciona funciones puras con los mismos argumentos infinitas veces, obtendrá el mismo resultado cada vez que ejecute una función. Si proporciona su función, add xy = x + y con los argumentos 2 y 3 , siempre obtendrá el resultado 5 y nunca otro resultado.

Pero el mundo que nos rodea es más complejo. Aquí hay unos ejemplos:

  1. A veces supone que obtendrá como entrada de los números de usuario 2 y usará la entrada como argumentos para su función add , pero no puede controlar al usuario y puede obtener en lugar de números una cadena. Su programa Haskell tiene que ser capaz de manejar tales casos. Entonces, escribe en su código que si obtiene algo más que no 2 números para su función, el resultado de la función es Nothing .
  2. O si escribe una función que recopila todos los nombres de archivo en su carpeta y subcarpetas Documentos, obtendrá resultados diferentes si ejecuta esta función hoy y dentro de un año, a pesar de que proporciona su función con el mismo argumento (con la ruta a su Carpeta de documentos).
  3. O si escribe una función que tiene un valor y algún estado externo, obtendrá resultados diferentes cuando ejecute esta función, porque el estado está cambiando. Imagínese que está generando números pseudoaleatorios. Cada vez que genera un número pseudoaleatorio, se actualiza el estado del generador de pseudonúmeros. Si desea poder replicar secuencias de pseudo-números, debe guardar no solo los números, sino también los estados iniciales y siguientes del generador.

Todas estas funciones anteriores son impuras , porque sus resultados dependen no solo de sus argumentos. Y aquí usamos mónadas, que envuelven nuestros valores en contexto m . Nos muestra que estas funciones son impuras y contiene información externa que no podemos controlar.

El primer ejemplo anterior es el más simple. Si crees que puedes obtener cualquier cosa menos números, usarás la mónada Maybe a . Si del usuario obtiene 2 números, el resultado de su función será Just number , y si obtiene algo más, el resultado será Nothing .

El segundo ejemplo explica por qué las listas son mónadas. Cuando recorra su carpeta Documentos y sus subcarpetas en un tiempo diferente, obtendrá como resultado listas de diferente longitud, porque la cantidad de archivos en su carpeta Documentos ha cambiado. Llama al no determinismo y List mónada representa llevar múltiples valores.

En el tercer ejemplo, puede usar State mónada State sa , que contiene no solo el valor a generado por el generador de números pseudoaleatorios, sino el estado de los generadores de pseudonúmeros s , que cambia después de cada generación de pseudonúmeros.

Espero que ahora entiendas por qué necesitamos valores envueltos. Y necesitamos mónadas como una interfaz entre nuestras funciones impuras y puras, entre valores envueltos y sin envolver.