【问题标题】:Monad State not UpdatingMonad 状态未更新
【发布时间】:2019-07-27 05:29:32
【问题描述】:

我正在尝试创建一个状态单子来存储游戏的当前状态,该状态接受语句(命令)列表以返回操作列表。

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)

操作状态的辅助函数

-- 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]

【问题讨论】:

  • 快速提问:简而言之,turbo 应该做什么?了解预期目的可能有助于诊断问题。
  • @bradrn turbo 应该接受Stmt 并返回State,结果TurboMem(存储地图和变量的状态)和SVGPathCmd 的列表(这只是由声明确定的MoveTo x yLineTo x y)。

标签: haskell state monads


【解决方案1】:

这是错误的:

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 &lt;- get 将再次读取原始状态。从本质上讲,“在 monad 中”的状态在 Seq (x:xs) 的整个执行过程中永远不会改变,当它应该改变时。

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

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

以这种方式编写是可行的,但 正是 state monad 有助于避免的!我们应该改写

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

让 monad 处理状态传递样板。

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

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

简单得多,因为现在turbo x 感觉就像是命令式语言中的函数调用,它可以修改状态变量并产生副作用——这就是状态单子的意义所在。我们不必明确地跟踪当前状态并传递它。

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

【讨论】:

  • 感谢您的回复。我想知道是否需要更改检查以下if (snd (deState (isVarBetween var from to) state)) then... 的方式,假设isVarBetween :: String -&gt; Double-&gt; Double-&gt; State TurboMem Bool
  • @loiuytre35 您可以在下一行使用between &lt;- isVarBetween var from to,然后使用if between then ...
  • 所以你真的不需要在任何地方使用deState,除了为初始状态执行的第一行代码。
  • @loiuytre35 是的,完全正确:您只需在一开始就使用它一次即可通过初始状态。
  • 为什么要显式递归?这不是一个完美的地方吗? concatMapM?
猜你喜欢
  • 1970-01-01
  • 2018-06-29
  • 2015-06-11
  • 2017-05-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-05
相关资源
最近更新 更多