Functors, Applicatives e Monads explicados com desenhos

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:

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

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:

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 (que significa Talvez) define dois contextos relacionados: (Nada) e (que significa "Simplesmente a" onde é um elemento que nós definimos, como um número ou uma String):

data Maybe a = Nothing | Just a

Veremos em um segundo como aplicar funções é diferente quando algo é um ou um . 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:

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

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

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

Na real, o que é um Functor?

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

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

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

1. recebe uma função, como
2. e um Functor, como
3. e retorna um novo Functor, como

De forma que podemos fazer isto:

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

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

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)

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

Daí você vira e manda, seguinte , tem como aplicar aí no?

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, sabe o que tem que fazer; começa com, e termina com! é zen. Agora a existência do tipo faz sentido. Por exemplo, é assim que se trabalha com registros em base de dados numa linguagem que não possui :

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

Mas em Haskell:

fmap (getPostTitle) (findPost 1)

Se retorna um post, nós obtemos o título com. Se ele retornar, nós retornamos! Maneiro, hein? é a versão infixa de, de forma que podemos usá-lo assim:

getPostTitle <$> (findPost 1)

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

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:

1. Recebe um valor
2. Retorna um valor

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

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 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:

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

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

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]

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

colocam um 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 chamada 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:

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

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 amiga é uma monad:

Apenas uma monad relaxando

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

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

- Recebe um valor
- Retorna um valor envolvido

O que acontece se passarmos um valor envolvido?

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

Funciona assim:

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

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

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

Onde é:

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

Então é uma Monad:

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

Vamos vê-la em ação com!

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

E se passamos um é ainda mais simples:

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

Também podemos encadear essas chamadas:

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

Coisa linda! Agora sabemos que é um, um , e uma.

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

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

getLine :: IO String

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

readFile :: FilePath -> IO String

recebe uma string e a imprime:

putStrLn :: String -> IO ()

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

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 :

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

Conclusão

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

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

  • functors: aplicam uma função em um valor envolvido usando ou
  • applicatives: aplicam uma função envolvida em um valor envolvido usando or
  • monads: aplicam uma função que retorna um valor envolvido em um valor envolvido usando or

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.

Software Craftsman, Polyglot Developer, Agilist, Climber and Hardcore Gamer. Twitter: @vonjuliano

Software Craftsman, Polyglot Developer, Agilist, Climber and Hardcore Gamer. Twitter: @vonjuliano