Почему государство нуждается в ценности?
Просто изучая государственную монаду из этого превосходного tutorial. Однако, когда я попытался объяснить это не программисту, у них возник вопрос, который меня насторожил.
Если целью государства является имитация измененной памяти, почему функция, которая хранит монады монадов, имеет тип:
s -> (a, s)
а не просто:
s -> s
Другими словами, какова потребность в "промежуточном" значении? Например, не могли бы мы в тех случаях, когда это нам нужно, имитировать его, просто определяя состояние как кортеж (state, value)
?
Я уверен, что я что-то смутил, любая помощь приветствуется.
Ответы
Ответ 1
Чтобы нарисовать параллель с императивным языком, подобным C, s -> s
соответствует функции с возвращаемым типом void
, которая вызывается исключительно для побочных эффектов (например, с изменением памяти). Он изоморфен State s ()
.
И действительно, можно писать функции C, которые передаются только через глобальные переменные. Но, как и в C, часто бывает удобно возвращать значения из функций. Для чего a
.
Конечно, возможно, что для вашей конкретной проблемы s -> s
- лучший выбор. Хотя это не Монада, это моноид (когда он завернут в Endo). Таким образом, вы можете построить такие функции с помощью <>
и mempty
, которые соответствуют >>=
и return
для Monad.
Ответ 2
Чтобы немного увеличить ответ Ника,
s
- это состояние. Если все ваши функции были s -> s
(состояние к состоянию), ваши функции не смогут возвращать какие-либо значения. Вы могли бы определить свое состояние как (the actual state, value returned)
, но это объединяет состояние со значением, которое вычисляются государственными функциями. И это также общий случай, что вы хотите, чтобы функции действительно вычисляли и возвращали значения...
Ответ 3
s' -> s'
эквивалентен (a, s) -> (a, s)
. Здесь очевидно, что вашему State
потребуется исходный a
, чтобы начать работу в дополнение к s
.
С другой стороны, s -> (a, s)
требуется только семя s
для начала и не требует значения a
вообще.
Таким образом, тип s -> (a, s)
говорит вам, что State
менее сложный, чем если бы он был (a, s) -> (a, s)
. Типы в Haskell передают LOTS информации.
Ответ 4
Если целью State
является имитация измененной памяти, то почему функция, которая хранит монады монадов, имеет тип:
s -> (a, s)
а не просто:
s -> s
Цель монады State
заключается не в том, чтобы имитировать изменчивую память, а в том, чтобы моделировать вычисления, которые производят значение и имеют побочный эффект. Просто, учитывая некоторое начальное состояние типа s
, ваше вычисление произведет некоторое значение типа a
, а также обновленное состояние.
Возможно, ваш расчет не дает значения... Тогда просто: тип значения a
- это просто ()
. Возможно, с другой стороны, ваш расчет не имеет побочного эффекта. Опять же, легко: вы можете подумать о своей функции перехода состояния (аргумент s -> s
в modify
) как о существовании id
. Но часто вы имеете дело с обоими в то же время.
Фактически вы можете использовать get
и put
как простые примеры:
get :: State s s -- s -> (s, s)
put :: s -> State () -- s -> (s -> ((), s))
-
get
- это вычисление, которое, учитывая текущее состояние (первый s
), вернет его как значение, т.е. результат вычисления, и как "новый" (немодифицированное) состояние.
-
put
- это вычисление, которое при новом состоянии (первое s
) и текущее состояние (второе s
) просто игнорирует текущее состояние. Он будет вырабатывать ()
в качестве вычисленного значения (потому что, конечно, он не вычислил никакого значения!) И зависает в новом состоянии.
Ответ 5
Предположительно, вы хотите использовать вычисления с учетом состояния внутри нотации do
?
Вы должны спросить себя, что будет выглядеть экземпляр Monad
для вычисления состояния, определенного
newtype State s = { runState :: s -> s }
Ответ 6
a
- это возвращаемое значение, а s
- конечное состояние.
http://www.haskell.org/haskellwiki/State_Monad#Implementation
Ответ 7
Задача, которую нужно решить, состоит в том, что у вас есть вход и ряд функций, и вы хотите применить функции к вводу по порядку.
Если функции являются чисто изменяющими состояние функциями, s -> s
на входе типа s
, вам не нужно использовать State
для их использования. Haskell очень хорошо сочетает функции, подобные этим, например. со стандартным композиционным оператором .
, или что-то вроде foldr (.) id
, или foldr id
.
Однако, если функции как мутируют состояние, так и сообщают о некоторых результатах этого, так что вы можете дать им тип s -> (s,a)
, а склеивать их все вместе немного неприятно. Вам необходимо распаковать кортеж результата и передать новое значение состояния следующей функции, использовать сообщаемое значение где-то еще, а затем распаковать этот результат и т.д. Легко передать неправильное состояние входной функции, потому что вы должны указать каждый результат и ввести явно для распаковки. Вы получите что-то вроде этого:
let
(res1, s1) = fun1 s0
(res2, s2) = fun2 s1
(res3, s3) = fun3 res1 res2 s1
...
in resN
Там я случайно прошел s1
вместо s2
, может быть, потому, что позже добавил вторую строку и не понял, что нужно изменить третью строку. При составлении функций s -> s
эта проблема не может возникнуть из-за того, что имена не имеют права:
let
resN = fun1 . fun2 . fun3 . -- etc.
Итак, мы изобрели State
, чтобы сделать тот же трюк. State
- это всего лишь способ склеивания таких функций, как s -> (s,a)
, таким образом, чтобы правильное состояние всегда передавалось правой функции.
Так что это не так много, что люди пошли "мы хотим использовать State
, пусть использовать s -> (s,a)
", а скорее "мы пишем такие функции, как s -> (s,a)
, придумаем State
, чтобы сделать это легко". С функциями s -> s
это уже легко и нам не нужно ничего придумывать.
В качестве примера того, как s -> (s,a)
возникает естественным образом, рассмотрим синтаксический анализ: синтаксическому анализатору будет предоставлен некоторый ввод, он будет потреблять часть ввода и возвращать значение. В Haskell это, естественно, моделируется как входной список и возвращает пару значений и оставшийся вход - т.е. [Input] -> ([Input], a)
или State [Input]
.