Путаница над кодом государственной Монады на тему "Учите вас в Haskell"
Я пытаюсь понять Haskell, используя онлайн-книгу Учите вас Haskell для отличного хорошего.
Я, насколько мне известно, смог понять Монады до тех пор, пока не попаду в главу, в которой представлена State Monad.
Однако код, представленный и заявленный как реализация типа Monad типа State (я не смог найти его в Hoogle), кажется слишком большим для меня.
-
Начнем с того, что я не понимаю логику, лежащую в ее основе, то есть, почему она должна работать и как автор рассматривал эту технику (могут быть предложены соответствующие статьи или справочные документы?)
-
В строке 4 предполагается, что функция f принимает 1 параметр.
Однако несколько строк вниз мы представляем поп, который не принимает параметров!
-
Чтобы расширить точку 1, то, что пытается сделать автор, используя функцию для представления состояния.
Любая помощь в понимании того, что происходит, очень ценится.
Изменить
К кому это может относиться,
Ниже приведены ответы на мои вопросы.
Одна вещь, которую я хотел бы добавить:
После прочтения статьи, предложенной ниже, я нашел ответ на мой второй пункт выше:
Все это время я предполагал, что функция pop будет использоваться как:
stuff >>= pop
, так как в типе связывания второй параметр является функцией, тогда как правильное использование - это pop >>= stuff
, которое я понял после прочтения снова, как do-notation транслируется на простой bind-lambdas.
Ответы
Ответ 1
Короткий ответ:
-
State
предназначен для использования функций монадов, чтобы имитировать императивно-подобное системное состояние с локальными переменными. Основная идея состоит в том, чтобы скрыть внутри монады активность взятия в текущем состоянии и возвращения нового состояния вместе с промежуточным результатом на каждом шаге (и здесь мы имеем s -> (a,s)
.
- Не допускайте ошибки с помощью функций
State
. Первый может иметь любой тип, который вам нужен (при условии, что в конечном итоге они произведут некоторый State a
, если вы хотите использовать их в государственной монаде). Последняя содержит функции типа s -> (a,s)
: это уровень передачи состояния, управляемый монадой.
- Как я уже сказал, функция, заключенная в
State
, фактически создается с помощью (>>=)
и return
, поскольку они определены для экземпляра Monad (State s)
. Его роль состоит в том, чтобы передать состояние через вызовы вашего кода.
Точка 3 также является причиной того, что параметр состояния исчезает из функций, фактически используемых в государственной монаде.
Длинный ответ:
Государственная Монада изучается в разных документах и существует также в рамках Haskell (я не помню хороших ссылок прямо сейчас, я добавлю их как можно скорее).
Это та идея, что это следует: рассмотрим тип data MyState = ...
, значения которого содержат текущее состояние системы.
Если вы хотите передать его через кучу функций, вы должны написать каждую функцию таким образом, чтобы она принимала по крайней мере текущее состояние в качестве параметра и возвращала вам пару с ее результатом (в зависимости от состояния и другие входные параметры) и новое (возможно, модифицированное) состояние. Ну, это именно то, о чем говорит вам тип государственной монады: s -> (a, s)
. В нашем примере s
является MyState
и предназначен для передачи состояния системы.
Функция, завернутая в State
, не принимает параметры, кроме текущего состояния, которое необходимо для создания в результате нового состояния и промежуточного результата. Функции с большим количеством параметров, которые вы видели в примерах, не являются проблемой, потому что, когда вы используете их в do
-notation внутри монады, вы примените их ко всем "дополнительным" необходимым параметрам, что означает, что каждый из они приведут к частично примененной функции, единственным оставшимся параметром которой является состояние; экземпляр monad для State
сделает все остальное.
Если вы посмотрите на тип функций (фактически, в монадах они обычно называются действиями), которые могут быть использованы в монаде, вы увидите, что тип результата вставляется в монаду: это точка, которая говорит вам, что как только вы дадите им все параметры, они фактически не вернут вам результат, но (в данном случае) функцию s -> (a,s)
, которая будет соответствовать законам композиции монады.
Вычисление будет выполнено путем передачи всему блоку/композиции первого/начального состояния системы.
Наконец, функции, которые не принимают параметры, будут иметь тип типа State a
, где a
- их возвращаемый тип: если вы посмотрите на конструктор значений для State
, вы снова увидите, что это на самом деле функция s -> (a,s)
.
Ответ 2
Монада State
представляет расчеты с состоянием, то есть вычисления, которые используют значения и, возможно, модифицируют какое-либо внешнее состояние. Когда вы последовательно выполняете вычисления с учетом состояния, последующие вычисления могут давать разные результаты в зависимости от того, как предыдущие вычисления изменяли состояние.
Так как функции в Haskell должны быть чистыми (т.е. не иметь побочных эффектов), мы имитируем эффект внешнего состояния, требуя, чтобы каждая функция принимала дополнительный параметр, представляющий текущее состояние мира, и возвращает дополнительное значение, представляющее модифицированное государство. Фактически, внешнее состояние пронизывается через последовательность вычислений, как в этой мерзости диаграммы, которую я просто рисовал в MSPaint:
![enter image description here]()
Обратите внимание, что каждый блок (представляющий вычисление) имеет один вход и два выхода.
Если вы посмотрите на экземпляр Monad
для State
, вы увидите, что определение (>>=)
указывает вам, как это сделать. В нем говорится, что для привязки вычисления состояния c0
к функции f
, которая принимает результаты вычисления с учетом состояния и возвращает другое вычисление с учетом состояния, мы делаем следующее:
- Запустите
c0
, используя начальное состояние s0
, чтобы получить результат и новое состояние: (val, s1)
- Подайте
val
функции f
, чтобы получить новое вычисление с учетом состояния, c1
- Запустите новое вычисление
c1
с измененным состоянием s1
Как это работает с функциями, которые уже принимают аргументы n
? Поскольку каждая функция в Haskell имеет значение по умолчанию, мы просто добавляем дополнительный аргумент (для состояния) в конец, а вместо нормального возвращаемого значения функция теперь возвращает пару, второй элемент которой является новым измененным состоянием. Поэтому вместо
f :: a -> b
теперь имеем
f :: a -> s -> (b, s)
Вы можете подумать, что
f :: a -> ( s -> (b, s) )
что является одним и тем же в Haskell (так как состав функций является правильным ассоциативным), который читает "f
- это функция, которая принимает аргумент типа a
и возвращает вычисление с учетом состояния". И это действительно все, что есть в монаде State
.
Ответ 3
Монада State
по существу
type State s a = s -> (a,s)
функция из одного состояния (s
) в пару желаемого результата (a
) и новое состояние. Реализация делает потоки состояния неявными и обрабатывает передачу состояния и обновление для вас, поэтому нет риска случайного перехода неправильного состояния к следующей функции.
Таким образом, функция, которая принимает аргументы k > 0
, одна из которых является состоянием и возвращает пару чего-то и новое состояние, в монаде State s
становится функцией, принимающей аргументы k-1
и возвращающей монадическое действие ( который в основном представляет собой функцию, принимающую один аргумент, состояние здесь).
В не-состоянии параметр pop
принимает один аргумент, стек, который является состоянием. Таким образом, в монадической настройке pop
становится действием State Stack Int
без явного аргумента.
Использование монады State
вместо явной передачи состояния делает для более чистого кода с меньшим количеством возможностей для ошибки, что делает монада State
. Все может быть сделано без него, это будет просто более громоздким и подверженным ошибкам.
Ответ 4
Я полный новичок в Haskell, и я не мог хорошо понять код государственной монады в этой книге. Но позвольте мне добавить мой ответ здесь, чтобы помочь кому-то в будущем.
Ответы:
-
Что они пытаются выполнить с помощью State Monad?
Составляющие функции, которые обрабатывают вычисления с учетом состояния.
например push 3 >>= \_ -> push 5 >>= \_ -> pop
-
Почему pop
не принимает никаких параметров, в то время как предлагается функция f
принимает 1 параметр?
pop
не принимает аргументов, потому что он обернут State
.
неустановленная функция, тип которой s -> (a, s)
принимает один аргумент.
то же самое для push
.
вы можете распаковать с помощью runState
.
runState pop :: Stack -> (Int, Stack)
runState (push 3) :: Stack -> ((), Stack)
если вы имеете в виду правую часть >>=
с помощью функции f
", f
будет выглядеть как \a -> pop
или \a -> push 3
, а не только pop
.
Длинное пояснение:
Эти 3 вещи помогли мне лучше понять пример штата Монада и Stack.
-
Рассмотрим типы аргументов для оператора bind (>>=
)
Определение оператора bind в стандартном классе Monad - это
(>>=) :: (Monad m) => m a -> (a -> m b) -> m b
В примере Stack m
есть State Stack
.
Если мы ментально заменим m
на State Stack
, определение может быть таким.
(>>=) :: State Stack a -> (a -> State Stack b) -> State Stack b
Следовательно, тип аргумента левой стороны для оператора привязки будет State Stack a
.
А правая сторона будет a -> State Stack b
.
-
Перевести обозначение для привязки оператора
Вот пример кода, использующего обозначения в книге.
stackManip :: State Stack Int
stackManip = do
push 3
pop
pop
он может быть переведен на следующий код с оператором bind.
stackManip :: State Stack Int
stackManip = push 3 >>= \_ -> pop >>= \_ -> pop
Теперь мы можем видеть, какая будет правая сторона для оператора bind.
Их типы a -> State Stack b
.
(\_ -> pop) :: a -> State Stack Int
(\_ -> push 3) :: a -> State Stack ()
-
Признать разницу между (State s)
и (State h)
в объявлении экземпляра
Вот объявление экземпляра для состояния в книге.
instance Monad (State s) where
return x = State $ \s -> (x,s)
(State h) >>= f = State $ \s -> let (a, newState) = h s
(State g) = f a
in g newState
Учитывая типы с примером Stack, тип (State s)
будет
(State s) :: State Stack
s :: Stack
И тип (State h)
будет
(State h) :: State Stack a
h :: Stack -> (a, Stack)
(State h)
- это аргумент левой стороны оператора связывания, а его тип State Stack a
, как описано выше.
Тогда почему h
становится Stack -> (a, Stack)
?
Это результат сопоставления шаблонов с конструктором значений состояния, который определен в оболочке newtype. То же самое относится к (State g)
.
newtype State s a = State { runState :: s -> (a,s) }
В общем случае тип h
- это s ->(a, s)
, представление вычисления состояния. Любое из следующих действий может быть h
в примере Stack.
runState pop :: Stack -> (Int, Stack)
runState (push 3) :: Stack -> ((), Stack)
runState stackManip :: Stack -> (Int, Stack)
что он.