и
>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
>()
просто сохраняет состояние как есть и ничего не делает.