Также можно использовать функции get
put
, чтобы реализовать функции pop
и push
. Вот определение функции pop
:pop :: State Stack Int
pop = do
(x:xs) <– get
put xs
return x
Мы используем функцию get
put
, чтобы новым состоянием были все элементы за исключением верхнего. После чего прибегаем к функции return
, чтобы представить значение x
в качестве результата.Вот определение функции push
get
и put
:push :: Int –> State Stack ()
push x = do
xs <– get
put (x:xs)
Мы просто используем функцию get
put
, чтобы установить состояние в такое же, как наш стек с элементом x
на вершине.Стоит проверить, каким был бы тип операции >>=
State
:(>>=) :: State s a –> (a –> State s b) –> State s b
Видите, как тип состояния s
a
на b
? Это означает, что мы можем «склеивать» вместе несколько вычислений с состоянием, результаты которых имеют различные типы, но тип состояния должен оставаться тем же. Почему же так?.. Ну, например, для типа Maybe
операция >>=
имеет такой тип:(>>=) :: Maybe a –> (a –> Maybe b) –> Maybe b
Логично, что сама монада Maybe
>>=
между двумя разными монадами. Для монады State
монадой на самом деле является State s
, так что если бы этот тип s
был различным, мы использовали бы операцию >>=
между двумя разными монадами.Случайность и монада State
В начале этого раздела мы говорили о том, что генерация случайных чисел может иногда быть неуклюжей. Каждая функция, использующая случайность, принимает генератор и возвращает случайное число вместе с новым генератором, который должен затем быть использован вместо прежнего, если нам нужно сгенерировать ещё одно случайное число. Монада State
Функция random
System.Random
имеет следующий тип:random :: (RandomGen g, Random a) => g –> (a, g)
Это значит, что она берёт генератор случайных чисел и производит случайное число вместе с новым генератором. Нам видно, что это вычисление с состоянием, поэтому мы можем обернуть его в конструктор newtype State
state
, а затем использовать его в качестве монадического значения, чтобы передача состояния обрабатывалась за нас:import System.Random
import Control.Monad.State
randomSt :: (RandomGen g, Random a) => State g a
randomSt = state random
Поэтому теперь, если мы хотим подбросить три монеты (True
False
– «орёл»), то просто делаем следующее:import System.Random
import Control.Monad.State
threeCoins :: State StdGen (Bool, Bool, Bool)
threeCoins = do
a <– randomSt
b <– randomSt
c <– randomSt
return (a, b, c)
Функция threeCoins
randomSt
, которая производит число и новый генератор, передаваемый следующей функции, и т. д. Мы используем выражение return (a, b, c)
, чтобы представить значение (a, b, c)
как результат, не изменяя самый последний генератор. Давайте попробуем:ghci> runState threeCoins (mkStdGen 33)
((True,False,True),680029187 2103410263)
Теперь выполнение всего, что требует сохранения некоторого состояния в промежутках между шагами, в самом деле стало доставлять значительно меньше хлопот!
Свет мой, Error, скажи, да всю правду доложи
К этому времени вы знаете, что монада Maybe
Just <нечто
> либо Nothing
. Как бы это ни было полезно, всё, что нам известно, когда у нас есть значение Nothing
, – это состоявшийся факт некоей неудачи: туда не втиснуть больше информации, сообщающей нам, что именно произошло.