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

<p>Случайность и монада State</p>

В начале этого раздела мы говорили о том, что генерация случайных чисел может иногда быть неуклюжей. Каждая функция, использующая случайность, принимает генератор и возвращает случайное число вместе с новым генератором, который должен затем быть использован вместо прежнего, если нам нужно сгенерировать ещё одно случайное число. Монада 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)

Теперь выполнение всего, что требует сохранения некоторого состояния в промежутках между шагами, в самом деле стало доставлять значительно меньше хлопот!

<p>Свет мой, Error, скажи, да всю правду доложи</p>

К этому времени вы знаете, что монада Maybe используется, чтобы добавить к значениям контекст возможной неудачи. Значением может быть Just <нечто> либо Nothing. Как бы это ни было полезно, всё, что нам известно, когда у нас есть значение Nothing, – это состоявшийся факт некоей неудачи: туда не втиснуть больше информации, сообщающей нам, что именно произошло.

Перейти на страницу:

Похожие книги