Внимание: перед тем как читать текст ниже, вы уже должны иметь представление о том, что такое монады. Если это не так, то прежде прочитайте вот этот пост!
Перед нами функция
![](https://habrastorage.org/r/w1560/storage2/cae/b90/f70/caeb90f70d874d167b9c41b79f6d9936.png)
И мы можем применить её несколько раз:
Всё работает как и ожидалось. Но вот вы решили, что хорошо бы иметь лог того, что происходит с этой функцией:
![](https://habrastorage.org/r/w1560/storage2/589/c29/38b/589c2938bad51c85d798b6b6a9109331.png)
Что ж, отлично. Но что будет если вы теперь захотите применить
Вот то, что мы хотели бы, чтобы происходило:
![](https://habrastorage.org/r/w1560/storage2/79d/276/7a6/79d2767a62262147d45b5cd660041b34.png)
Спойлер: автоматически так не сделается. Придётся всё расписывать ручками:
Фу! Это ни капли не похоже на лаконичное
А что, если у вас есть ещё функции, имеющие лог? Напрашивается такая схема: для каждой функции, возвращающей вместе со значением лог, мы бы хотели объединять эти логи. Это побочный эффект, а никто не силён в побочных эффектах так, как монады!
![](https://habrastorage.org/r/w1560/storage2/8aa/d64/c75/8aad64c7563305afd1f02d8c4b6236cf.png)
Появляется монада Writer набелом коне
Монада Writer — крутая личность. «Не парься, чувак, я обработаю твои логи,» — говорит она. — «Возвращайся к своему чистому коду и выжми максимум под что-нибудь из Zeppelin!»
У каждого экземпляра Writer есть лог и возвращаемое значение:
![](https://habrastorage.org/r/w1560/storage2/b98/d81/15a/b98d8115a8526bcd1f3844d0f12f653f.png)
Writer позволяет нам писать код наподобие
Ну, или вы можете использовать
что чертовски близко к
Вы можете использовать
Она возвращает
![](http://habrastorage.org/r/w1560/storage2/c8a/8e0/ed0/c8a8e0ed0a51dc082e7d3d1ee174c696.png)
И мы можем использовать
![](http://habrastorage.org/r/w1560/storage2/af1/c2a/8a0/af1c2a8a09344c40303c9b5d2ff4cd81.png)
Но самый клёвый момент, это то, что теперь мы можем связать вызовы
Вот что происходит:
![](http://habrastorage.org/r/w1560/storage2/9e7/9bc/372/9e79bc3728309efd8350fc3095f1e8a0.png)
Магическим образом
![](http://habrastorage.org/r/w1560/storage2/323/c0f/ae6/323c0fae648b3f70a4582a5bd091a5d6.png)
Точно такой же шаблонный код мы ранее писали сами, а теперь эту обязанность взяла на себя
![](http://habrastorage.org/r/w1560/storage2/492/f80/340/492f80340d2467c8b885b43812a3bd7d.png)
(Замечание: это определение почти верное. На самом деле монада
Спасибо тебе, монада Writer!
Допустим, вы хотите передать какие-то настройки в множество функций. Просто используйте монаду Reader:
![](http://habrastorage.org/r/w1560/storage2/1cb/2f6/22f/1cb2f622f33160a16fa139e29b131e0d.png)
Она позволит вам передать ваше значение всем функциям, скрыв сам механизм передачи за кулисами. Например:
![](http://habrastorage.org/r/w1560/storage2/d17/adb/a48/d17adba48af58a7228300515b4faf8e8.png)
Вот её определение:
Reader всегда был перебежчиком. Тёмной лошадкой. Reader другой, потому что он позволяет нам обрабатывать функцию, что смущает при его рассмотрении. Но мы с вами понимаем, что можем использовать
![](http://habrastorage.org/r/w1560/storage2/5b8/860/790/5b8860790e50b21119db1d44c70bbfeb.png)
И можем передать этой функции некое состояние, которое потом используется в
![](http://habrastorage.org/r/w1560/storage2/cc4/137/fb8/cc4137fb86883996f0c2097871423713.png)
таким образом, когда вы используете
![](http://habrastorage.org/r/w1560/storage2/a69/bc3/cc3/a69bc3cc388b8eff61e694c499f18489.png)
Reader всегда был несколько комплексным. Это вообще его лучшее качество.
![](http://habrastorage.org/r/w1560/storage2/823/bba/491/823bba491a78328a17db34481e9d9794.png)
И, наконец,
Хотите провести с Reader'ом больше времени? Врубите панк-рок и взгляните на более длинный пример.
Монада State — это более впечатлительный лучший друг монады Reader:
![](http://habrastorage.org/r/w1560/storage2/4b6/ae3/604/4b6ae36049f62125b42ea465af0c846f.png)
Она в точности тоже самое, что и монада Reader, но с её помощью вы можете не только читать, но и писать!
Вот определение
![](http://habrastorage.org/r/w1560/storage2/5ba/e9c/89b/5bae9c89b2f3b838fd751de24ad82243.png)
Вы можете получить состояние с помощью
Мило! Если Reader был с характером«тебе-меня-не-изменить», то State наоборот стремится к отношениям и сама готова меняться.
Определение
![](http://habrastorage.org/r/w1560/storage2/2a4/b96/411/2a4b96411d683dc17a584daaa82209d3.png)
![](http://habrastorage.org/r/w1560/storage2/051/afb/1ec/051afb1ecd485340650293d6f82f1b4b.png)
![](http://habrastorage.org/r/w1560/storage2/458/bfb/779/458bfb779ad856baea09e0691be0c57a.png)
Writer. Reader. State. Сегодня вы добавили три мощных оружия в свой Haskell-арсенал. Используйте их с умом.
От переводчика: я буду бесконечно признательна за любые замечания в личку, которые помогут улучшить качество этого перевода.
Перед нами функция
half
:![](https://habrastorage.org/storage2/cae/b90/f70/caeb90f70d874d167b9c41b79f6d9936.png)
И мы можем применить её несколько раз:
half . half $ 8
=> 2
Всё работает как и ожидалось. Но вот вы решили, что хорошо бы иметь лог того, что происходит с этой функцией:
![](https://habrastorage.org/storage2/589/c29/38b/589c2938bad51c85d798b6b6a9109331.png)
half x = (x `div` 2, "Я только что располовинил " ++ (show x) ++ "!")
Что ж, отлично. Но что будет если вы теперь захотите применить
half
несколько раз?half . half $ 8
Вот то, что мы хотели бы, чтобы происходило:
![](https://habrastorage.org/storage2/79d/276/7a6/79d2767a62262147d45b5cd660041b34.png)
Спойлер: автоматически так не сделается. Придётся всё расписывать ручками:
finalValue = (val2, log1 ++ log2)
where (val1, log1) = half 8
(val2, log2) = half val1
Фу! Это ни капли не похоже на лаконичное
half . half $ 8
А что, если у вас есть ещё функции, имеющие лог? Напрашивается такая схема: для каждой функции, возвращающей вместе со значением лог, мы бы хотели объединять эти логи. Это побочный эффект, а никто не силён в побочных эффектах так, как монады!
Монада Writer
![](https://habrastorage.org/storage2/8aa/d64/c75/8aad64c7563305afd1f02d8c4b6236cf.png)
Появляется монада Writer на
Монада Writer — крутая личность. «Не парься, чувак, я обработаю твои логи,» — говорит она. — «Возвращайся к своему чистому коду и выжми максимум под что-нибудь из Zeppelin!»
У каждого экземпляра Writer есть лог и возвращаемое значение:
![](https://habrastorage.org/storage2/b98/d81/15a/b98d8115a8526bcd1f3844d0f12f653f.png)
data Writer w a = Writer { runWriter :: (a, w) }
Writer позволяет нам писать код наподобие
half 8 >>= half
Ну, или вы можете использовать
<=<
функцию, которая суть композиция функций для монад, чтобы получить:half <=< half $ 8
что чертовски близко к
half . half $ 8
. Круто!Вы можете использовать
tell
, чтобы записать что-то в лог. А return
передаст это значение в Writer. Вот наша обновлённая функция half
:half :: Int -> Writer String Int
half x = do
tell ("I just halved " ++ (show x) ++ "!")
return (x `div` 2)
Она возвращает
Writer
:![](http://habrastorage.org/storage2/c8a/8e0/ed0/c8a8e0ed0a51dc082e7d3d1ee174c696.png)
И мы можем использовать
runWriter
, чтобы извлечь из него значение:![](http://habrastorage.org/storage2/af1/c2a/8a0/af1c2a8a09344c40303c9b5d2ff4cd81.png)
runWriter $ half 8
=> (4, "I just halved 8!")
Но самый клёвый момент, это то, что теперь мы можем связать вызовы
half
в цепочку, используя >>=
:runWriter $ half 8 >>= half
=> (2, "I just halved 8!I just halved 4!")
Вот что происходит:
![](http://habrastorage.org/storage2/9e7/9bc/372/9e79bc3728309efd8350fc3095f1e8a0.png)
Магическим образом
>>=
знает, как объединить два Writer'а, так что нам нет нужды писать самостоятельно какой бы то ни было нудный код! Вот полное определение >>=
:![](http://habrastorage.org/storage2/323/c0f/ae6/323c0fae648b3f70a4582a5bd091a5d6.png)
Точно такой же шаблонный код мы ранее писали сами, а теперь эту обязанность взяла на себя
>>=
. Клёво! Мы так же используем функцию return
, которая принимает значение и помещает его в монаду:![](http://habrastorage.org/storage2/492/f80/340/492f80340d2467c8b885b43812a3bd7d.png)
return val = Writer (val, "")
(Замечание: это определение почти верное. На самом деле монада
Writer
позволяет нам использовать любой Monoid
в качестве лога, а не одни только строки. Я тут упростил слегка.)Спасибо тебе, монада Writer!
Монада Reader
Допустим, вы хотите передать какие-то настройки в множество функций. Просто используйте монаду Reader:
![](http://habrastorage.org/storage2/1cb/2f6/22f/1cb2f622f33160a16fa139e29b131e0d.png)
Она позволит вам передать ваше значение всем функциям, скрыв сам механизм передачи за кулисами. Например:
greeter :: Reader String String
greeter = do
name <- ask
return ("hello, " ++ name ++ "!")
greeter
возвращает монаду Reader:![](http://habrastorage.org/storage2/d17/adb/a48/d17adba48af58a7228300515b4faf8e8.png)
Вот её определение:
data Reader r a = Reader { runReader :: r -> a }
Reader всегда был перебежчиком. Тёмной лошадкой. Reader другой, потому что он позволяет нам обрабатывать функцию, что смущает при его рассмотрении. Но мы с вами понимаем, что можем использовать
runReader
, чтобы извлечь эту функцию:![](http://habrastorage.org/storage2/5b8/860/790/5b8860790e50b21119db1d44c70bbfeb.png)
И можем передать этой функции некое состояние, которое потом используется в
greeter
:![](http://habrastorage.org/storage2/cc4/137/fb8/cc4137fb86883996f0c2097871423713.png)
runReader greeter $ "adit"
=> "hello, adit!"
таким образом, когда вы используете
>>=
, назад вы должны получить Reader. Когда вы передадите в него состояние, то оно будет передано всем функциям, содержащимся в этой монаде.![](http://habrastorage.org/storage2/a69/bc3/cc3/a69bc3cc388b8eff61e694c499f18489.png)
m >>= k = Reader $ \r -> runReader (k (runReader m r)) r
Reader всегда был несколько комплексным. Это вообще его лучшее качество.
return
помещает значение в Reader
: ![](http://habrastorage.org/storage2/823/bba/491/823bba491a78328a17db34481e9d9794.png)
return a = Reader $ \_ -> a
И, наконец,
ask
возвращает вам обратно состояние, которое вы передали:ask = Reader $ \x -> x
Хотите провести с Reader'ом больше времени? Врубите панк-рок и взгляните на более длинный пример.
Монада State
Монада State — это более впечатлительный лучший друг монады Reader:
![](http://habrastorage.org/storage2/4b6/ae3/604/4b6ae36049f62125b42ea465af0c846f.png)
Она в точности тоже самое, что и монада Reader, но с её помощью вы можете не только читать, но и писать!
Вот определение
State
:State s a = State { runState :: s -> (a, s) }
![](http://habrastorage.org/storage2/5ba/e9c/89b/5bae9c89b2f3b838fd751de24ad82243.png)
Вы можете получить состояние с помощью
get
или изменить его с помощью put
. Вот пример:greeter :: State String String
greeter = do
name <- get
put "tintin"
return ("hello, " ++ name ++ "!")
runState greeter $ "adit"
=> ("hello, adit!", "tintin")
Мило! Если Reader был с характером«тебе-меня-не-изменить», то State наоборот стремится к отношениям и сама готова меняться.
Определение
State
монады выглядит очень похоже на определение монады Reader
:return
:![](http://habrastorage.org/storage2/2a4/b96/411/2a4b96411d683dc17a584daaa82209d3.png)
return a = State $ \s -> (a, s)
>>=
:![](http://habrastorage.org/storage2/051/afb/1ec/051afb1ecd485340650293d6f82f1b4b.png)
m >>= k = State $ \s -> let (a, s') = runState m s
in runState (k a) s'
Заключение
![](http://habrastorage.org/storage2/458/bfb/779/458bfb779ad856baea09e0691be0c57a.png)
Writer. Reader. State. Сегодня вы добавили три мощных оружия в свой Haskell-арсенал. Используйте их с умом.
От переводчика: я буду бесконечно признательна за любые замечания в личку, которые помогут улучшить качество этого перевода.