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

时间:2016-10-01 14:24:29

标签: haskell monads 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)

代码模拟一个简单的抽象机器,它有一个寄存器,添加到寄存器的指令从中减去,并得到它的值。最后的“程序”将寄存器初始化为0,向其中加6,减去4并返回结果,当然为2。

我理解州monad的目的(或者至少认为我这样做),我希望它能让我重新编写这个,以便我最终得到像

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

然而,尽管我读过所有教程,但我仍然不太清楚如何将代码转换为这种形式。

当然,我的实际机器的状态要复杂得多,而且我也传递它的输出(将传递给另一台机器)和其他各种东西,所以我非常希望简化它。知道如何为这个简化的例子做这件事将是一个非常好的帮助。

在李的好回答之后

更新:我现在知道如何做到这一点,但是当我有多台交互式机器时,我仍然坚持如何以相同的优雅形式编写代码。我已在a new question中询问过这个问题。

1 个答案:

答案 0 :(得分:7)

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