Как я могу написать этот простой код, используя монаду состояния?
Я новичок в 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)