Skip to content

Instantly share code, notes, and snippets.

@josejuan
Last active December 26, 2015 02:39
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save josejuan/7080358 to your computer and use it in GitHub Desktop.
Save josejuan/7080358 to your computer and use it in GitHub Desktop.
*** AVISO, si lees ésto podrías llegar a desaprender ***
Cuidado con las mónadas, yo estoy bastante seguro de no entenderlas (bien).
Me explico, el concepto "por definición" de mónada es bastante simple,
bastante confuso al principio (como todo, supongo) pero luego es simple
(simple en el sentido de que las reglas que se aplican son sencillas,
luego cada mónada ya... puede ser infumable de entender).
(Ojo, lo que sigue hasta el siguiente aviso es una explicación CONCEPTUAL,
no tiene relación con una visión práctica)
Una definición "free style" de mónada para un programador OOP podría ser
que, cuando "estás dentro" de una mónada (algo así como "estar en el ámbito
de la mónada") es lo mismo que en OOP cuando estás dentro del ámbito de
la instancia de un objeto (pero la mónada es mucho más potente).
Las similitudes (insisto "free style") son:
1. una instancia de objeto acarrea objetos implícitamente (están ahí sin
hacer nada), en una mónada, los objetos son acarreados también implícitamente.
2. en una instancia de objeto, puedes llamar a los métodos dentro del
ámbito de la instancia (¡ojo! incluyendo miembros heredados) asumiendo
implícitamente los datos en [1]. En una mónada ocurre lo mismo, puedes
llamar a funciones que accederán implícitamente a los datos en [1].
Realmente, el contexto así descrito muestra las pocas similitudes, porque
hay montones de diferencias.
(Ojo, ahora buscar cualquier similitud con OOP llevará a la locura :)
Por ejemplo, las mónadas funcionan currificando el contexto, ésto sólo
se entiende porque *TODO ES INMUTABLE* (el contexto, en un lenguaje no
inmutable por definición, sería simplemente pasar los parámetros por valor),
ésto es **ALGO COMO** (free style) usando pseudo-C#/javascript lambdas
// ¡¡¡ESTO ES LA FUNCIÓN MONÁDICA f1 !!!
(var contexto1, var argumentos1) => {
// ¡¡¡ESTO ES LA FUNCIÓN MONÁDICA f2 !!!
resultado1 <= ((var contexto2, var argumentos2) {
...
})(argumentos);
var resultado2 = resultado1 + 314159265358979;
return resultado2;
}
¡¡PERO OJO!! la aplicación del operador (<=) ¡¡¡no es asignación!!!, la
forma en que funciona (que no opera) esa "pseudoasignación" depende de
cada mónada (aquí es donde empezamos a no entender nada de nada ¿eh?,
je, je, ...).
¡¡OTRO OJO!! ese return que vemos ahí **NO ES UN RETURN**, sino que METE
EL RESULTADO DEL CÓMPUTO EN LA MÓNADA (esto tampoco se entiende, lo se,
es un lío, ...). En este caso es al revés, siempre funciona igual ¡pero
opera de diferente forma según la mónada!.
Por ejemplo, la mónada listas [1..10] cuando devuelve un resultado al
aplicar una función monádica (digamos f), lo que hace es aplicar la función
monádica a cada elemento 1, 2, ... 10 (item de la lista) del contexto
(la lista ¡porque es una mónada de listas!), como dicha función tiene
por fuerza que devolver una lista (¡porque es una mónada de listas y cualquier
función monádica que actúe en dicha mónada debe devolver listas!), tenemos
una lista de listas, por eso el "return" ¡¡¡de la mónada listas!!! (otra
mónada hará otra cosa **TOTALMENTE DIFERENTE**) lo que hace (repito, porque
le da la gana, podría hacer otra cosa y seguir siendo una mónada) es concatenar
los resultados.
Es decir, si tenemos una mónada sobre X dato, cualquier función que sea
digna de llamarse mónadica sobre X deberá devolver X.
Por ejemplo, si estamos en la mónada listas, cualquier función que devuelva
el tipo listas podrá aplicarse dentro de la mónada listas (¡ésto es mejor
que OOP! en OOP dentro de una instancia de un objeto sólo pueden aplicarse
sus miembros, pero sólo los suyos, mientras que en las mónadas ¡puedes
definir a posteriori tu propio método y actuar DENTRO de la mónada [parecido
a como pasa con las extensiones de C#]).
Así, cualquier función que termine con la firma
... -> [a]
es monádica en la mónada listas.
Por ejemplo, la función generadora de listas
[1..n]
toma como argumento un número y devuelve una lista.
Si estamos en la mónada listas, y hacemos ésto
do
x <- [1..10]
return x
¡No estamos haciendo nada!, veamos que está pasando
Aquí `x` aunque no lo parezca, ¡¡¡es el argumento de una función!!! (te
he matado lo se), de la función definida por **TODO LO QUE HAY DEBAJO
DE ESA PSEUDOASIGNACION**.
De hecho puede ponerse como lo hemos puesto antes
// todo ésto, es lo que haya debajo de un "bind" (<-)
(contexto_mónada, x) => {
return x;
}
sí, al operador (<-) se le llama "bind" (no pseudoasignación :).
El bind, como he dicho antes siempre funciona igual (hace que todo lo
que haya debajo sea una función) ¡pero la operación que percibimos difiere
de la mónada!.
Bien, sigamos con el ejemplo, hemos dicho que "return" lo que hace **en
la mónada de listas** (en otra mónada hará otra cosa) es un "concat map
f" ¿y cual es nuestra f aquí? ¡pues la identidad, porque teniendo un x
devolvemos un simple x!.
Así, en ese return lo que hacemos es un "map id", es decir, si nos dan
un 1, tenemos un 1, si nos dan un 2, tenemos un 2, ... y todo ello, lo
concatenamos, vamos, que tenemos la lista tal cual.
Veamos el código otra vez
do
x <- [1..10]
return x
¡¡cuidado que el que actúa de forma diferente según la mónada es el "return"!!
el bind siempre actúa igual (haciendo que lo que tiene debajo sea la función
"f" que luego el return hace "concat map f" en la mónada listas).
Bien, así que el resultado del cómputo anterior es
[1..10]
menuda sorpresa... :P
Veamos lo anterior a cámara lenta.
Tenemos
do
x <- [1..10]
return x
y sabemos que lo de debajo del bind se convierte en una función f, digamos
function f(x) {
retornar (return x)
}
**ATENCIÓN** que `x` es un elemento y "return" **LO METE EN LA MÓNADA**
por lo que hacer una llamada como
f(3)
lo que devuelve **SOLO** en el caso de la mónada listas, en otra mónada
devolverá otra cosa
[3]
así, con
do
x <- [1..10]
return x
la mónada listas hace un
concat (map f [1..10])
es decir, tenemos
concat ([[1],[2],[3], ..., [10]])
es decir
[1,2,3,...,10]
¿Parece fácil?, si te lo parece, no lo has entendido :P
¿que pasa si hacemos
do
x <- [1..6]
[1..x] // ojo que no ponemos return
veamos, lo de debajo del bind se convierte en una función digamos
function f(x) {
retornar [1..x]; // ¡¡ojo que no es "return!
}
entonces la mónada listas hace un
concat (map f [1..6])
por lo que tenemos
concat ([[1], [1,2], [1,2,3], ...)
por lo que es
[1,1,2,1,2,3,1,2,3,4,1,2,3,4,5,1,2,3,4,5,6]
¿Y si hacemos
do
x <- [1..6]
return [1..x] // ojo que SI ponemos return
veamos, lo de debajo del bind ahora es
function f(x) {
retornar (return [1..x]); // ojo que SI es "return"
}
entonces la mónada listas hace un
concat (map f [1..6])
¡pero cuidado! dijimos que "return" mete las cosas en la mónada, que esa
cosa sea *AHORA* una lista (ej. [1..3]) no impide que lo meta en la mónada
y si en el primer ejemplo (de la identidad) era
f(3) retorna [3]
ahora
f(3) retorna [[1..3]]
por lo que tenemos
concat ([[[1]],[[1,2]],...,[[1,2,3,4,5,6]]])
es decir
[[1],[1,2],[1,2,3],[1,2,3,4],[1,2,3,4,5],[1,2,3,4,5,6]]
Triple salto mortal, ¿y si...
do
x <- [1..3]
y <- "ABC"
return (x, y)
pues resulta que ahora tenemos dos bind, por lo que todo lo de debajo
del primer bind es una función y todo lo de debajo del segundo bind es
otra función (dentro de la primera, claro)
function f(x) {
function g(y) {
retornar (return (x, y))
}
retornar lo que venga
}
convirtiendo a "código plano", tenemos primero
concat (map f [1..3])
y luego, al expandir f
concat (map (\x -> concat (map g "ABC")) [1..3])
es decir
concat (map (\x -> concat (map (\y -> return (x, y)) "ABC")) [1..3])
que finalmente (sin mónadas, completamente código plano), es
concat (map (\x -> concat (map (\y -> [(x, y)]) "ABC")) [1..3])
de ahí, que al hacer
do
x <- [1..3]
y <- "ABC"
return (x, y)
**CON** la mónada listas sea
[(1,'A'),(1,'B'),(1,'C'),(2,'A'),...,(3,'C')]
El cómo actúan (funcionan y operan) realmente el "return" y "bind" monádicos
depende de cada mónada y puede ser realmente diferente una de otras.
El tema entonces está en **COMPRENDER LAS PROPIEDADES GENERALES DE LAS
MÓNADAS** (aquellas que actúan con independencia de la expresión concreta
de una mónada concreta).
Por ejemplo, creo que el concepto de "aditividad" es comprendido (aunque
quizás no explícitamente sí de forma intuitiva) por cualquier persona
(incluso de muy corta edad).
Todos sabemos que podemos "añadir sal a la sopa", que podemos "añadir
una prenda a un vestido", que *NO* podemos "añadir aceite al agua", etc...
Las propiedades de "añadir" las comprendemos sin tener que ir a ejemplos
concretos (yo actualmente para comprender una mónada tengo que tener un
ejemplo concreto).
Para comprender las propiedades y "esencia" de las mónadas hay que ir
a la teoría de categorías, las propiedades del "return" y del "bind" *CREO*
que no se van mucho de lo dicho, pero yo no comprendo aún (y no se si
lo sabré algún día) cuales son esas propiedades interesantes que tienen
las mónadas y que generalizan los conceptos concretados anteriormente
(en la mónada listas, pero fácilmente aplicables a Maybe, Reader, Writer,
...).
En fin, espero que no hayas desaprendido mucho.
:)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment