2016-10-01 94 views
2

我是Haskell的初学者,我遇到过我想使用状态monad的情况。 (或者至少,我认为我就是这么想的)。国家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中询问过这个问题。

回答

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) 
+0

非常感谢你,这是非常宝贵的。为了让它能够运行,我必须做出一些改变(我根本没有抱怨,这样做非常有益)。我不确定是将它们编辑为答案还是保留原样 - 如果您有偏好,请告诉我。 – Nathaniel

+0

@Nathaniel - 欢迎你做任何你想要的修改。 – Lee

+0

如果你有兴趣进一步帮助我有一个[关于问题](http://stackoverflow.com/questions/39813675/simulating-interacting-stateful-objects-in-haskell)关于如何在这种风格编程时我有多个交互的有状态对象。 – Nathaniel