Autor: Aditya Bhargava
Oryginalny post: Functors, Applicatives, And Monads In Pictures
Mamy prostą wartość (ang. value):

I wiemy, jak zastosować do tej wartości funkcję:

Dość proste. Powiedzmy więc, że wartość może być umieszczona w kontekście. Za kontekst przyjmijmy pudełko, do którego została zapakowana:

Teraz wywołując funkcję otrzymamy różne wyniki w zależności od kontekstu. To na tej idei oparte są funktory (ang. functor, w Haskellu Functor), funktory aplikatywne (ang. applicative functor, Applicative), monady (ang. monad, Monad) itp. Typ danych Maybe (pol. [być] może) definiuje dwa powiązane konteksty:

Za chwilę zorientujemy się jak różny jest wynik wywołania w zależności od tego, czy argumentem jest Just a czy Nothing. Jednak najpierw wspomnijmy o funktorach.
Gdy wartość jest zapakowana w kontekst, nie można na niej wykonać normalnej funkcji:

W takim przypadku przydaje się fmap, bo jest na bieżąco z kontekstami. fmap wie, jak wywoływać funkcje na argumentach opakowanych w kontekst. Dla przykładu żałóżmy, że chcemy wywołać (+3) na Just 2. Korzystając z fmap:
> fmap (+3) (Just 2)
Just 5

Bum! fmap pokazał nam jak to się robi! Ale skąd wie, jak wywołać funkcję?
Functor?Functor jest typeklasą. Oto jego definicja:

fmap będzie na nim operował.Functorem jest każdy typ danych, który definiuje, w jaki sposób operuje na nim fmap. Oto jak działa fmap:

(+3))Just 2),Just 5).Możemy więc zrobić tak:
> fmap (+3) (Just 2)
Just 5
I fmap magicznie wykonuje funkcję, bo Maybe jest Functorem. Specyfikuje, w jaki sposób fmap stostuje funkcję wobec Just–ów i Nothing–ów:
instance Functor Maybe where
fmap func (Just val) = Just (func val)
fmap func Nothing = Nothing
Oto co dzieje się za kulisami, gdy piszemy fmap (+3) (Just 2):

A co, gdybyśmy kazali fmapowi zastosować (+3) na Nothing?

> fmap (+3) Nothing
Nothing

Nic (nie) wchodzi, nic (nie) wychodzi
Tego nie ogarniesz!
Jak Morfeusz w Matrixie, fmap po prostu wie, co trzeba robić. Zaczynasz z Nothing i kończysz z Nothing! Staje się jasne, dlaczego powstał typ Maybe. Dla przykładu, w języku bez Maybe można by pracować z rekordem bazy danych w poniższy sposób:
post = Post.find_by_id(1)
if post
return post.title
else
return nil
end
Jednak w Haskellu można tak:
fmap (getPostTitle) (findPost 1)
Jeśli findPost znajdzie posta, dostaniemy jego tytuł z getPostTitle. Natomiast jeśli zwróci Nothing, dostaniemy Nothing! Nieźle, co? <$> jest infiksową wersją fmap, więc częściej możesz spotkać taką konstrukcję:
getPostTitle <$> (findPost 1)
Weźmy inny przykład. Co stanie się, gdy wywołamy funkcję na liście?

Listy też są funktorami. Oto definicja:
instance Functor [] where
fmap = map
No, to jeszcze ostatni przykład. Co stanie się, gdy do funkcji przekażemy inną funkcję?
fmap (+3) (+1)
Tu funkcja:

A tu funkcja wywołana z funkcją jako argumentem:

Wynikiem jest po prostu inna funkcja!
> import Control.Applicative
> let foo = fmap (+3) (+2)
> foo 10
15
A więc funkcje też są Functorami!
instance Functor ((->) r) where
fmap f g = f . g
Używając fmap na funkcji, tak naprawdę po prostu komponujesz funkcje!
Funktory aplikatywne (Applicative) wchodzą na nieco wyższy poziom. Podobnie jak ze zwyczajnymi funktorami (Functor), nasze wartości są opakowane w kontekst:

Jednak także nasze funkcje także są opakowane w kontekst!

Tak, niech to zapadnie w pamięć. Funktory aplikatywne się nie patyczkują. Control.Applicative definiuje <*>, który wie, jak zastosować opakowaną w kontekst funkcję do zapakowanej wartości:

To jest:
Just (+3) <*> Just 2 == Just 5
Użycie <*> może doprowadzić do interesujących sytuacji, np.:
> [(*2), (+3)] <*> [1, 2, 3]
[2, 4, 6, 4, 5, 6]

A teraz coś, czego nie można zrobić zwykłym funktorem, ale można funktorem aplikatywnym. Jak wykonać funkcję dwóch zmiennych na dwóch zapakowanych wartościach?
> (+) <$> (Just 5)
Just (+5)
> Just (+5) <$> (Just 4)
ERROR ??? CO TO W OGÓLE MA ZNACZYĆ CZEMU FUNKCJA JEST ZAPAKOWANA W JUST
Funktor aplikatywny:
> (+) <$> (Just 5)
Just (+5)
> Just (+5) <*> (Just 3)
Just 8
Applicative odsuwa Functor na bok. “Duzi chłopcy mogą używać funkcji z dowolną liczbą argumentów” – mówi. “Uzbrojony w <$> i <*> mogę wziąć dowolną funkcję oczekującą dowolną liczbę nieopakowanych wartości. Przekazuję jej wszystkie wartości zapakowane i otrzymuję zapakowany wynik! AHAHAHAHAH!”
> (*) <$> Just 5 <*> Just 3
Just 15
Jest też funkcja nazwana liftA2, która robi dokładnie to samo:
> liftA2 (*) (Just 5) (Just 3)
Just 15
Jak nauczyć się o monadach:
Monady dodają coś jeszcze.
Funktory wykonują funkcję na opakowanej wartości:

Funktory aplikatywne wykonują opakowaną funkcję na opakowanej wartości:

Monady wykonują funkcję, która zwraca opakowaną wartość na opakowanej wartości. Monady mają w tym celu funkcję >>= (czyt. “bind”).
Zobaczmy przykład. Znane nam Maybe jest monadą:

Przyjmijmy, że half jest funkcją działającą tylko na liczbach parzystych:
half x = if even x
then Just (x `div` 2)
else Nothing

Co gdybyśmy podali jej zapakowaną wartość?

Musimy użyć >>=, żeby wepchnąć naszą zapakowaną wartość do tej funkcji. Oto zdjęcie >>=:

A oto jak działa:
> Just 3 >>= half
Nothing
> Just 4 >>= half
Just 2
> Nothing >>= half
Nothing
Co dzieje się w środku? Monad (monada) jest kolejną typeklasą. Oto część jej definicji:
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
Gdzie >>= to:

>>= bierze monadę (jak Just 3)half)Więc Maybe jest monadą:
instance Monad Maybe where
Nothing >>= func = Nothing
Just val >>= func = func val
A tutaj w akcji z Just 3!

>>= (czyt. “bind”) odpakowuje wartość,A gdy przkażesz na wejściu Nothing, jest jeszcze prościej:

Wywołania można też połączyć w łańcuch:
> Just 20 >>= half >>= half >>= half
Nothing


Nieźle! Już wiemy, że Maybe jest Functorem, Applicativem i Monadą. Teraz przejdźmy do innego przykładu: monady IO:

Skupimy się na trzech funkcjach. getLine nie bierze żadnych argumentów i pobiera wejście od użytkownika:

getLine :: IO String
readFile bierze ciąg znaków (nazwę pliku) i zwraca zawartość pliku:

readFile :: FilePath -> IO String
putStrLn bierze ciąg znaków i wypisuje go:

putStrLn :: String -> IO ()
Wszystkie te trzy funkcje biorą zwyczajną wartość (bądź żadną) i zwracają opakowaną wartość. Możemy wywołać je wszystkie w łańcuchu korzystając z >>=!

getLine >>= readFile >>= putStrLn
O, tak! Pierwszy rząd na występach monad!
Haskell dostarcza także cukier składniowy do operacji na monadach, zwany do notation, notacją do:
foo = do
filename <- getLine
contents <- readFile filename
putStrLn contents
Functor.Applicative.Monad.Maybe implementuje wszystkie te trzy, jest więc funktorem, funktorem aplikatywnym i monadą.Jakie są między nimi różnice?

fmap bądź <$>.<*> bądź liftA.>>= bądź liftM.Więc, drogi kumplu (myślę, że na tym etapie jesteśmy już kumplami), myślę, że zgodzimy się oboje, że monady są łatwe i że są DOBRYM POMYSŁEM™. Skoro już napociłeś się czytając ten przewodnik, czemu by nie pójść na całość? Przejrzyj rozdział o monadach w Learn You A Haskell (ang). Jest wiele rzeczy, które pominąłem, bo Miran świetnie wgłębia się w ten temat.
Od tłumacza: a jeśli masz jeszcze chwilkę, może zainteresują Cię moje projekty. ☺