До сих пор каждый раз, когда мы реализовывали операцию >>=
f
, чтобы получить новое монадическое значение. В случае с монадой Writer
после того, как это сделано и получено новое монадическое значение, нам по-прежнему нужно позаботиться о контексте, объединив прежнее и новое моноидные значения с помощью функции mappend
. Здесь мы выполняем вызов выражения f a
и получаем новое вычисление с состоянием g
. Теперь, когда у нас есть новое вычисление с состоянием и новое состояние (известное под именем newState
), мы просто применяем это вычисление с состоянием g
к newState
. Результатом является кортеж из окончательного результата и окончательного состояния!Итак, при использовании операции >>=
pop
и push
уже являются вычислениями с состоянием, легко обернуть их в обёртку State
:import Control.Monad.State
pop :: State Stack Int
pop = state $ \(x:xs) –> (x, xs)
push :: Int –> State Stack ()
push a = state $ \xs –> ((), a:xs)
Обратите внимание, как мы задействовали функцию state
newtype State
, не прибегая к использованию конструктора значения State
напрямую.Функция pop
push
принимает значение типа Int
и возвращает вычисление с состоянием. Теперь мы можем переписать наш предыдущий пример проталкивания числа 3
в стек и выталкивания двух чисел подобным образом:import Control.Monad.State
stackManip :: State Stack Int
stackManip = do
push 3
a <– pop
pop
Видите, как мы «склеили» проталкивание и два выталкивания в одно вычисление с состоянием? Разворачивая его из обёртки newtype
ghci> runState stackManip [5,8,2,1]
(5,[8,2,1])
Нам не требовалось привязывать второй вызов функции pop
a
, потому что мы вовсе не использовали этот образец. Значит, это можно было записать вот так:stackManip :: State Stack Int
stackManip = do
push 3
pop
pop
Очень круто! Но что если мы хотим сделать что-нибудь посложнее? Скажем, вытолкнуть из стека одно число, и если это число равно 5
5
, вместо этого протолкнуть обратно 3
и 8
. Вот он код:stackStuff :: State Stack ()
stackStuff = do
a <– pop
if a == 5
then push 5
else do
push 3
push 8
Довольно простое решение. Давайте выполним этот код с исходным стеком:
ghci> runState stackStuff [9,0,2,1,0] ((),[8,3,0,2,1,0])
Вспомните, что выражения do
State
одно выражение do
является также функцией с состоянием. Поскольку функции stackManip
и stackStuff
являются обычными вычислениями с состоянием, мы можем «склеивать» их вместе, чтобы производить дальнейшие вычисления с состоянием:moreStack :: State Stack ()
moreStack = do
a <– stackManip
if a == 100
then stackStuff
else return ()
Если результат функции stackManip
100
, мы вызываем функцию stackStuff
; в противном случае ничего не делаем. Вызов return
()
просто сохраняет состояние как есть и ничего не делает.Получение и установка состояния
Модуль Control.Monad.State
MonadState
, в котором присутствуют две весьма полезные функции: get
и put
. Для монады State
функция get
реализована вот так:get = state $ \s –> (s, s)
Она просто берёт текущее состояние и представляет его в качестве результата.
Функция put
put newState = state $ \s –> ((), newState)
Поэтому, используя их, мы можем посмотреть, чему равен текущий стек, либо полностью заменить его другим стеком – например, так:
stackyStack :: State Stack ()
stackyStack = do
stackNow <– get
if stackNow == [1,2,3]
then put [8,3,1]
else put [9,2,1]