Почему государство нуждается в ценности?

Просто изучая государственную монаду из этого превосходного 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 }

Ответ 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].