Monad State未更新

时间:2019-07-27 05:29:32

标签: haskell state monads

我试图创建一个使用状态monad来存储游戏的当前状态的游戏,该状态需要一个语句(命令)列表来返回动作列表。

单独地,turbo命令起作用,但是顺序执行,先前的命令对当前命令没有任何影响。

我不太了解的是这些状态应该如何传播到下一个命令?以下是如果我手动运行代码该怎么办:

s0 = turbo (PenDown)
s1 = turbo (Forward (RLit 50))
s2 = turbo (Turn (RLit 90))
s3 = turbo (Forward (RLit 50))
s4 = turbo (Turn (RLit 90))
s5 = turbo (Forward (RLit 50))

a1 = snd (deState s1 (fst (deState s1 (fst (deState s0 initTurboMem)))))
a2 = snd (deState s3 (fst (deState s2 (fst (deState s1 (fst (deState s1 (fst (deState s0 initTurboMem)))))))))
a3 = snd (deState s5 (fst (deState s4 (fst (deState s3 (fst (deState s2 (fst (deState s1 (fst (deState s1 (fst (deState s0 initTurboMem)))))))))))))
a = a1 ++ a2 ++ a3

这会给出答案,但是我不确定如何在下面的代码中完成它。

要运行代码,请使用以下

stmt = Seq [ 
             PenDown
           , Forward (RLit 50)
           , Turn (RLit 90)    
           , Forward (RLit 50)    
           , Turn (RLit 90)    
           , Forward (RLit 50)   
           ]  
snd (deState (turbo stmt) initTurboMem)

此处涉及的函数未考虑先前的语句

turbo (Seq [x]) = turbo x
turbo (Seq (x:xs)) = do
    state <- get
    let a0 = snd (deState (turbo x) state)
    state <- get
    let a1 = snd (deState (turbo (Seq xs)) state)
    pure (a0 ++ a1)

其余是这些功能

turbo :: Stmt -> State TurboMem [SVGPathCmd]
turbo (var := expr) = do
    state <- get
    let val = snd (deState (evalReal expr) state)
    setVar var val
    pure []
turbo PenDown = do
    setPen True
    pure []
turbo PenUp = do
    setPen False
    pure []
turbo (Turn expr) = do
    state <- get
    let angle = snd (deState (evalReal expr) state)
    turn angle
    pure []
turbo (Forward expr) = do
    state <- get
    let angle = snd (deState (getAngle) state)
        dist = snd (deState (evalReal expr) state)
        x = dist * cos (angle * pi / 180)
        y = dist * sin (angle * pi / 180)
        pen = snd (deState (getPen) state)
    if pen then pure [LineTo x y] else pure [MoveTo x y]

加速状态

data TurboMem = TurboMem (Map String Double) Double Bool
    deriving (Eq, Show)

表达式和语句

data RealExpr
    = RLit Double               -- literal/constant
    | RVar String               -- read var's current value
                                -- if uninitialized, the answer is 0
    | Neg RealExpr              -- unary minus
    | RealExpr :+ RealExpr      -- plus
    | RealExpr :- RealExpr      -- minus
    | RealExpr :* RealExpr      -- times
    | RealExpr :/ RealExpr      -- divide
    deriving (Eq, Ord, Read, Show)
data Stmt
    = String := RealExpr        -- assignment, the string is var name
    | PenDown                   -- set pen to down (touch paper) state
    | PenUp                     -- set pen to up (away from paper) state
    | Turn RealExpr             -- turn counterclockwise by given degrees
                                -- negative angle just means clockwise
    | Forward RealExpr          -- move by given distance units (in current direction)
                                -- negative distance just means backward
                                -- if pen is down, this causes drawing too
                                -- if pen is up, this moves without drawing
    | Seq [Stmt]                -- sequential compound statement. run in given order
    deriving (Eq, Ord, Read, Show)
data SVGPathCmd = MoveTo Double Double -- move without drawing
                | LineTo Double Double -- draw and move
    deriving (Eq, Ord, Read, Show)

Helper函数来操纵状态

-- Get current direction.
getAngle :: State TurboMem Double
-- Change direction by adding the given angle.
turn :: Double -> State TurboMem ()
-- Get pen state.
getPen :: State TurboMem Bool
-- Set pen state.
setPen :: Bool -> State TurboMem ()
-- Get a variable's current value.
getVar :: String -> State TurboMem Double
-- Set a variable to value.
setVar :: String -> Double -> State TurboMem ()

初始状态

initTurboMem = TurboMem Map.empty 0 False

我希望结果是

[LineTo 50.0 0.0,LineTo 0.0 50.0,LineTo -50.0 0.0]

但是我真正得到的是

[MoveTo 50.0 0.0,MoveTo 50.0 0.0,MoveTo 50.0 0.0]

1 个答案:

答案 0 :(得分:4)

这是错误的:

turbo (Seq [x]) = turbo x
turbo (Seq (x:xs)) = do
    state <- get
    let a0 = snd (deState (turbo x) state)
    state <- get
    let a1 = snd (deState (turbo (Seq xs)) state)
    pure (a0 ++ a1)

在这里,deState (turbo x) state返回一对(newState, a0)newStatesnd丢弃。因此,下一个state <- get将再次读取原始状态。本质上,{mon}中的状态在Seq (x:xs)执行期间始终不会改变。

这里的问题是您在单子计算中使用deState。您不应该这样做,因为这需要您手动跟踪什么是“当前”状态,并像这样传递它:

let (state0,a0) = deState (turbo x0) state
    (state1,a1) = deState (turbo x1) state0
    (state2,a2) = deState (turbo x2) state1
    ...

以这种方式写是可行的,但是状态monad可以避免的正是精确!我们应该改写

a0 <- turbo x0
a1 <- turbo x1
a2 <- turbo x2
...

让单子处理状态传递样板。

我将重写turbo (Seq ...)的情况,如下所示:

turbo (Seq []) = pure []
turbo (Seq (x:xs)) = do
    a0 <- turbo x
    a1 <- turbo (Seq xs)
    pure (a0 ++ a1)

要简单得多,因为从现在起turbo x就像命令式语言中的函数调用一样,它可以修改状态变量而产生副作用-这就是状态monad的作用。我们不必显式跟踪当前状态并将其传递。

尝试删除代码中deState的所有其他用法。您只应使用deState一次:在turbo之外,当您“退出monad”并且返回类型不再为State TurboMem something

形式时