Usar mónadas es mucho más fácil de lo que crees, empezando con la programación funcional

Usar mónadas es mucho más fácil de lo que crees, empezando con la programación funcional
Sin comentarios Facebook Twitter Flipboard E-mail

Seguramente todos tenemos formada una buena idea sobre lo que es una "propiedad", un "método estático", un "singleton" u otros términos de uso común. Nos resultan algo más exóticos e infrecuentes términos como "clase abstracta" o "función virtual pura". Todos éstos son términos habituales en la programación orientada a objetos. Lo que seguramente ya no tengamos tan claras son todas las ramificaciones, implicaciones, interacciones que todos éstos conceptos poseen y sin embargo los usamos. Usar una mónada es tanto o más fácil de usar que, por ejemplo, un objeto. Pero una mónada no es un objeto, y quien quiera comprender cómo usar una mónada tendrá que hacer el esfuerzo por desprenderse de viejas y apoltronadas preconcepciones.

¿Qué es una mónada?

Una mónada está bien definida. Podemos usar la definición de la foto de la portada si queremos adquirir las profundas y sesudas implicaciones que las mónadas poseen (y que yo desconozco por incapacidad manifiesta) o bien podemos usar otra más útil y pragmática como la usada en diversos lenguajes de programación y en particular la de Haskell. Sin embargo, para aprender informalmente lo que es una mónada, creo que es un error tomar como base la definición de mónada, la cual sólo es útil cuando ya se está cómodo usándolas.

¿A qué huelen las mónadas?

Huelen a "contexto". Cuando estás tumbado en el sofá de tu casa, estás en un contexto. Cuando estás conduciendo, estás en un contexto. Cuando estás buceando en el mar, estás en un contexto. Así, podríamos decir que las mónadas son contextos:


dormirLaSiesta :: Sofá ()
dormirLaSiesta = ...

regresarAlFuturo :: DeLoreanDMC12 ()
regresarAlFuturo = ...

brazada :: Buceando ()
brazada = ...

Por tanto, una mónada es fácil de usar pero lo más importante es que es segura de usar porque no habría ningún problema en que te quedaras dormido en el sofá, pero sí desastroso si te da por dormirte en el coche o mientras buceas.


irAlPueblo :: DeLoreanDMC12 ()
irAlPueblo = do
                ...
                encenderMotor
                meterPrimera
                acelerar
                dormirLaSiesta        -- por fortuna no compila
                ...

No veas posibles similitudes con los objetos. Un objeto protege sus datos y define sus métodos, pero no controla quién ni cómo es usado y es difícilmente extensible (ej. mixings). Una mónada controla quién y cómo es usada, no sólo protege posibles datos, sino todo el contexto en que se ejecuta y es trivial y necesariamente extensible.

¿Para qué sirven las mónadas?

Simple y llanamente para definir una computación un proceso que se ejecuta en un contexto. El ejemplo anterior irAlPueblo es un sencillo ejemplo, pongamos ahora otro más interesante.

Supón que tenemos tres contextos situaciones completamente diferentes:

  1. una lista de números.
  2. un único número pero que quizás esté o no definido (el típico "opcional").
  3. un teletipo por el que un usuario introduce números.

Para cada situación, debemos implementar un proceso que sume 2 a los números involucrados. Es decir, el contexto con la lista de números se convertirá en un contexto con los números incrementados en dos unidades, el contexto que quizás tiene o no un número se verá incrementado o no en dos unidades y los números que el usuario introduce por el teletipo se verán incrementados en dos.


suma2_lista :: Lista Número -> Lista Número
suma2_lista lista = ...

suma2_quizás :: Quizás Número -> Quizás Número
suma2_quizás quizás = ...

suma2_teletipo :: Teletipo Número -> Teletipo Número
suma2_teletipo teletipo = ...

¿Cómo implementarías los tres procesos que te han solicitado?, ¿qué diferencias hay entre unos y otros?.

Al contrario de lo que ocurría con dormir la siesta en que una acción sí podía realizarse en un contexto pero no en otros, aquí es precisamente el mismo proceso el que puede efectuarse en cualquiera de los tres procesos pero, ¿cual es entonces el contexto sobre el que definiremos nuestro proceso?, ¿qué nombre tiene o como se define un contexto que a la vez sea una lista, un quizás y un teletipo?. Veámoslo:


suma2 :: Mónada m => m Número -> m Número
suma2 cosa_que_devuelve_números = ...

En lugar de concretar la mónada el contexto sobre el que sumaremos, decimos que "nos sirve cualquier mónada cosa que devuelva números". Ahora, las tres funciones anteriores son exactamente la nueva función suma2 y realmente no las necesitamos porque los tres procesos solicitados son:


> suma2 [1..5]
[3,4,5,6,7]
> suma2 (Just 10)
Just 12
> suma2 Nothing
Nothing
> suma2 (putStr "Número: " >> readLn)
Número: 34
36

Procesos sobre las mónadas

Ahora ya podemos empezar a definir un poco mejor ésto de las mónadas y para ello, únicamente necesitamos introducir dos símbolos:

  1. el símbolo que, dado un contexto, me devuelve algo. Por ejemplo si estoy en el coche, quiero obtener la velocidad actual, entonces tendrá que haber una función como dameVelocidad que en algún sitio (digamos variable) me entregue esa velocidad.
  2. el símbolo con el que devuelvo el resultado de mi definición. Por ejemplo si estoy pasando la velocidad de kilómetros/hora a millas/hora, tras hacer la conversión tendré que devolver el resultado.

Veamos los símbolos con un ejemplo:


-- Ésta nos la dan de serie con el coche
velocidadEnKilómetrosHora :: DeLoreanDMC12 KilómetrosHora

-- Queremos convertir de Kilómetros a Millas
velocidadEnMillasHora :: DeLoreanDMC12 MillasHora
velocidadEnMillasHora = do
                            kmh <- velocidadEnKilómetrosHora
                            return ( kmh / 1.609344 )

Y ya está, ya sabes usar las tan complicadas y difíciles mónadas.

Tejiendo con mónadas

Creo que resultará interesante contrastar las funciones suma2 y velocidadEnKilómetrosHora e implementar ambas. ¿Porqué en suma2 aparece la mónada dos veces (como argumento de entrada y como argumento de salida) y en velocidadEnKilómetrosHora sólo de salida?.

La razón es que en suma2 tomamos algo con números y devolvemos otro algo diferente con números diferentes mientras que velocidadEnKilómetrosHora es el mismo coche el que nos devuelve la velocidad (no hay dos coches). Veamos si comparando las definiciones se ve mejor:


-- Dada cualquier mónada que devuelve números, podemos sumar 2 a cada uno de esos números
suma2 :: Mónada m => m Número -> m Número
suma2 algo_que_devuelve_números = do
                                     x <- algo_que_devuelve_números
                                     return (x + 2)

-- La velocidad está en el coche, no necesitamos ningún argumento de entrada
velocidadEnKilómetrosHora :: DeLoreanDMC12 KilómetrosHora
velocidadEnKilómetrosHora = do
                               km <- distanciaRecorridaEnLosÚltimos (5 :: Segundos)
                               return (3600 * km / 5)

¿A donde ha ido la pureza?

Alguien puede pensar que velocidadEnKilómetrosHora no es una función pura, porque aparentemente devuelve un valor sin ningún argumento de entrada. Ésto no es así.

Lo que devuelve la función velocidadEnKilómetrosHora es en realidad una función de la forma \datos_coche -> .... Como devuelve una función, no requiere ningún argumento de entrada. Quizás te resulten familiares los términos deferred o promise en que una computación se realizará cuando cierto valor esté disponible. En este caso la función velocidadEnKilómetrosHora define una computación que promete devolver la velocidad del coche cuando esté disponible. El mecanismo usado en diversas mónadas es sencillo, pero los detalles requieren sentirse cómodo usando las mónadas.

Conclusión

Obviamente no hemos introducido los ingredientes necesarios para poder trabajar con mónadas en ningún lenguaje, no es el objetivo. Pero hemos dado una aproximación sobre lo que definen y cómo se opera "dentro" de ellas. Utilizar las mónadas no es más difícil que usar otros mecanismos que nos encontramos en los lenguajes de programación, pero al sernos extraños, debemos hacer un esfuerzo por no "cerrarnos en banda" y ser pacientes a que los nuevos conceptos vayan posándose en nuestro cerebro. Sólo la práctica nos permitirá percibir y poder enjuiciar adecuadamente las ventajas e inconvenientes de cada enfoque, en este caso el uso de mónadas para definir procesos que se ejecutan en cierto contexto.

Extra

Una mónada no es una lista de cosas, no es un opcional, no es un teletipo, no es una promesa, no es algo que contenga cosas ni es aquello que puede invocarse. Si eres capaz de ver que una mónada es tan sólo la concatenación de esos procesos ("promesas") que podemos definir sobre ciertos contextos, al fin y al cabo, quizás sí comprendes que una mónada ¡tan sólo es un monoide en la categoría de endofunctores!

Comentarios cerrados
Inicio