Поначалу государственная монада выглядит немного запутанно; давайте сделаем, как предложил Норман Рэмси, и рассмотрим, как реализовать с нуля. Внимание, это довольно долго!
Во-первых, State имеет два типа параметров: тип содержащихся данных о состоянии и тип конечного результата вычисления . Мы будем использовать stateData
и result
соответственно в качестве переменных типа для них здесь. Это имеет смысл, если вы думаете об этом; Определяющая характеристика вычислений на основе состояния заключается в том, что они изменяют состояние при создании выходных данных.
Менее очевидно, что конструктор типов переводит функцию из состояния в измененное состояние и приводит к следующему результату:
newtype State stateData result = State (stateData -> (result, stateData))
Таким образом, в то время как монада называется «Состояние», фактическое значение, заключенное в нее, равно вычислению на основе состояния , а не фактическому значению содержащегося состояния.
Имея это в виду, мы не должны удивляться, обнаружив, что функция runState
, используемая для выполнения вычислений в монаде State, на самом деле является не чем иным, как средством доступа к самой обернутой функции, и может быть определена следующим образом :
runState (State f) = f
Так что это значит, когда вы определяете функцию, которая возвращает значение State? Давайте на мгновение проигнорируем тот факт, что State является монадой, и просто посмотрим на основные типы. Сначала рассмотрим эту функцию (которая на самом деле ничего не делает с состоянием):
len2State :: String -> State Int Bool
len2State s = return ((length s) == 2)
Если вы посмотрите на определение State, мы увидим, что здесь тип stateData
равен Int
, а тип result
равен Bool
, поэтому функция, заключенная в конструктор данных, должна иметь тип Int -> (Bool, Int)
. Теперь представьте версию len2State
без состояния - очевидно, она будет иметь тип String -> Bool
. Итак, как бы вы преобразовали такую функцию в функцию, возвращающую значение, которое помещается в оболочку State?
Что ж, очевидно, что преобразованной функции нужно будет принять второй параметр, Int
, представляющий значение состояния. Также необходимо вернуть значение состояния, другое Int
. Поскольку мы на самом деле ничего не делаем с состоянием в этой функции, давайте просто сделаем очевидную вещь - пропустим этот int насквозь. Вот функция в форме состояния, определенная в терминах версии без состояния:
len2 :: String -> Bool
len2 s = ((length s) == 2)
len2State :: String -> (Int -> (Bool, Int))
len2State s i = (len2' s, i)
Но это глупо и излишне. Давайте обобщим преобразование, чтобы мы могли передать значение результата и превратить что-либо в функцию, подобную состоянию.
convert :: Bool -> (Int -> (Bool, Int))
convert r d = (r, d)
len2 s = ((length s) == 2)
len2State :: String -> (Int -> (Bool, Int))
len2State s = convert (len2 s)
Что если нам нужна функция, которая изменяет состояние? Очевидно, что мы не можем построить один с convert
, так как мы написали это для прохождения состояния. Давайте будем проще и напишем функцию для перезаписи состояния новым значением. Какой тип будет нужен? Для нового значения состояния понадобится Int
, и, конечно, придется возвращать функцию stateData -> (result, stateData)
, потому что это то, что нужно нашему упаковщику состояний. Перезапись значения состояния на самом деле не имеет разумного значения result
за пределами вычисления State, поэтому наш результат здесь будет просто ()
, кортеж с нулевым элементом, который представляет "нет значения" в Haskell.
overwriteState :: Int -> (Int -> ((), Int))
overwriteState newState _ = ((), newState)
Это было легко! Теперь давайте на самом деле сделаем что-то с этими данными состояния. Давайте перепишем len2State
сверху во что-то более разумное: сравним длину строки с текущим значением состояния.
lenState :: String -> (Int -> (Bool, Int))
lenState s i = ((length s) == i, i)
Можем ли мы обобщить это в преобразователь и функцию без состояния, как мы делали раньше? Не так легко. Наша функция len
должна будет принимать состояние в качестве аргумента, но мы не хотим, чтобы она «знала» о состоянии. Действительно неловко. Однако мы можем написать быструю вспомогательную функцию, которая обрабатывает все для нас: мы дадим ей функцию, которая должна использовать значение состояния, и она передаст значение, а затем упакует все обратно в функцию в форме состояния. оставив len
ни одного мудрее.
useState :: (Int -> Bool) -> Int -> (Bool, Int)
useState f d = (f d, d)
len :: String -> Int -> Bool
len s i = (length s) == i
lenState :: String -> (Int -> (Bool, Int))
lenState s = useState (len s)
Теперь сложная часть - что если мы хотим связать эти функции вместе? Допустим, мы хотим использовать lenState
для строки, затем удвоить значение состояния, если результат равен false, затем проверить строку еще раз и, наконец, вернуть true, если какая-либо проверка прошла. У нас есть все части, которые нам нужны для этой задачи, но написать все это было бы больно. Можем ли мы создать функцию, которая автоматически связывает воедино две функции, каждая из которых возвращает функции, подобные состоянию? Конечно, вещь! Нам просто нужно убедиться, что он принимает в качестве аргументов две вещи: функцию State, возвращаемую первой функцией, и функцию, которая принимает тип result
предыдущей функции в качестве аргумента. Посмотрим, как получится:
chainStates :: (Int -> (result1, Int)) -> (result1 -> (Int -> (result2, Int))) -> (Int -> (result2, Int))
chainStates prev f d = let (r, d') = prev d
in f r d'
Все, что это делает, это применяет первую функцию состояния к некоторым данным состояния, затем применяет вторую функцию к результату и измененным данным состояния. Просто, правда?
Теперь интересная часть: между chainStates
и convert
мы почти сможем превратить любую комбинацию функций без состояния в функцию с поддержкой состояния! Единственное, что нам сейчас нужно, - это замена useState
, которая возвращает данные о состоянии в качестве результата, так что chainStates может передать их функциям, которые ничего не знают о приеме, который мы применяем. Также мы будем использовать лямбда-выражения, чтобы принять результат от предыдущих функций и дать им временные имена. Хорошо, давайте сделаем это так:
extractState :: Int -> (Int, Int)
extractState d = (d, d)
chained :: String -> (Int -> (Bool, Int))
chained str = chainStates extractState $ \state1 ->
let check1 = (len str state1) in
chainStates (overwriteState (
if check1
then state1
else state1 * 2)) $ \ _ ->
chainStates extractState $ \state2 ->
let check2 = (len str state2) in
convert (check1 || check2)
И попробуйте:
> chained "abcd" 2
(True, 4)
> chained "abcd" 3
(False, 6)
> chained "abcd" 4
(True, 4)
> chained "abcdef" 5
(False, 10)
Конечно, мы не можем забывать, что State на самом деле является монадой, которая оборачивает функции, подобные State, и удерживает нас от них, поэтому ни одна из наших изящных функций, которые мы создали, не поможет нам с реальной вещью. Или они будут? В шокирующем повороте оказывается, что настоящая государственная монада предоставляет все те же функции под разными именами:
runState (State s) = s
return r = State (convert r)
(>>=) s f = State (\d -> let (r, d') = (runState s) d in
runState (f r) d')
get = State extractState
put d = State (overwriteState d)
Обратите внимание, что >> = почти идентично chainStates, но не было хорошего способа определить его с помощью chainStates. Итак, чтобы подвести итоги, мы можем переписать последний пример, используя real State:
chained str = get >>= \state1 ->
let check1 = (len str state1) in
put (if check1
then state1 else state1 * 2) >>= \ _ ->
get >>= \state2 ->
let check2 = (len str state2) in
return (check1 || check2)
Или, все засахарено с эквивалентной нотацией:
chained str = do
state1 <- get
let check1 = len str state1
_ <- put (if check1 then state1 else state1 * 2)
state2 <- get
let check2 = (len str state2)
return (check1 || check2)