Функторы, аппликативные функторы и монады в картинках

Итак, у нас есть число:


И мы знаем, как к этому числу можно применить функцию:


Достаточно просто. А теперь усложним задание — пусть наше значение имеет контекст. Пока что вы можете думать о контексте просто как о ящике, куда можно положить значение:


Теперь, применяя функцию к значению, вы будете получать разные значения в зависимости от контекста. Это основная идея, на которой базируются функторы, аппликативные функторы, монады, стрелки и т.п. Тип данных Maybe определяет два связанных контекста:


data Maybe a = Nothing | Just a 


Позже мы увидим, как изменяется применение функции, когда что-то является Just a vs Nothing. Но сначала давайте поговорим о функторах!


Функторы


Когда у вас есть значение, обёрнутое в контекст, вы не можете просто взять и применить к нему обычную функцию:


И здесь fmap спешит на помощь. fmap — парень с улицы, fmap знает толк в контекстах. Он в курсе, как применить функцию к значению, обёрнутому в контекст. Например, предположим, что вы хотите применить (+3) к Just 2. Используйте fmap:

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





Бам! fmap показал нам, как это делается! Но откуда он знает, как правильно применять функцию?


Так что такое функтор на самом деле?


Функтор — это тип классов. Вот его определение:


Функтором является любой тип данных, для которого определено, как к нему применяется fmap. А вот как fmap работает:


Так что мы можем делать так:

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



И fmap магическим образом применит эту функцию, потому что Maybe является функтором. В нём определено, как применять функции к Just‘ам и Nothing‘ам:

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



Вот что происходит за сценой, когда мы пишем fmap (+3) (Just 2):


А потом вы скажете: «Ладно, fmap, а примени-ка, пожалуйста, (+3) к Nothing


> fmap (+3) Nothing Nothing 




Билл О’Рейли ничегошеньки не смыслит в Maybe функторе

Как Морфеус в «Матрице», fmap знает, что делать; вы начали с Nothing и закончите тоже с Nothing! Это fmap-дзен. И теперь понятно, для чего вообще существует тип данных Maybe. Вот, например, как бы вы работали с записью в базе данных на языке без Maybe:

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



На Haskell же:

fmap (getPostTitle) (findPost 1) 



Если findPost возвращает пост, то мы выдаём его заголовок с помощью getPostTitle. Если же он возвращает Nothing, то и мы возвращаем Nothing! Чертовски изящно, а?
<$> — инфиксная версия fmap, так что вместо кода выше вы частенько можете встретить:

getPostTitle <$> (findPost 1) 



А вот ещё один пример: что происходит, когда вы применяете функцию к списку?


Списки тоже функторы! Вот определение:

instance Functor [] where     fmap = map 



Ладно, ладно, ещё один (последний) пример: что случится, когда вы примените функцию к другой функции?

fmap (+3) (+1) 



Вот эта функция:


А вот функция, применённая к другой функции:


Результат — просто ещё одна функция!

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



Так что функции — тоже функторы!

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



И когда вы применяете fmap к функции, то попросту делаете композицию функций!


Аппликативные функторы


Следующий уровень — аппликативные функторы. С ними наше значение по-прежнему обёрнуто в контекст (так же как с функторами):


Но теперь в контекст обёрнута и наша функция!


Ага! Давайте-ка нырнём поглубже. Аппликативные функторы надувательством не занимаются. Control.Applicative определяет <*>, который знает, как применить функцию, обёрнутую в контекст, к значению, обёрнутому в контекст:


Т.е.

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



Использование <*> может привести к некоторым интересным ситуациям. Например:

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





А вот кое-что, что вы можете сделать с аппликативными функторами, но не сможете с обычными. Как вы примените функцию, которая принимает два аргумента, к двум обёрнутым значениям?

> (+) <$> (Just 5) Just (+5) > Just (+5) <$> (Just 4) ОШИБКА??? ЧТО ЭТО ВООБЩЕ ЗНАЧИТ ПОЧЕМУ ФУНКЦИЯ ОБЁРНУТА В JUST 



Аппликативные функторы:

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



Applicative технично отодвигает Functor в сторону. «Большие парни могут использовать функции с любым количеством аргументов,» — как бы говорит он. — «Вооружённый <$> и <*>, я могу взять любую функцию, которая ожидает любое число необёрнутых аргументов. Затем я передам ей все обёрнутые значения и получу обёрнутый же результат! БВАХАХАХАХАХА!»

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




Аппликативный функтор наблюдает за тем, как обычный применяет функцию

И да! Существует функция liftA2, которая делает тоже самое:

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




Монады


Как изучать монады:

  1. Получить корочки PhD в Computer Science
  2. Выкинуть их нафиг, потому что при чтении этого раздела они вам не понадобятся!


Монады добавляют новый поворот в нашем сюжете.
Функторы применяют обычную функцию к обёрнутому значению.


Аппликативные функции применяют обёрнутую функцию к обёрнутому значению


Монады применяют функцию, которая возвращает обёрнутое значение, к обёрнутому значению. У монад есть функция >>= (произносится «bind»), позволяющая делать это.
Рассмотрим такой пример: наш старый добрый Maybe — это монада:

Просто болтающаяся монада

Пусть half — функция, которая работает только с чётными числами:

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





А что, если мы скормим ей обёрнутое значение?


Нам нужно использовать >>=, чтобы пропихнуть обёрнутое значение через функцию. Вот фото >>=:


А вот как она работает:

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



Что же происходит внутри? Monad — ещё один класс типов. Вот его частичное определение:

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



Где >>=:


Так что Maybe — это монада:

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



Вот какие действия происходят над бедным Just 3!


А если вы подадите на вход Nothing, то всё ещё проще:


Можно так же связать цепочку из вызовов:

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






Клёвая штука! И теперь мы знаем, что Maybe — это Functor, Applicative и Monad в одном лице.
А сейчас давайте переключимся на другой пример: IO монаду:


В частности, на три её функции. getLine не принимает аргументов и получает пользовательские данные с входа:


getLine :: IO String 



readFile принимает строку (имя файла) и возвращает его содержимое:


readFile :: FilePath -> IO String 



putStrLn принимает строку и печатает её:


putStrLn :: String -> IO () 



Все три функции принимают регулярные значения (или вообще не принимают значений) и возвращают обёрнутые значения. Значит, мы можем связать их с помощью >>=!


getLine >>= readFile >>= putStrLn 



О да, у нас билеты в первый ряд на «Монады-шоу»!
Haskell так же предоставляет нам немного синтаксического сахара для монад, называемого do-нотация:

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




Заключение




ссылка на оригинал статьи http://habrahabr.ru/post/182466/

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>