Внимание: перед тем как читать текст ниже, вы уже должны иметь представление о том, что такое монады. Если это не так, то прежде прочитайте вот этот пост!
Перед нами функция

И мы можем применить её несколько раз:
Всё работает как и ожидалось. Но вот вы решили, что хорошо бы иметь лог того, что происходит с этой функцией:

Что ж, отлично. Но что будет если вы теперь захотите применить
Вот то, что мы хотели бы, чтобы происходило:

Спойлер: автоматически так не сделается. Придётся всё расписывать ручками:
Фу! Это ни капли не похоже на лаконичное
А что, если у вас есть ещё функции, имеющие лог? Напрашивается такая схема: для каждой функции, возвращающей вместе со значением лог, мы бы хотели объединять эти логи. Это побочный эффект, а никто не силён в побочных эффектах так, как монады!

Появляется монада Writer набелом коне
Монада Writer — крутая личность. «Не парься, чувак, я обработаю твои логи,» — говорит она. — «Возвращайся к своему чистому коду и выжми максимум под что-нибудь из Zeppelin!»
У каждого экземпляра Writer есть лог и возвращаемое значение:

Writer позволяет нам писать код наподобие
Ну, или вы можете использовать
что чертовски близко к
Вы можете использовать
Она возвращает

И мы можем использовать

Но самый клёвый момент, это то, что теперь мы можем связать вызовы
Вот что происходит:

Магическим образом

Точно такой же шаблонный код мы ранее писали сами, а теперь эту обязанность взяла на себя

(Замечание: это определение почти верное. На самом деле монада
Спасибо тебе, монада Writer!
Допустим, вы хотите передать какие-то настройки в множество функций. Просто используйте монаду Reader:

Она позволит вам передать ваше значение всем функциям, скрыв сам механизм передачи за кулисами. Например:

Вот её определение:
Reader всегда был перебежчиком. Тёмной лошадкой. Reader другой, потому что он позволяет нам обрабатывать функцию, что смущает при его рассмотрении. Но мы с вами понимаем, что можем использовать

И можем передать этой функции некое состояние, которое потом используется в

таким образом, когда вы используете

Reader всегда был несколько комплексным. Это вообще его лучшее качество.

И, наконец,
Хотите провести с Reader'ом больше времени? Врубите панк-рок и взгляните на более длинный пример.
Монада State — это более впечатлительный лучший друг монады Reader:

Она в точности тоже самое, что и монада Reader, но с её помощью вы можете не только читать, но и писать!
Вот определение

Вы можете получить состояние с помощью
Мило! Если Reader был с характером«тебе-меня-не-изменить», то State наоборот стремится к отношениям и сама готова меняться.
Определение



Writer. Reader. State. Сегодня вы добавили три мощных оружия в свой Haskell-арсенал. Используйте их с умом.
От переводчика: я буду бесконечно признательна за любые замечания в личку, которые помогут улучшить качество этого перевода.
Перед нами функция
half
:
И мы можем применить её несколько раз:
half . half $ 8
=> 2
Всё работает как и ожидалось. Но вот вы решили, что хорошо бы иметь лог того, что происходит с этой функцией:

half x = (x `div` 2, "Я только что располовинил " ++ (show x) ++ "!")
Что ж, отлично. Но что будет если вы теперь захотите применить
half
несколько раз?half . half $ 8
Вот то, что мы хотели бы, чтобы происходило:

Спойлер: автоматически так не сделается. Придётся всё расписывать ручками:
finalValue = (val2, log1 ++ log2)
where (val1, log1) = half 8
(val2, log2) = half val1
Фу! Это ни капли не похоже на лаконичное
half . half $ 8
А что, если у вас есть ещё функции, имеющие лог? Напрашивается такая схема: для каждой функции, возвращающей вместе со значением лог, мы бы хотели объединять эти логи. Это побочный эффект, а никто не силён в побочных эффектах так, как монады!
Монада Writer

Появляется монада Writer на
Монада Writer — крутая личность. «Не парься, чувак, я обработаю твои логи,» — говорит она. — «Возвращайся к своему чистому коду и выжми максимум под что-нибудь из Zeppelin!»
У каждого экземпляра Writer есть лог и возвращаемое значение:

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
:
И мы можем использовать
runWriter
, чтобы извлечь из него значение:
runWriter $ half 8
=> (4, "I just halved 8!")
Но самый клёвый момент, это то, что теперь мы можем связать вызовы
half
в цепочку, используя >>=
:runWriter $ half 8 >>= half
=> (2, "I just halved 8!I just halved 4!")
Вот что происходит:

Магическим образом
>>=
знает, как объединить два Writer'а, так что нам нет нужды писать самостоятельно какой бы то ни было нудный код! Вот полное определение >>=
:
Точно такой же шаблонный код мы ранее писали сами, а теперь эту обязанность взяла на себя
>>=
. Клёво! Мы так же используем функцию return
, которая принимает значение и помещает его в монаду:
return val = Writer (val, "")
(Замечание: это определение почти верное. На самом деле монада
Writer
позволяет нам использовать любой Monoid
в качестве лога, а не одни только строки. Я тут упростил слегка.)Спасибо тебе, монада Writer!
Монада Reader
Допустим, вы хотите передать какие-то настройки в множество функций. Просто используйте монаду Reader:

Она позволит вам передать ваше значение всем функциям, скрыв сам механизм передачи за кулисами. Например:
greeter :: Reader String String
greeter = do
name <- ask
return ("hello, " ++ name ++ "!")
greeter
возвращает монаду Reader:
Вот её определение:
data Reader r a = Reader { runReader :: r -> a }
Reader всегда был перебежчиком. Тёмной лошадкой. Reader другой, потому что он позволяет нам обрабатывать функцию, что смущает при его рассмотрении. Но мы с вами понимаем, что можем использовать
runReader
, чтобы извлечь эту функцию:
И можем передать этой функции некое состояние, которое потом используется в
greeter
:
runReader greeter $ "adit"
=> "hello, adit!"
таким образом, когда вы используете
>>=
, назад вы должны получить Reader. Когда вы передадите в него состояние, то оно будет передано всем функциям, содержащимся в этой монаде.
m >>= k = Reader $ \r -> runReader (k (runReader m r)) r
Reader всегда был несколько комплексным. Это вообще его лучшее качество.
return
помещает значение в Reader
: 
return a = Reader $ \_ -> a
И, наконец,
ask
возвращает вам обратно состояние, которое вы передали:ask = Reader $ \x -> x
Хотите провести с Reader'ом больше времени? Врубите панк-рок и взгляните на более длинный пример.
Монада State
Монада State — это более впечатлительный лучший друг монады Reader:

Она в точности тоже самое, что и монада Reader, но с её помощью вы можете не только читать, но и писать!
Вот определение
State
:State s a = State { runState :: s -> (a, s) }

Вы можете получить состояние с помощью
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
:
return a = State $ \s -> (a, s)
>>=
:
m >>= k = State $ \s -> let (a, s') = runState m s
in runState (k a) s'
Заключение

Writer. Reader. State. Сегодня вы добавили три мощных оружия в свой Haskell-арсенал. Используйте их с умом.
От переводчика: я буду бесконечно признательна за любые замечания в личку, которые помогут улучшить качество этого перевода.