Funtores, Aplicativos y Mónadas en imágenes

Miguel Á. Moreno
10 min readMar 28, 2016

--

Es curioso, no se me da bien escribir posts, blogs, ni nada de eso, soy más consumidor que productor. No había escrito en medium tampoco hasta ahora, pero hace unos días me topé con un post sobre Funtores, Aplicativos y Mónadas contados con imágenes que me encantó. Lo he leído un par de veces y me he dado cuenta de que la traducción al español estaba desaparecida, cosas de Internet… Así que he contactado con el creador y me he ofrecido para traducirlo yo mismo, por si a alguien hispano hablante le apetece tenerlo en castellano y compartirlo. Este será mi primer post, por tanto :)

El post recorre lo que son los Funtores, los Aplicativos y las Mónadas con imágenes ilustrativas hechas por el propio autor, así como ejemplos en Haskell, aunque no creo que tengas que saber mucho Haskell para poder seguirlo. Yo he podido seguirlo sin problemas, así que tú también.

El link original al artículo es:

http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html

Espero que os guste tanto como a mí y os animo a seguir el blog de este autor.

Funtores, Aplicativos y Mónadas en imágenes

Una traducción entre mil

Aquí hay un simple valor:

Y sabemos cómo aplicar una función a este valor:

Bastante simple. Vamos a extender esto diciendo que cualquier valor puede estar en un contexto. Por ahora, puedes pensar en ese contexto como en una caja en la que puedes meter ese valor:

Ahora, cuando apliques la función a este valor, obtendrás diferentes resultados dependiendo de ese contexto. Esta es la idea en la que se basan los Funtores, los Aplicativos, las Mónadas y las Flechas (o Morfismos). El tipo `Maybe` define dos contextos relacionados: `Nothing` (que significa Nada) y `Just a` (que significaría algo como “Simplemente a”, siendo a un elemento no definido ahora mismo, será un número, un String… etc).

data Maybe a = Nothing | Just a

En un momento vamos a ver como difiere el aplicar una función cuando algo es un `Just` o un `Nothing`. ¡Pero primero vamos a hablar de Funtores!

Funtores

Cuando un valor está envuelto en un contexto, no puedes simplemente aplicarle una función normal:

Ahí es donde entra `fmap`. `fmap` viene del barrio, `fmap` es consciente de los contextos, `fmap` sabe como tiene que aplicar funciones a valores que están envueltos en un contexto. Por ejemplo, supón que quieres aplicar la función `(+3)` a un `Just 2`. Usando `fmap`:

> fmap (+3) (Just 2)
Just 5
La magia pasa ahí

¡Boom! ¡`fmap` nos enseña cómo se hace! Pero, ¿cómo sabe `fmap` cómo aplicar la función?

Pero, primero, ¿qué es realmente un funtor?

Un, Funtor es una `typeclass`, aquí está la definición:

class Functor f where
fmap :: (a->b) -> fa -> fb

1. Para hacer ‘f’ un Funtor,
2. Ese tipo de datos tiene que definir cómo `fmap` se va a comportar para él

Un Funtor es cualquier tipo de datos que define cómo `fmap` se comporta para su caso. Esto es cómo funciona `fmap`:

fmap :: (a->b) -> fa -> fb

1. `fmap` toma una función, como (+3)
2. y un funtor, como `Just 2`
3. y devuelve otro Funtor, como `Just 5`

Así que podemos hacer esto:

> fmap (+3) (Just 2)
Just 5

Y `fmap` mágicamente aplica la función, porque el tipo `Maybe` es un Funtor. Especifica en su definición cómo se tiene que comportar `fmap` con cosas como `Just` y `Nothing`.

instance Functor Maybe where
fmap func (Just val) = Just (func val)
fmap func Nothing = Nothing

Aquí puedes ver lo que está pasando entre bambalinas cuando escribimos

fmap (+3) (Just 2)

1. Desenvuelve/extrae el valor de su contexto
2. Aplica la función
3. Vuelve a envolver el resultado en un contexto igual al inicial

Así que te has quedado como… ok, `fmap` ¿puedes aplicarle la función (+3) a un `Nothing`?

1. No hay valor
2. No apliques la función
3. Acabas teniendo nada

> fmap (+3) Nothing
Nothing

No entra nada, no sale nada… ¡no puedo explicar eso!

Bill O’Really que ignora totalmente el funtor Maybe

Como Morpheo en Matrix, `fmap` sabe lo que tiene que hacer: empiezas no teniendo nada, y acabas sin tener nada! `fmap` es zen, es guay. Ahora tiene sentido por qué existe el tipo `Maybe`. Por ejemplo, aquí hay un ejemplo de cómo trabajarías con una base de datos en un lenguaje que no tiene el tipo `Maybe`:

post = Post.find_by_id(1)
if post
return post.title
else
return nil
end

Pero con Haskell:

fmap (getPostTitle) (findPost 1)

Si `findPost` devuelve un `post`, entonces obtendremos el título con `gePostTitle`. Si no devuelve nada, ¡entonces no obtendremos nada! Mola, ¿eh?. `<$>` es el símbolo que funciona como versión infija de `fmap` de manera que podemos usarlo así:

getPostTitle <$> (findPost 1)

Aquí hay otro ejemplo: ¿qué pasa cuando aplicas una función a una lista?

1. Un array de valores

2. Aplica la función a cada valor

3. Un nuevo array de valores

¡Las listas también son funtores! Aquí está la definición:

instance Functor [] where
fmap = map

Okei, okei, un último ejemplo: ¿Qué pasa cuando aplicas una función a otra función?

fmap (+3) (+1)

Aquí hay una función:

- Toma un valor
- Devuelve un valor

Aquí hay una función aplicada a otra función

- Toma un valor
- Devuelve un valor

¡El resultado es simplemente otra función!

> import Control.Applicative
> let foo = fmap (+3) (+2)
> foo 10
15

¡Así que las funciones son Funtores también!

instance Functor ((->) r) where
fmap f g = f . g

¡Cuando le aplicas fmap a una función, simplemente estás haciendo composición!

Aplicativos

Los Aplicativos lo llevan al siguiente nivel. Con los Aplicativos, nuestros valores se envuelven en un contexto, igual que con los Funtores:

¡Pero nuestras funciones también se pueden envolver en contextos!

Sip, déjalo reposar. Los Aplicativos no se andan con chiquitas. El módulo de haskell `Control.Applicative` define `<*>`, que sabe cómo aplicar una función envuelta en un contexto a un valor envuelto en un contexto:

1. Una función envuelta en un contexto
2. Un valor en un contexto
3. Desenvuelve tanto el valor como la función y aplícasela al valor
4. Envuelvelo en un conexto otra vez.

Ejemplo

Just (+3) <*> Just 2 == Just 5

Usar `<*>` puede llevar a situaciones interesantes, por ejemplo:

> [(*2), (+3)] <*> [1, 2, 3]
[2, 4, 6, 4, 5, 6]

Aquí hay una cosa que puedes hacer con Aplicativos que no puedes hacer con Funtores. ¿Cómo le aplicas una función que toma dos argumentos a dos valores envueltos?

> (+) <$> (Just 5)
Just (+5)
> Just (+5) <$> (Just 4)
ERROR ??? QUÉ QUIERE DECIR??? POR QUÉ ESTÁ LA FUNCIÓN ENVUELTA EN UN JUST?

Aplicativos:

> (+) <$> (Just 5)
Just (+5)
> Just (+5) <*> (Just 3)
Just 8

Los Aplicativos se ponen los Funtores a un lado.

“Los chicos mayores usan funciones con cualquier número de argumentos”, 

diría un Aplicativo.

“Armado con `<$>` y `<*>`, puedo coger cualquier función que espere cualquier número de argumentos desenvueltos. Luego le paso todos los argumentos envueltos y obtengo un valor envuelto! ¡JAJAJAJAJAJAJAJA!”
> (*) <$> Just 5 <*> Just 3
Just 15

Y, ¡eh!, hay una función llamada `liftA2` que hace lo mismo!

> liftA2 (*) (Just 5) (Just 3)
Just 15

Mónadas

Cómo aprender algo sobre las Mónadas:

  1. Sácate un doctorado en ingeniería informática.
  2. ¡Tíralo porque no lo necesitas para esta sección!

Las Mónadas añaden un giro de tuerca a lo visto hasta ahora:

Los Funtores aplican una función a un valor envuelto en un contexto:

Los Aplicativos aplican una función envuelta a un valor envuelto:

Las Mónadas aplican, a un valor envuelto, una función que a su vez devuelve un valor envuelto. Las Mónadas tienen una función `>>=` (llamada ‘bind’ y pronunciado “baind”) que hace esto.

Vamos a ver un ejemplo: Nuestra amiga ‘Maybe’ es una Mónada:

Solamente una Mónada a su bola

Supón que `half` es una función que solamente funciona con números pares

half x = if even x
then Just (x `div` 2)
else Nothing

- Toma un valor
- Devuelve un valor envuelto

¿Qué pasa si le metemos un valor envuelto?

half quejándose al recibir un valor envuelto

Necesitamos utilizar `>>=` para calzarle nuestro valor envuelto a la función. Aquí hay una foto de `>>=`:

Así es como funciona

> Just 3 >>= half
Nothing
> Just 4 >>= half
Just 2
> Nothing >>= half
Nothing

¿Qué está pasando por ahí dentro? Que `Monad`(Mónada) es otra `typeclass`. Aquí está parte de su definición:

class Monad m where
(>>=) :: m a -> (a -> m b) -> m b

Donde `>>=` es así:

1. >>= recibe una Mónada, como (Just 3)
2. Y una función que devuelve una Mónada (como la función `half`)
3. Y devuelve una Mónada

Así que `Maybe` es una Mónada

instance Monad Maybe where
Nothing >>= func = Nothing
Just val >>= func = func val

¡Y aquí al vemos en acción con un “Just 3”!

1. Bind desenvuelve el valor
2. Le pasa ese valor a la función
3. Devuelve un valor envuelto (ya sea un `Just` o un `Nothing`)

Y si le pasas un `Nothing` es incluso más sencillo:

1. Nada entra
2. No se hace nada
3. Nada sale

También puedes encadenar estas llamadas

> Just 20 >>= half >>= half >>= half
Nothing

¡Mola! Así que ahora sabemos que `Maybe`es un `Funtor`, un `Applicativo` y una `Mónada`.

Ahora vamos a dar un paseito tranquilamente por otro ejemplo, la Mónada IO (de input/output = Entrada/Salida en inglés)

Más específicamente tres funciones:

- `getLine`: No recibe ningún argumento y lee los datos dados por el usuario:

getLine :: IO String

- `readFile`: Toma una cadena de texto (una ruta a un fichero) y devuelve el contenido del fichero.

readFile :: FilePath -> IO String

- `putStrLine`: Recibe una cadena de texto y lo imprime:

putStrLn :: String -> IO ()

Las tres funciones toman un valor normal (o incluso ningún valor) y devuelve uno envuelto. ¡Las podemos enlazar usando `>>=`!

1. Lee los datos del usuario
2. Usalos para leer un fichero
3. ¡Imprime el contenido del fichero!

getLine >>= readFile >>= putStrLn

¡Oh yeah baby! !Buen asiento para ver el espectáculo de las Mónadas!

Haskell viene también con una construcción para las Mónadas, llamada `do-notation`


foo = do
filename <- getLine
contents <- readFile filename
putStrLn contents

Esto es equivalente a lo anterior.

Conclusión

  1. Un Funtor es un tipo de datos que implementa la `typeclass` `Functor`
  2. Un Aplicativo es un tipo de datos que implementa la `typeclass` `Applicative`
  3. Una Mónada es un tipo de datos que implementa la `typeclass` `Monad`
  4. Un `Maybe` implementa los tres, por lo que es un Funtor, un Applicativo y una Mónada

¿Cuál es la diferencia entre las 3?

- Funtor
- Aplicativo
- Mónada

  • Funtores: Le aplicas la función a un valor envuelto con `fmap` o `<$>`
  • Aplicativos: Le aplicas una función envuelta a un valor envuelto usando `<*>` o `liftA`
  • Mónadas: Le aplicas una función que devuelve un valor envuelto, a un valor envuelto a su vez, utilizando `>>=` o `liftM`

Así que, queridísimo amigo (creo que ya somos amigos llegados a este punto), creo que ambos estaremos de acuerdo al pensar que las Mónadas son fáciles y una IDEA INTELIGENTE(tm). Ahora que te has probado un poco del pastel con esta guía, por qué no ir de héroe y comerte la tarta entera. échale un ojo a la sección de Mónadas de “Learn you a Haskell for Great Good”. Hay un montón de cosas que yo he pasado por encima porque el autor, Miran Lipovaca, hace un gran trabajo hablando en profundidad sobre el asunto.

--

--