Базовая схема использования любой «нормальной» монады [0] почти одинакова для всех. По существу:
- Напишите функции, которые возвращают значение монадического типа, используя запись
do
, если хотите, точно так же, как вы бы написали функцию IO
, например main
.
- Используйте любые специальные функции для монады, с которой вы работаете.
- Вызовите эти функции друг от друга, используя стандартное правило:
- Свяжите значение из той же монады , используя
<-
, чтобы получить значение «изнутри», в результате чего другое значение будет «выполнено».
- Свяжите любое другое значение, используя
let
, оставляя его независимым от монадической структуры.
- Используйте специализированную функцию run для конкретной монады, чтобы оценить монадическое вычисление и получить окончательный результат.
Сделайте это, и все грязные детали дополнительной функциональности, описанной монадой (в данном случае передача дополнительного параметра среды), обрабатываются автоматически.
Теперь обычные операции чтения: ask
и local
:
ask
- монадическое значение, удерживающее окружающую среду; в блоке do
вы используете его так же, как если бы вы использовали что-то вроде getLine
в монаде IO
.
local
принимает функцию, которая обеспечивает новую среду и вычисления в монаде Reader, запускает последнюю в среде, модифицированной первой, затем берет результат и помещает его в текущую функцию. Другими словами, он выполняет подсчет с локально модифицированным environemnt.
Функцией "run" является креативно названное runReader
, которое просто берет вычисление в монаде Reader и значение окружения, запускает первое с использованием последнего и возвращает конечный результат вне монады.
В качестве примера, вот некоторые функции, выполняющие некоторые бессмысленные вычисления в монаде Reader, где среда - это «максимальное значение», которое говорит, когда нужно остановиться:
import Control.Monad.Reader
computeUpToMax :: (Int -> Int) -> Int -> Reader Int [Maybe Int]
computeUpToMax f x = do
maxVal <- ask
let y = f x
if y > maxVal
then return []
else do zs <- local (subtract y) (computeUpToMax f y)
z <- frob y
return (z:zs)
frob :: Int -> Reader Int (Maybe Int)
frob y = do
maxVal <- ask
let z = maxVal - y
if z == y
then return Nothing
else return $ Just z
Чтобы запустить его, вы должны использовать что-то вроде этого:
> runReader (computeUpToMax (+ 1) 0) 9
[Just 8, Just 6, Nothing]
... где 9
- начальная среда.
Почти точно такую же структуру можно использовать с другими монадами, такими как State
, Maybe
или []
, хотя в последних двух случаях вы обычно просто используете окончательное значение монадического результата вместо использования функция запуска.
[0] : Там, где нормальный означает отсутствие магии компилятора, наиболее очевидной «ненормальной» монадой, конечно, является IO
.