我如何使用状态 monad 编写这个简单的代码?

How can I write this simple code using the state monad?

我是 Haskell 的初学者,我遇到过我想使用状态 monad 的情况。 (或者至少,我认为这就是我想要使用的。)状态 monad 有上百万个教程,但它们似乎都假定我的主要目标是在深层概念层面上理解它,并且因此,他们就在他们说如何用它实际开发软件的部分之前停下来。所以我正在寻找一个简化的实际示例的帮助。

下面是我当前代码的一个非常简单的版本。如您所见,我正在通过我的函数处理状态,我的问题只是如何使用 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)

代码模拟了一个简单的抽象机,它有一个寄存器,以及添加到寄存器、减去寄存器和获取寄存器值的指令。最后的 "program" 将寄存器初始化为 0,加 6,减 4 和 returns 结果,当然是 2.

我理解 state monad 的目的(或者至少我认为是这样),我希望它能让我重写这个,这样我最终得到类似

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

然而,尽管我已经阅读了所有教程,但我仍然不太清楚如何将我的代码转换成这种形式。

当然,我的实际机器的状态要复杂得多,而且我还在传递它的输出(将传递到另一台机器)和各种其他东西,所以我很想简化它。知道如何为这个简化的例子做这件事会有很大的帮助。

更新: 在 Lee 的出色回答之后,我现在知道如何做到这一点,但是当我有多个交互机器时,我仍然坚持如何以相同的优雅形式编写代码.我已经在 a new question.

中询问过这个问题

首先,您需要将现有函数转换为 return 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)