Изучай Haskell во имя добра! (Липовача) - страница 260

и >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, в котором присутствуют две весьма полезные функции: