Esta é uma tradução do excelente post feito por Aditya Bhargava. O post original pode ser lido aqui.
Ao traduzir o post realizei algumas escolhas, tais como:
- manter os nomes dos conceitos em inglês, exemplo: Functors(Funtores) e Monads.
- Algumas expressões não fazem sentido se forem traduzidas literalmente para o português, outras perdem o contexto cultural quando você às trás para o Brasil. Por exemplo, Aditya cita Mel Gibson no texto(acredito eu) pelo uso excessivo de álcool. Portanto acredito que um substituto para ele seja Zecá Pagodinho, me processe
Se você encontrar qualquer erro na tradução e você encontrará, não utilizei corretores ortográficos, não se acanhe em enviar um pull request.
A imagem abaixo representa um valor simples
E você sabe como aplicar uma função(ex: adicione +3) cujo o argumento é o valor acima,
Muito simples! Uma generalização para o processo acima é dizer que qualquer valor pode estar dentro de um contexto. Um recurso didático é imaginar que o contexto é uma caixa na qual você pode colocar valores dentro
Agora, quando você aplica uma função a este valor você receberá diferentes resultados dependendo do contexto em que o resultado está inserido. Esta é a ideia que serve de alicerce para Functors, Applicatives, Monads, Arrows(veja morfismos) etc. Em se tratando de contextos, o tipo Maybe
define dois contextos
data Maybe a = Nothing | Just a
Posteriormente eu lhe mostrarei as diferenças quando se aplica uma função em algo que é um Just
ou Nothing
, mas antes disso vamos conversar um pouco sobre Functors!
Quando um valor está envolto por um contexto ele não permite a aplicação de uma função "ordinária" à ele
Aqui é onde o fmap
surge. O fmap
tem consciência do contexto. O fmap
sabe como atuar funções em valores
que estão envoltos por um contexto. Por exemplo, suponha que você queira
aplicar (+3)
em Just 2
. Usando fmap
isso é fácil
> fmap (+3) (Just 2)
Just 5
Bum! fmap
mostra seu valor. Mas como fmap
sabe como deve aplicar uma função?
Um Functor é chamado de typelcass. A figura a baixo apresenta a definição
Um functor é qualquer tipo de dado que define como fmap
se aplicará a ele. Aqui, como o fmap
funciona:
Então podemos fazer o seguinte
> fmap (+3) (Just 2)
Just 5
E fmap
magicamente aplica isto (Just 2
) a função, pois Maybe
é um Functor. A imagem abaixo especifica como fmap
deve atuar em Just
's e Nothing
's:
instance Functor Maybe where
fmap func (Just val) = Just (func val)
fmap func Nothing = Nothing
O que acontece quando escrevemos fmap +(3) (Just 2)
Você gostou? Certo fmap
, então aplique (+3)
em um Nothing
> fmap (+3) Nothing
Nothing
Bill O’Reilly sendo ignorante sobre Maybe
(Não conhecia esse cara até traduzir esse post, não confunda com Tim O'Reilly)
Assim como Morfeu em Matrix fmap
sabe o que tem que ser feito. Se você começa com Nothing
então ele retorna Nothing
. fmap
segue o modo zen. Agora faz sentido o porquê da existência do tipo Maybe
. Por exemplo, vamos começar a trabalhar com um banco de dados em uma linguagem(nesse caso, ruby) sem 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
retornar um post, o código acima retornará o título do post através de getPostTitle
. Se findPost
retornar um Nothing
, no final ainda teremos Nothing
! Você precisa concordar que isto é uma forma bem mais organizada de trabalhar do que utilizar código em ruby.
No código podemos utilizar <$>
, que é uma versão infix de fmap
, então comumente você poderá encontrar códigos em haskell escritos da seguinte maneira
getPostTitle <$> (findPost 1)
Aqui em baixo segue um outro exemplo: o que acontece quando você aplica uma função em uma lista?
Listas são funtores também! Segue uma definição:
instance Functor [] where
fmap = map
Okay, okay, um último exemplo: o que acontece quando você aplica uma função em outra função?
fmap (+3) (+1)
Aqui a função:
Aqui a função aplicada em outra função:
O resultado é outra função!
> import Control.Applicative
> let foo = fmap (+3) (+2)
> foo 10
15
Então funcções são funtores também!
instance Functor ((->) r) where
fmap f g = f . g
Quando você usa fmap
em uma função, você está apenas realizando uma composição de funções!
Applicatives nos levam ao próximo nível. Com um applicative nossos valores são envoltos por contextos assim como Functors
Mas é importante notar que no caso de applicatives nossas funções também são envoltas por contextos!
Applicatives não estão para brincadeira. Control.Applicative
define um operador <*>
, o qual sabe como aplicar uma função envolta por um contexto em um valor envolto por um contexto
i.e:
> Just (+3) <*> Just 2
Just 5
O uso de <*>
pode nos retornar situações deveras interessantes. Por exemplo:
> [(*2), (+3)] <*> [1, 2, 3]
[2, 4, 6, 4, 5, 6]
Abaixo segue uma coisa que você consegue fazer com Applicatives, mas não consegue fazer com Functors.
Como aplicar uma função que pega dois argumentos para dois valores envoltos em um contexto?
> (+1) <$> (Just 5)
Just (+6)
> Just (+6) <$> (Just 4)
ERRO!!
Applicatives:
> Just (+6) <*> (Just 3)
Just 8
Applicatives colocam Functors de lado.
"Adultos podem usar funções com qualquer número de argumentos"-Applicative
"Armado com <$>
and <*>
, eu posso pegar qualquer função que possui qualquer quantidade de argumentos que não estão envoltos por contextos. Então, eu coloco esses argumentos dentro de contextos, e finalmente eu retorno como saída um valor envolto por contexto huehehuuhe!"-Applicative
> (*) <$> Just 5 <*> Just 3
Just 15
Ei! Existe uma função chamada liftA2
que faz
a mesma coisa:
> liftA2 (*) (Just 5) (Just 3)
Just 15
Como aprender sobre Monads:
- Obtenha um título de Doutor em Ciência da Computação
- Jogue ele fora por que você não precisará dele nesta secção!
Monads estão em alta. Functors aplicam funções a valores dentro de contextos:
Applicative aplicam funções que estão dentro de contextos em valores que por sua vez também estão dentro de outros contextos:
Monads aplicam funções em valores envoltos por contextos e retornam valores envoltos por contextos. A maquinaria das Monads em haskell é representada pela função >>=
(“bind”).
Vejamos um exemplo. Good ol’ Maybe
is a monad:
Suponha que half
é uma função que funciona unicamente com números pares
half x = if even x
then Just (x `div` 2)
else Nothing
O que acontece se alimentarmos tal função com um valor envolto por um contexto?
Nos precisamos utilizar >>=
em nosso valor com contexto para expulsá-lo do contexto, precisamos retirá-lo da caixa.
Aqui uma foto representando >>=
Aqui como ele funciona
> Just 3 >>= half
Nothing
> Just 4 >>= half
Just 2
> Nothing >>= half
Nothing
Mas o que aconteceu dentro da nossa caixa? Monad é um outro typeclass
. Segue uma definição parcial de uma Monad
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
Where >>= is:
Então uma Maybe
Monad é
instance Monad Maybe where
Nothing >>= func = Nothing
Just val >>= func = func val
Aqui a ação desta monad em Just 3
!
E se você passar um Nothing
a coisa fica mais simples ainda
Você também pode colocar várias dessas ações em cadeia
> Just 20 >>= half >>= half >>= half
Nothing
Muito legal! Agora vamos nos mexer um pouco e partir para um outro exemplo: a IO monad
Especifiquemos três funções.
getLine
pega os argumentos que são fornecidos por um input do usuário
getLine :: IO String
readFile
pega uma string(que representa o nome do arquivo) e retorna o conteúdo do arquivo cujo nome é essa string
readFile :: FilePath -> IO String
putStrLn :: String -> IO ()
Todas essas três funções pegam um valor comum(ou nenhum valor) e retornam um valor dentro de um contexto. Nos podemos encadear todas essas ações utilizando >>=
!
getLine >>= readFile >>= putStrLn
Aw yeah! Acomodem-se em suas cadeiras para o show da Monad! Haskell também nos fornece uma sintaxe sucinta para monads, chamada do notation
:
foo = do
filename <- getLine
contents <- readFile filename
putStrLn contents
- Functor é um tipo que implementa um Functor typeclass.
- Applicative é um tipo que implementa um Applicative typeclass.
- Monad é um tipo que implementa um Monad typeclass.
- Maybe implenta todos os outros, então é um functor, um applicative e um monad.
Qual é a diferença entre os três?
- functors: você aplica uma função em um valor envolto por um contexto utilizando
fmap
ou<$>
- applicatives: você aplica uma função envolta por um contexto em um valor envolto por um contexto usando
<*>
ouliftA
- monads: você pega um valor envolto por um contexto, extrai ele, aplica uma função, e retorna um valor envolto por um contexto, para isso você pode usar
>>=
ouliftM
Então, querido amigo (Eu penso que neste ponto já posso considerar que somos amigos), eu penso que ambos concordamos que monads são faceis além de serem uma ideia muito inteligente.
Agora que você saboreou esse guia por que não convidar o Zeca Pagodinho para entornar uma cerveja e estudar toda uma secção sobre monads? Tal secção está disponível aqui. Existem muitas coisas que eu passei por cima, pois Miran Lipovaca(criador do Learn You a Haskell) fez um grande trabalho o qual se aprofundanda em monads e muito outros temas, tal trabalho esta disponível gratuitamente neste link : http://learnyouahaskell.com/chapters