Как я могу написать этот простой код, используя монаду состояния?

Я новичок в Haskell, и я столкнулся с ситуацией, когда я хотел бы использовать государственную монаду. (Или, по крайней мере, я думаю, что это то, что я хотел бы использовать.) Есть миллион учебных пособий для государственной монады, но все они, похоже, предполагают, что моя главная цель - понять это на глубоком концептуальном уровне, и следовательно, они останавливаются непосредственно перед тем, как сказать, как на самом деле разрабатывать программное обеспечение с его помощью. Поэтому я ищу помощь с упрощенным практическим примером.

Ниже приведена очень простая версия того, как выглядит мой текущий код. Как вы можете видеть, я пропускаю состояние через свои функции, и мой вопрос заключается в том, как просто переписать код, используя do запись, так что мне не придется это делать.

data Machine = Register Int

addToState :: Machine -> Int -> Machine
addToState (Register s) a = Register $ s+a

subtractFromState :: Machine -> Int -> Machine
subtractFromState (Register s) a = Register (s-a)

getValue :: Machine -> Int
getValue (Register s) = s

initialState = Register 0

runProgram = getValue (subtractFromState (addToState initialState 6) 4)

Код имитирует простую абстрактную машину с единственным регистром и инструкциями для добавления в регистр, вычитания из него и получения его значения. "Программа" в конце инициализирует регистр в 0, добавляет к нему 6, вычитает 4 и возвращает результат, который, конечно, равен 2.

Я понимаю цель государственной монады (или, по крайней мере, думаю, что знаю), и я ожидаю, что это позволит мне переписать это так, что я получу что-то вроде

runProgram :: ???????
runProgram = do
    put 0
    addToState 6
    subtractFromState 4
    value <- getValue
    return value

Однако, несмотря на все уроки, которые я прочитал, я до сих пор не знаю, как преобразовать мой код в эту форму.

Конечно, состояние моей реальной машины намного сложнее, и я также передаю ее вывод (который будет передан на другую машину) и другие различные вещи, поэтому я очень заинтересован в том, чтобы упростить его. Знание того, как это сделать для этого упрощенного примера, очень помогло бы.

Обновление: после хорошего ответа Ли теперь я знаю, как это сделать, но я застрял в том, как писать код в одной и той же элегантной форме, когда у меня несколько взаимодействующих машин. Я спросил об этом в новом вопросе.

1 ответ

Решение

Сначала вам нужно преобразовать существующие функции для возврата State Machine a ценности:

import Control.Monad.State.Lazy

data Machine = Register Int

addToState :: Int -> State Machine ()
addToState i = do
        (Register x) <- get
        put $ Register (x + i)

subtractFromState :: Int -> State Machine ()
subtractFromState i = do
        (Register x) <- get
        put $ Register (x - i)

getValue :: State Machine Int
getValue = do
        (Register i) <- get
        pure i

тогда вы можете объединить их в вычисление с учетом состояния:

program :: State Machine Int
program = do
  addToState 6
  subtractFromState 4
  getValue

наконец, вам нужно запустить это вычисление с evalState чтобы получить окончательный результат и отказаться от состояния:

runProgram :: Int
runProgram = evalState program (Register 0)
Другие вопросы по тегам