我试图创建一个使用状态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]
答案 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)
,newState
被snd
丢弃。因此,下一个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