Тройка полезных монад

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

Перед нами функция 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-арсенал. Используйте их с умом.

От переводчика: я буду бесконечно признательна за любые замечания в личку, которые помогут улучшить качество этого перевода.

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

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

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