Также можно использовать функции 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
, – это состоявшийся факт некоей неудачи: туда не втиснуть больше информации, сообщающей нам, что именно произошло.