Functors, Applicatives e Monads explicados com desenhos

Juliano Alves
Dec 6, 2016 · 9 min read

Se você trabalha desenvolvendo software, deve conhecer a pergunta do momento: O que são Monads? A resposta mais didática e divertida que encontrei está no post Functors, Applicatives, And Monads In Pictures. Há traduções desse post em várias línguas, então decidi fazer a tradução para português.

Esse é um valor simples:

Image for post
Image for post

E sabemos como aplicar uma função nesse valor:

Image for post
Image for post

Bem simples. Vamos expandir esse exemplo dizendo que qualquer valor pode estar em um contexto. Por enquanto, pense no contexto como uma caixa onde colocamos um valor dentro:

Image for post
Image for post
Valor e contexto

Agora quando aplicarmos uma função nesse valor, teremos resultados diferentes dependendo do contexto. Essa é a idéia na qual Functors, Applicatives, Monads, Arrows e etc são baseados. O tipo Maybe (que significa Talvez) define dois contextos relacionados: Nothing (Nada) e Just a (que significa "Simplesmente a" onde a é um elemento que nós definimos, como um número ou uma String):

Image for post
Image for post
data Maybe a = Nothing | Just a

Veremos em um segundo como aplicar funções é diferente quando algo é um Just a ou um Nothing. Mas primeiro, vamos falar de Functors!

Functors

Quando um valor está envolvido por um contexto, você não pode aplicar uma função comum nele:

Image for post
Image for post

Aqui é onde fmap entra. fmap é malandro, fmap manja dos paranauês. fmap sabe como aplicar funções em valores que estão envolvidos por contextos. Como exemplo, vamos aplicar (+3) a Just 2. Usando fmap:

> fmap (+3) (Just 2)
Just 5
Image for post
Image for post

Rá! fmap mostrou como se faz! Mas comofmap sabe como aplicar a função?

Na real, o que é um Functor?

Functor é uma typeclass. Essa é a definição:

Image for post
Image for post

1. Para fazer do tipo ‘f’ um Functor,
2. Esse tipo precisa definir como fmap vai trabalhar com ele

Um Functor é qualquer tipo que define como aplicar fmap a ele mesmo. É assim quefmap funciona:

Image for post
Image for post

1. fmap recebe uma função, como (+3)
2. e um Functor, como Just 2
3. e retorna um novo Functor, como Just 5

De forma que podemos fazer isto:

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

E fmap magicamente aplica a função porque Maybe é um Functor. Ele especifica como aplicar fmap em Justs e Nothings:

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

Isso é o que acontece por baixo dos panos quando escrevemos

fmap (+3) (Just 2)
Image for post
Image for post

1. Extrai valor do contexto
2. Aplica a função
3. Envolve o valor em um contexto novamente

Daí você vira e manda, seguinte fmap, tem como aplicar (+3) aí noNothing?

Image for post
Image for post

1. Não há valor
2. Não aplica a função
3. Acaba sem nada

> fmap (+3) Nothing
Nothing

Assim como Morpheus em Matrix, fmap sabe o que tem que fazer; começa comNothing, e termina comNothing! fmap é zen. Agora a existência do tipo Maybe faz sentido. Por exemplo, é assim que se trabalha com registros em base de dados numa linguagem que não possui Maybe:

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

Mas em Haskell:

fmap (getPostTitle) (findPost 1)

Se findPost retorna um post, nós obtemos o título comgetPostTitle. Se ele retornarNothing, nós retornamosNothing! Maneiro, hein? <$> é a versão infixa defmap, de forma que podemos usá-lo assim:

getPostTitle <$> (findPost 1)

Outro exemplo: o que acontece quando aplicamos a função numa lista?

Image for post
Image for post

1. Um array de valores
2. Aplica a função em cada valor
3. Um novo array de valores

Listas também são functors! Essa é a definição:

instance Functor [] where
fmap = map

Certo, certo, último exemplo: o que acontece quando aplicamos uma função em outra função?

fmap (+3) (+1)

Essa é uma função:

Image for post
Image for post

1. Recebe um valor
2. Retorna um valor

Essa é uma função sendo aplicada a outra função:

Image for post
Image for post

O resultado é simplesmente outra função!

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

Então funções também são Functors!

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

Quando aplicamos fmap em outra função, estamos fazendo composição de funções!

Applicatives

Applicatives nos leva para o próximo level. Com um applicative, nossos valores são envolvidos por um contexto, exatamente como Functors:

Image for post
Image for post

Mas nossas funções são envolvidas por um contexto também!

Image for post
Image for post

Eu sei. Aceita, só aceita. Applicatives não estão de brincadeira. Control.Applicative define <*>, que sabe como aplicar uma função envolvida por um contexto a um valor envolvido por um contexto:

Image for post
Image for post

1. Função envolvida por um contexto
2. Valor envolvido por um contexto
3. Retira ambos dos contextos e aplica a função ao valor
4. Novo valor em um novo contexto

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

Usar <*> pode levar a situações interessantes. Por exemplo:

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

Isso é algo que pode ser feito com Applicatives que não pode ser feito com Functors. Como se aplica uma função que recebe dois argumentos em dois valores envoltos por contextos?

> (+) <$> (Just 5)
Just (+5)
> Just (+5) <$> (Just 4)
ERRO??? COMO ASSIM??? POR QUÊ A FUNÇÃO ESTÁ ENVOLVIDA POR UM JUST???

Applicatives:

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

Applicatives colocam umFunctors de lado. “Somente adultos podem usar funções com qualquer número de argumentos, garoto” dizem os Applicatives. “Armado com <$> e <*>, posso lidar com funções que recebem qualquer número de valores sem contextos. Daí eu passo todos os valores envolvidos e recebo um valor envolvido como retorno! HUEHUEHUE!”

> (*) <$> Just 5 <*> Just 3
Just 15

E olha! Tem uma função chamadaliftA2 que faz a mesma coisa:

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

Monads

Como aprender Monads:

  1. Consiga um doutorado em Ciência da Computação.
  2. Joga fora porque não será necessário para entender essa seção!

Monads adicionam um toque especial.

Functors aplicam funções em valores envolvidos:

Image for post
Image for post

Applicatives aplicam uma função envolvida em um valor envolvido:

Image for post
Image for post

Monads aplicam uma função que retorna um valor envolvido em um valor envolvido. Monads possuem uma função>>= (chamada “bind”) que faz isso.

Vamos ver um exemplo. Nossa amigaMaybe é uma monad:

Image for post
Image for post
Apenas uma monad relaxando

Supondo que half é uma função que trabalha apenas com números pares:

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

- Recebe um valor
- Retorna um valor envolvido

O que acontece se passarmos um valor envolvido?

Image for post
Image for post

Precisamos usar >>= para empurrar nosso valor envolvido na função. Aqui temos uma foto do >>=:

Image for post
Image for post

Funciona assim:

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

O que acontece por baixo dos panos? Monad é outra typeclass. Essa é uma definição parcial:

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

Onde >>= é:

Image for post
Image for post

1. >>= recebe una Monad, como Just 3
2. e uma função que retorna uma Monad (como a função half)
3. e retorna uma Monad

Então Maybe é uma Monad:

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

Vamos vê-la em ação comJust 3!

Image for post
Image for post

1. Bind extrai o valor
2. Passa o valor extraído para a função
3. Retorna um valor envolvido (que pode ser um Just ou um Nothing)

E se passamos um Nothing é ainda mais simples:

Image for post
Image for post

1. Nada entra
2. Não faz nada
3. Nada sai

Também podemos encadear essas chamadas:

> Just 20 >>= half >>= half >>= half
Nothing
Image for post
Image for post
Image for post
Image for post

Coisa linda! Agora sabemos queMaybe é umFunctor, um Applicative, e umaMonad.

Agora vamos dar um rolêzinho em outro exemplo: a IO monad (de input/output = Entrada/Saída em inglês):

Image for post
Image for post

Mais especificamente três funções. getLine não recebe argumentos e lê a entrada de dados do usuário:

Image for post
Image for post
getLine :: IO String

readFile recebe uma string (o nome de um arquivo) e retorna o conteúdo daquele arquivo:

Image for post
Image for post
readFile :: FilePath -> IO String

putStrLn recebe uma string e a imprime:

Image for post
Image for post
putStrLn :: String -> IO ()

As três funções recebem valores comuns (ou nenhum valor) e retornam um valor envolvido. Podemos encadear todas elas com >>=!

Image for post
Image for post

1. Lê os dados do usuário
2. Usa-os para ler um arquivo
3. Imprime o conteúdo do arquivo!

getLine >>= readFile >>= putStrLn

Oh yeah! Ótimo lugar para assistir o espetáculo das Monads!

Haskell também vem com um açúcar sintático para monads, chamado do-notation:

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

Conclusão

  1. Um functor é um tipo que implementa Functor typeclass.
  2. Um applicative é um tipo que implementa Applicative typeclass.
  3. Uma monad é um tipo que implementa Monad typeclass.
  4. Um Maybe implementa todos os três, então é um functor, um applicative, e uma monad.

E qual é a diferença entre os três?

Image for post
Image for post
  • functors: aplicam uma função em um valor envolvido usando fmap ou <$>
  • applicatives: aplicam uma função envolvida em um valor envolvido usando <*> or liftA
  • monads: aplicam uma função que retorna um valor envolvido em um valor envolvido usando >>= or liftM

Então, querido amigo(creio que somos amigos nesse ponto), acho que nós dois concordamos que monads são simples e UMA BOA IDÉIA®. Agora que você molhou o bico com esse guia, por quê não beber a garrafa toda? Dê uma olhada na seção de Monads do “Learn you a Haskell for Great Good. Há vários tópicos que eu apenas passei rapidamente porque a autora, Miran Lipovaca fez um grande trabalho indo fundo nesse assunto.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app