Вот некое простое значение:
![](https://habrastorage.org/r/w1560/storage2/455/886/8ce/4558868cef65f4b28e06fb33e8dea6e1.png)
И мы знаем, как к нему можно применить функцию:
![](https://habrastorage.org/r/w1560/storage2/56c/b1d/fcf/56cb1dfcfe93c695abe91f967efa46b4.png)
Элементарно. Так что теперь усложним задание — пусть наше значение имеет контекст. Пока что вы можете думать о контексте просто как о ящике, куда можно положить значение:
![](https://habrastorage.org/r/w1560/storage2/3c0/4cc/19b/3c04cc19b198537e5334459b79d915c1.png)
Теперь, когда вы примените функцию к этому значению, результаты вы будете получать разные — в зависимости от контекста. Это основная идея, на которой базируются функторы, аппликативные функторы, монады, стрелки и т.п. Тип данных
![](https://habrastorage.org/r/w1560/storage2/be0/182/2de/be01822de6f660845c952b2b4fa7edb6.png)
Позже мы увидим разницу в поведении функции для
Когда у вас есть значение, упакованное в контекст, вы не можете просто взять и применить к нему обычную функцию:
![](https://habrastorage.org/r/w1560/storage2/495/97b/9b9/49597b9b94e87887cd2f1bd79c0eddaf.png)
И здесь
![](https://habrastorage.org/r/w1560/storage2/745/6ee/882/7456ee882cd44a105dd9c943e8605f97.png)
Бам!
Функтор — это класс типов. Вот его определение:
![](https://habrastorage.org/r/w1560/storage2/6f3/b55/4f6/6f3b554f6ecdfddef9e1f4346f9b30d0.png)
Функтором является любой тип данных, для которого определено, как к нему применяется
![](https://habrastorage.org/r/w1560/storage2/2db/49f/7fe/2db49f7fef6ef78e27bf4f6b96866a17.png)
Так что мы можем делать так:
И
Вот что происходит за сценой, когда мы пишем
![](https://habrastorage.org/r/w1560/storage2/983/73b/546/98373b54695813ce070a7fc782ca8a35.png)
А потом вы скажете: «Ладно,
![](https://habrastorage.org/r/w1560/storage2/788/3ad/937/7883ad93713ea4406c129d54f83659d8.png)
![](https://habrastorage.org/r/w1560/storage2/238/88c/8ed/23888c8ed4ae153e5c4d62321fec63c1.png)
Билл О'Рейли ничегошеньки не смыслит в функторе Maybe
Как Морфеус в «Матрице»,
На Haskell же:
Если
А вот ещё один пример: что происходит, когда вы применяете функцию к списку?
![](https://habrastorage.org/r/w1560/storage2/5ba/e29/3d1/5bae293d123881aa5920d3e7e9e1d039.png)
Списки тоже функторы! Вот определение:
Ладно, ладно, ещё один (последний) пример: что случится, когда вы примените функцию к другой функции?
Вот эта функция:
![](https://habrastorage.org/r/w1560/storage2/201/3f4/3c6/2013f43c6f7083d7acec85ce3af127e5.png)
А вот функция, применённая к другой функции:
![](https://habrastorage.org/r/w1560/storage2/963/2a1/176/9632a117608770a1282fe34ee569c7cd.png)
Результат — просто ещё одна функция!
Так что функции — тоже функторы!
И когда вы применяете
Следующий уровень — аппликативные функторы. С ними наше значение по-прежнему упаковано в контекст (так же как с функторами):
![](https://habrastorage.org/r/w1560/storage2/3c0/4cc/19b/3c04cc19b198537e5334459b79d915c1.png)
Но теперь в контекст упакована и наша функция!
![](https://habrastorage.org/r/w1560/storage2/a30/d05/78b/a30d0578bd8153b5d70d19561132da28.png)
Ага! Давайте-ка вникнем в это. Аппликативные функторы надувательством не занимаются.
![](https://habrastorage.org/r/w1560/storage2/513/590/2f0/5135902f0402a8cdce151394f1e5a27f.png)
Т.е.
Использование
![](https://habrastorage.org/r/w1560/storage2/bc8/171/e3d/bc8171e3d0850ac81f6eca96502f15f6.png)
А вот кое-что, что вы можете сделать с помощью аппликативных функторов, но не сможете с помощью обычных. Как вы примените функцию, которая принимает два аргумента, к двум упакованным значениям?
Аппликативные функторы:
![](https://habrastorage.org/storage2/21d/00a/044/21d00a0447e98cd11f0f3f0f3eb1dff1.gif)
Аппликативный функтор наблюдает за тем, как обычный применяет функцию
И да! Существует функция
Как изучать монады:
Монады добавляют новый поворот в наш сюжет.
Функторы применяют обычную функцию к упакованному значению:
![](https://habrastorage.org/r/w1560/storage2/4ad/340/ac3/4ad340ac37572e625b8b78bdfc7229b6.png)
Аппликативные функторы применяют упакованную функцию к упакованному же значению:
![](https://habrastorage.org/r/w1560/storage2/e14/0a9/34a/e140a934a1e7eb6d5936fa73007eb3b6.png)
Монады применяют функцию, которая возвращает упакованное значение, к упакованному значению. У монад есть функция
Рассмотрим такой пример: наш старый добрый
![](https://habrastorage.org/r/w1560/storage2/be0/182/2de/be01822de6f660845c952b2b4fa7edb6.png)
Просто болтающаяся монада
Пусть
![](https://habrastorage.org/r/w1560/storage2/390/43d/bf8/39043dbf84680405fa43322159287fb3.png)
А что, если мы скормим ей упакованное значение?
![](https://habrastorage.org/r/w1560/storage2/360/8a6/45e/3608a645e7f5af4ecdbf22b0313d450f.png)
Нам нужно использовать
![](https://habrastorage.org/r/w780q1/storage2/e05/3d7/ed3/e053d7ed34654fa46ccfeaee5ef68194.jpg)
А вот как она работает:
Что же происходит внутри?
Где
![](https://habrastorage.org/r/w1560/storage2/071/04f/43f/07104f43fa43b49db82e7c59c48cbcf3.png)
Так что
А вот какие действия проделываются над бедным
![](https://habrastorage.org/r/w1560/storage2/1c2/2f3/a83/1c22f3a8336dad867b52eaef21e98e04.png)
Если же вы подадите на вход
![](https://habrastorage.org/r/w1560/storage2/584/90e/07e/58490e07ec45b3dfff7d08e21fefafab.png)
Можно так же связать цепочку из вызовов:
![](https://habrastorage.org/r/w1560/storage2/2dc/bcd/64a/2dcbcd64a4d6b362425a08bbae2efc98.png)
![](https://habrastorage.org/r/w1560/storage2/232/4c6/639/2324c6639d2035cc71101880c2de5ed6.png)
Клёвая штука! И теперь мы знаем, что
А сейчас давайте переключимся на другой пример:
![](https://habrastorage.org/r/w1560/storage2/1ba/e13/8f3/1bae138f3fae81b2215ea828949e7a57.png)
В частности, на три её функции.
![](https://habrastorage.org/r/w1560/storage2/0d4/10f/515/0d410f515a206108eea1eb7ff661a647.png)
![](https://habrastorage.org/r/w1560/storage2/263/39f/b92/26339fb92465bc86988a2270a2e0e05d.png)
![](https://habrastorage.org/r/w1560/storage2/fb1/4a7/eb5/fb14a7eb57319bc17c7c13c24e532310.png)
Все три функции принимают регулярные значения (или вообще не принимают значений) и возвращают упакованные значения. Значит, мы можем связать их в цепочку с помощью
![](https://habrastorage.org/r/w1560/storage2/fe1/00a/6ac/fe100a6ac7d36f6c7de657088bbc8e97.png)
О да, у нас билеты в первый ряд на «Монады-шоу»!
Haskell так же предоставляет нам некоторый синтаксический сахар для монад, называемый
В чём разница между этими тремя?
![](http://habrastorage.org/r/w1560/storage2/175/629/0fe/1756290fe42096710c2d3761c763351e.png)
Итак, дорогие друзья (а я надеюсь, что к этому моменту мы стали друзьями), я думаю, все мы согласимся с тем, что монады простая и УМНАЯ ИДЕЯ (тм). А теперь, после того, как мы промочили горло этим руководством, то почему бы не позвать Мела Гибсона и не допить бутылку до дна? Проверьте раздел, посвящённый монадам, в LYAH. Там очень много вещей, о которых я умолчал, потому что Миран проделал великолепную работу по углублению в этот материал.
Ещё больше монад и картинок можно найти в трёх полезных монадах (перевод).
От переводчика:
Ссылка на оригинал: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html Пишу её так, потому что Хабр ругается на url с запятыми.
И, конечно, я буду очень признательна за замечания в личку относительно перевода.
![](https://habrastorage.org/storage2/455/886/8ce/4558868cef65f4b28e06fb33e8dea6e1.png)
И мы знаем, как к нему можно применить функцию:
![](https://habrastorage.org/storage2/56c/b1d/fcf/56cb1dfcfe93c695abe91f967efa46b4.png)
Элементарно. Так что теперь усложним задание — пусть наше значение имеет контекст. Пока что вы можете думать о контексте просто как о ящике, куда можно положить значение:
![](https://habrastorage.org/storage2/3c0/4cc/19b/3c04cc19b198537e5334459b79d915c1.png)
Теперь, когда вы примените функцию к этому значению, результаты вы будете получать разные — в зависимости от контекста. Это основная идея, на которой базируются функторы, аппликативные функторы, монады, стрелки и т.п. Тип данных
Maybe
определяет два связанных контекста:![](https://habrastorage.org/storage2/be0/182/2de/be01822de6f660845c952b2b4fa7edb6.png)
data Maybe a = Nothing | Just a
Позже мы увидим разницу в поведении функции для
Just a
против Nothing
. Но сначала поговорим о функторах!Функторы
Когда у вас есть значение, упакованное в контекст, вы не можете просто взять и применить к нему обычную функцию:
![](https://habrastorage.org/storage2/495/97b/9b9/49597b9b94e87887cd2f1bd79c0eddaf.png)
И здесь
fmap
спешит на помощь. fmap
— парень с улицы, fmap
знает толк в контекстах. Уж он-то в курсе, как применить функцию к упакованному в контекст значению. Допустим, что вы хотите применить (+3)
к Just 2
. Используйте fmap
:> fmap (+3) (Just 2)
Just 5
![](https://habrastorage.org/storage2/745/6ee/882/7456ee882cd44a105dd9c943e8605f97.png)
Бам!
fmap
продемонстрировал нам, как это делается! Но вот откуда он знает, как правильно применять функцию?Так что такое функтор на самом деле?
Функтор — это класс типов. Вот его определение:
![](https://habrastorage.org/storage2/6f3/b55/4f6/6f3b554f6ecdfddef9e1f4346f9b30d0.png)
Функтором является любой тип данных, для которого определено, как к нему применяется
fmap
. А вот как fmap
работает:![](https://habrastorage.org/storage2/2db/49f/7fe/2db49f7fef6ef78e27bf4f6b96866a17.png)
Так что мы можем делать так:
> 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)
:![](https://habrastorage.org/storage2/983/73b/546/98373b54695813ce070a7fc782ca8a35.png)
А потом вы скажете: «Ладно,
fmap
, а примени-ка, пожалуйста, (+3)
к Nothing
.»![](https://habrastorage.org/storage2/788/3ad/937/7883ad93713ea4406c129d54f83659d8.png)
> fmap (+3) Nothing
Nothing
![](https://habrastorage.org/storage2/238/88c/8ed/23888c8ed4ae153e5c4d62321fec63c1.png)
Билл О'Рейли ничегошеньки не смыслит в функторе 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)
А вот ещё один пример: что происходит, когда вы применяете функцию к списку?
![](https://habrastorage.org/storage2/5ba/e29/3d1/5bae293d123881aa5920d3e7e9e1d039.png)
Списки тоже функторы! Вот определение:
instance Functor [] where
fmap = map
Ладно, ладно, ещё один (последний) пример: что случится, когда вы примените функцию к другой функции?
fmap (+3) (+1)
Вот эта функция:
![](https://habrastorage.org/storage2/201/3f4/3c6/2013f43c6f7083d7acec85ce3af127e5.png)
А вот функция, применённая к другой функции:
![](https://habrastorage.org/storage2/963/2a1/176/9632a117608770a1282fe34ee569c7cd.png)
Результат — просто ещё одна функция!
> import Control.Applicative
> let foo = fmap (+3) (+2)
> foo 10
15
Так что функции — тоже функторы!
instance Functor ((->) r) where
fmap f g = f . g
И когда вы применяете
fmap
к функции, то попросту делаете композицию функций!Аппликативные функторы
Следующий уровень — аппликативные функторы. С ними наше значение по-прежнему упаковано в контекст (так же как с функторами):
![](https://habrastorage.org/storage2/3c0/4cc/19b/3c04cc19b198537e5334459b79d915c1.png)
Но теперь в контекст упакована и наша функция!
![](https://habrastorage.org/storage2/a30/d05/78b/a30d0578bd8153b5d70d19561132da28.png)
Ага! Давайте-ка вникнем в это. Аппликативные функторы надувательством не занимаются.
Control.Applicative
определяет <*>
, который знает, как применить функцию, упакованную в контекст, к значению, упакованному в контекст:![](https://habrastorage.org/storage2/513/590/2f0/5135902f0402a8cdce151394f1e5a27f.png)
Т.е.
Just (+3) <*> Just 2 == Just 5
Использование
<*>
может привести к возникновению интересных ситуаций. Например:> [(*2), (+3)] <*> [1, 2, 3]
[2, 4, 6, 4, 5, 6]
![](https://habrastorage.org/storage2/bc8/171/e3d/bc8171e3d0850ac81f6eca96502f15f6.png)
А вот кое-что, что вы можете сделать с помощью аппликативных функторов, но не сможете с помощью обычных. Как вы примените функцию, которая принимает два аргумента, к двум упакованным значениям?
> (+) <$> (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
![](https://habrastorage.org/storage2/21d/00a/044/21d00a0447e98cd11f0f3f0f3eb1dff1.gif)
Аппликативный функтор наблюдает за тем, как обычный применяет функцию
И да! Существует функция
liftA2
, которая делает тоже самое:> liftA2 (*) (Just 5) (Just 3)
Just 15
Монады
Как изучать монады:
- Получить корочки PhD в Computer Science
- Выкинуть их нафиг, потому что при чтении этого раздела они вам не понадобятся!
Монады добавляют новый поворот в наш сюжет.
Функторы применяют обычную функцию к упакованному значению:
![](https://habrastorage.org/storage2/4ad/340/ac3/4ad340ac37572e625b8b78bdfc7229b6.png)
Аппликативные функторы применяют упакованную функцию к упакованному же значению:
![](https://habrastorage.org/storage2/e14/0a9/34a/e140a934a1e7eb6d5936fa73007eb3b6.png)
Монады применяют функцию, которая возвращает упакованное значение, к упакованному значению. У монад есть функция
>>=
(произносится «связывание» (bind)), позволяющая делать это.Рассмотрим такой пример: наш старый добрый
Maybe
— это монада:![](https://habrastorage.org/storage2/be0/182/2de/be01822de6f660845c952b2b4fa7edb6.png)
Просто болтающаяся монада
Пусть
half
— функция, которая работает только с чётными числами:half x = if even x
then Just (x `div` 2)
else Nothing
![](https://habrastorage.org/storage2/390/43d/bf8/39043dbf84680405fa43322159287fb3.png)
А что, если мы скормим ей упакованное значение?
![](https://habrastorage.org/storage2/360/8a6/45e/3608a645e7f5af4ecdbf22b0313d450f.png)
Нам нужно использовать
>>=
, чтобы пропихнуть упакованное значение через функцию. Вот фото >>=
: ![](https://habrastorage.org/storage2/e05/3d7/ed3/e053d7ed34654fa46ccfeaee5ef68194.jpg)
А вот как она работает:
> Just 3 >>= half
Nothing
> Just 4 >>= half
Just 2
> Nothing >>= half
Nothing
Что же происходит внутри?
Monad
— ещё один класс типов. Вот его частичное определение:class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
Где
>>=
:![](https://habrastorage.org/storage2/071/04f/43f/07104f43fa43b49db82e7c59c48cbcf3.png)
Так что
Maybe
— это монада:instance Monad Maybe where
Nothing >>= func = Nothing
Just val >>= func = func val
А вот какие действия проделываются над бедным
Just 3
!![](https://habrastorage.org/storage2/1c2/2f3/a83/1c22f3a8336dad867b52eaef21e98e04.png)
Если же вы подадите на вход
Nothing
, то всё ещё проще:![](https://habrastorage.org/storage2/584/90e/07e/58490e07ec45b3dfff7d08e21fefafab.png)
Можно так же связать цепочку из вызовов:
> Just 20 >>= half >>= half >>= half
Nothing
![](https://habrastorage.org/storage2/2dc/bcd/64a/2dcbcd64a4d6b362425a08bbae2efc98.png)
![](https://habrastorage.org/storage2/232/4c6/639/2324c6639d2035cc71101880c2de5ed6.png)
Клёвая штука! И теперь мы знаем, что
Maybe
— это Functor
, Applicative
и Monad
в одном лице.А сейчас давайте переключимся на другой пример:
IO
монаду:![](https://habrastorage.org/storage2/1ba/e13/8f3/1bae138f3fae81b2215ea828949e7a57.png)
В частности, на три её функции.
getLine
не принимает аргументов и получает пользовательские данные с входа:![](https://habrastorage.org/storage2/0d4/10f/515/0d410f515a206108eea1eb7ff661a647.png)
getLine :: IO String
readFile
принимает строку (имя файла) и возвращает его содержимое:![](https://habrastorage.org/storage2/263/39f/b92/26339fb92465bc86988a2270a2e0e05d.png)
readFile :: FilePath -> IO String
putStrLn
принимает строку и печатает её:![](https://habrastorage.org/storage2/fb1/4a7/eb5/fb14a7eb57319bc17c7c13c24e532310.png)
putStrLn :: String -> IO ()
Все три функции принимают регулярные значения (или вообще не принимают значений) и возвращают упакованные значения. Значит, мы можем связать их в цепочку с помощью
>>=
!![](https://habrastorage.org/storage2/fe1/00a/6ac/fe100a6ac7d36f6c7de657088bbc8e97.png)
getLine >>= readFile >>= putStrLn
О да, у нас билеты в первый ряд на «Монады-шоу»!
Haskell так же предоставляет нам некоторый синтаксический сахар для монад, называемый
do
-нотацией:foo = do
filename <- getLine
contents <- readFile filename
putStrLn contents
Заключение
- Функтор — это тип данных, реализуемый с помощью класса типов
Functor
- Аппликативный функтор — это тип данных, реализуемый с помощью класса типов
Applicative
- Монада — это тип данных, реализуемый с помощью класса типов
Monad
- Maybe реализуется с помощью всех трёх классов типов, поэтому является функтором, аппликативным функтором и монадой одновременно
В чём разница между этими тремя?
![](http://habrastorage.org/storage2/175/629/0fe/1756290fe42096710c2d3761c763351e.png)
- функтор: вы применяете функцию к упакованному значению, используя
fmap
или<$>
- аппликативный функтор: вы применяете упакованную функцию к упакованному значению, используя
<*>
илиliftA
- монада: вы применяете функцию, возвращающую упакованное значение, к упакованному значению, используя
>>=
илиliftM
Итак, дорогие друзья (а я надеюсь, что к этому моменту мы стали друзьями), я думаю, все мы согласимся с тем, что монады простая и УМНАЯ ИДЕЯ (тм). А теперь, после того, как мы промочили горло этим руководством, то почему бы не позвать Мела Гибсона и не допить бутылку до дна? Проверьте раздел, посвящённый монадам, в LYAH. Там очень много вещей, о которых я умолчал, потому что Миран проделал великолепную работу по углублению в этот материал.
Ещё больше монад и картинок можно найти в трёх полезных монадах (перевод).
От переводчика:
Ссылка на оригинал: http://adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html Пишу её так, потому что Хабр ругается на url с запятыми.
И, конечно, я буду очень признательна за замечания в личку относительно перевода.