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

>pop (x:xs) = (x, xs)


>push :: Int –> Stack –> ((), Stack)

>push a xs = ((), a:xs)

При проталкивании в стек в качестве результата мы использовали значение >(), поскольку проталкивание элемента на вершину стека не несёт какого-либо существенного результирующего значения – его основная задача заключается в изменении стека. Если мы применим только первый параметр функции >push, мы получим вычисление с состоянием. Функция >pop уже является вычислением с состоянием вследствие своего типа.

Давайте напишем небольшой кусок кода для симуляции стека, используя эти функции. Мы возьмём стек, протолкнём в него значение >3, а затем вытолкнем два элемента просто ради забавы. Вот оно:

>stackManip :: Stack –> (Int, Stack)

>stackManip stack = let

>   ((), newStack1) = push 3 stack

>   (a , newStack2) = pop newStack1

>   in pop newStack2

Мы принимаем стек, а затем выполняем выражение >push 3 stack, что даёт в результате кортеж. Первой частью кортежа является значение >(), а второй частью является новый стек, который мы называем >newStack1. Затем мы выталкиваем число из >newStack1, что даёт в результате число >a (равно >3), которое мы протолкнули, и новый стек, названный нами >newStack2. Затем мы выталкиваем число из >newStack2 и получаем число и новый стек. Мы возвращаем кортеж с этим числом и новым стеком. Давайте попробуем:

>ghci> stackManip [5,8,2,1]

>(5,[8,2,1])

Результат равен >5, а новый стек – >[8,2,1]. Обратите внимание, как функция >stackManip сама является вычислением с состоянием. Мы взяли несколько вычислений с состоянием и как бы «склеили» их вместе. Хм-м, звучит знакомо.

Предшествующий код функции >stackManip несколько громоздок, потому как мы вручную передаём состояние каждому вычислению с состоянием, сохраняем его, а затем передаём следующему. Не лучше ли было бы, если б вместо того, чтобы передавать стек каждой функции вручную, мы написали что-то вроде следующего:

>stackManip = do

>   push 3

>   a <– pop

>   pop

Ла-адно, монада >State позволит нам делать именно это!.. С её помощью мы сможем брать вычисления с состоянием, подобные этим, и использовать их без необходимости управлять состоянием вручную.

Монада State

Модуль >Control.Monad.State предоставляет тип >newtype, который оборачивает вычисления с состоянием. Вот его определение:

>newtype State s a = State { runState :: s –> (a, s) }

Тип >State>s>a – это тип вычисления с состоянием, которое манипулирует состоянием типа >s и имеет результат типа >a.

Как и модуль >Control.Monad.Writer, модуль >Control.Monad.State не экспортирует свой конструктор значения. Если вы хотите взять вычисление с состоянием и обернуть его в