Haskell 状态单子在 2 维中跟踪运动
Haskell State monad in tracking movement in 2 dimensions
我正在尝试在二维平面上跟随一个物体的移动,该物体已获得命令列表 "forward, left or right"。
到目前为止,我的函数接受对象状态(方向、位置和移动)的组成部分和 return 所有移动完成后或所有位置沿方式。
对象的状态采用 Sstate (Dir dx dy) (Pos px py) [m]
形式,移动包括递归应用移动列表的头部以生成新状态
即Sstate (Dir 1 0) (Pos 0 0) "fff" -> Sstate (Dir 1 0) (Pos 0 1) "ff"
type Mov = Char
data Pos = Pos Int Int
deriving (Show, Eq)
data Dir = Dir Int Int
deriving (Show, Eq)
data Sstate = Sstate Dir Pos [Mov]
deriving (Show, Eq)
move :: Sstate -> Sstate
move (Sstate (Dir dx dy) (Pos px py) [] ) = Sstate (Dir dx dy) (Pos px py) []
move (Sstate (Dir dx dy) (Pos px py) (m:ms))
| m == 'f' = ss dx dy (dx + px) (dy + py) ms
| m == 'l' = ss (-dy) dx px py ms
| m == 'r' = ss dy (-dx) px py ms
| otherwise = ss dy dx px py []
where
ss a b c d = Sstate (Dir a b) (Pos c d)
end :: Dir -> Pos -> [Mov] -> Sstate
end d p ms = iterate move initialState !! n
where
initialState = Sstate d p ms
n = length ms + 1
position :: Sstate -> Pos
position (Sstate _ p _) = p
route :: Dir -> Pos -> [Mov] -> [Pos]
route d p ms = map position . take n . iterate move $ initialState
where
initialState = Sstate d p ms
n = length ms + 1
给予
λ: let x = Sstate (Dir 0 1) (Pos 0 2) "ff"
λ: move x
Sstate (Dir 0 1) (Pos 0 3) "f"
λ: end (Dir 0 1) (Pos 0 2) "ff"
Sstate (Dir 0 1) (Pos 0 4) ""
λ: route (Dir 0 1) (Pos 0 2) "ff"
[Pos 0 2,Pos 0 3,Pos 0 4]
这似乎可行,但似乎这也是需要 State monad
的东西。我觉得 State monad
令人困惑,但我觉得如果有人足够友好地展示它如何在这里使用,它会帮助我理解。
这里是一些简单的 'starter' 代码,通过在状态方面进行一些重新表述来扩展您的模块。我认为,您需要在摆弄它们的同时查看像 LYAH 章节这样的教程。我省略了签名,它变得越来越复杂,但查询 ghci 中的类型将很有启发性。您需要添加
import Control.Monad.State
import Control.Monad.Writer -- for the position-remembering example
那么下面的内容应该都可以使用你对 move
的定义
step = do -- step the state once with your `move`,
sstate <- get -- whatever the state is
put (move sstate)
-- this little program could also be written: `modify move` which shows the
-- link between what you wrote and State a little more clearly
steps = do -- repeatedly apply `step` to the state
Sstate _ _ ls <- get -- til there are no moves, then stop
if null ls
then return () -- could be: unless (null ls) $ do step ; steps
else step >> steps
stepsIO = do -- do all steps as with `steps`, but print
sstate@(Sstate a b ls) <- get -- the current state at each state update
liftIO $ print sstate
if null ls then liftIO (putStrLn "Done!")
else step >> stepsIO
stepsPrintPosition = do -- do all steps as with `steps`, printing
Sstate _ b ls <- get -- only position at each state update
liftIO $ do putStr "current position: "
print b
if null ls then liftIO (putStrLn "Done!")
else do step
stepsPrintPosition
stepsAccumulatePositions = do -- move through all states as with `steps`
sstate@(Sstate a b ls) <- get -- but use `tell` to keep adding the current
tell [b] -- position to the underlying list
if null ls then return () -- of positions
else step >> stepsAccumulatePositions
example = Sstate (Dir 0 1) (Pos 0 2) "ffff"
要使用 step
、steps
、stepsIO
等,我们应用 runState
;这给了我们一个从一个状态到一个新状态的函数
runStateT :: StateT s m a -> s -> m (a, s)
这当然只是新类型定义的访问器
newtype StateT s m a = StateT {runStateT :: s -> m (a, s)}
包装允许我们使用更简单的 s -> m (a, s)
位来编写花哨的 s -> m (a, s)
东西,但在新类型引擎盖下,它始终只是我们在 do 中编写的函数 s -> m (a, s)
符号。
当然,一旦我们用 runStateT
解包并得到我们的函数 s -> m (a, s)
,我们需要为它提供一个初始状态。通过在 ghci
中进行测试,最容易看出这是如何工作的
>>> example
Sstate (Dir 0 1) (Pos 0 2) "ffff"
>>> runStateT step example -- we step the state once with move
((),Sstate (Dir 0 1) (Pos 0 3) "fff")
>>> runStateT steps example -- we keep stepping till there are no moves
((),Sstate (Dir 0 1) (Pos 0 6) "")
>>> runStateT stepsIO example -- we print state at each state update
Sstate (Dir 0 1) (Pos 0 2) "ffff"
Sstate (Dir 0 1) (Pos 0 3) "fff"
Sstate (Dir 0 1) (Pos 0 4) "ff"
Sstate (Dir 0 1) (Pos 0 5) "f"
Sstate (Dir 0 1) (Pos 0 6) ""
Done!
((),Sstate (Dir 0 1) (Pos 0 6) "")
>>> runStateT stepsPrintPosition example -- print position only at state updates
current position: Pos 0 2
current position: Pos 0 3
current position: Pos 0 4
current position: Pos 0 5
current position: Pos 0 6
Done!
((),Sstate (Dir 0 1) (Pos 0 6) "")
-- the WriterT examples accumulate a 'monoid' of things you keep
-- adding to with `tell xyz` Here we accumulate a [Position]
-- execXYZ and evalXYZ, where they exist, return less information than runXYZ
>>> runWriterT $ runStateT stepsAccumulatePositions example
(((),Sstate (Dir 0 1) (Pos 0 6) ""),[Pos 0 2,Pos 0 3,Pos 0 4,Pos 0 5,Pos 0 6])
>>> execWriterT $ evalStateT stepsAccumulatePositions example
[Pos 0 2,Pos 0 3,Pos 0 4,Pos 0 5,Pos 0 6]
在上面的代码中,我使用 mtl
类型 classes 然后使用 runStateT
和 runWriterT
到 "interpret" 或专门化 class 涉及签名。这些属于 Control.Monad.Trans.{State/Writer}
中定义的具体类型 StateT
和 WriterT
可以省略 classes,直接用这些具体类型编写,导入那些模块。唯一的区别是,在我组合两种效果、状态和书写或任何你想称呼它的情况下,你需要做 lift $ tell [b]
。
关于您正在使用的状态分析有很多要说的,但如果您仔细考虑以上内容,就会发现您可以如何对其进行修改。
注意这里不需要直接使用State
monad,因为end
和route
可以用foldl'
和[=18=来写] 一旦您将 Mov
视为对某个状态 操作 的东西,而不是状态的 一部分 。 Sstate
的记录语法也有帮助。
type Mov = Char
data Pos = Pos Int Int deriving (Show, Eq)
data Dir = Dir Int Int deriving (Show, Eq)
data Sstate = Sstate {direction :: Dir, position :: Pos} deriving (Show, Eq)
-- A helper to simplify move
changeDir :: Mov -> Dir -> Dir
changeDir 'l' (Dir x y) = Dir (-y) x
changeDir 'r' (Dir x y) = Dir y (-x)
changeDir m (Dir x y) = Dir y x
move :: Mov -> Sstate -> Sstate
move 'f' oldState@(Sstate (Dir dx dy) (Pos px py)) = oldState { position = Pos (dx + px) (dy + py) }
move m oldState@(Sstate dir _) = oldState { direction = changeDir m dir }
end :: [Mov] -> Sstate -> Sstate
end ms initialState = foldl' (flip move) initialState ms
route :: [Mov] -> Sstate -> [Pos]
route ms initialState = map position $ scanl' (flip move) initialState ms
然后你的例子变成:
λ: let x = Sstate {direction = Dir 0 1, position = Pos 0 2}
λ: move 'f' x
Sstate {direction = Dir 0 1, position = Pos 0 3}
λ: end "ff" x
Sstate {direction = Dir 0 1, position = Pos 0 4}
λ: route "ff" x
[Pos 0 2,Pos 0 3,Pos 0 4]
我会把它留作练习(或留给比我知识渊博且懒惰的人)以适应
move :: Mov -> Sstate -> Sstate
end :: [Mov] -> Sstate -> Sstate
route :: [Mov] -> Sstate -> [Pos]
到
move :: Mov -> State Sstate ()
-- or possibly move :: Mov -> State Sstate Sstate ?
-- Like I said, more knowledgeable than me
end :: [Mov] -> State Sstate Sstate
route :: [Mov] -> State Sstate [Pos]
我正在尝试在二维平面上跟随一个物体的移动,该物体已获得命令列表 "forward, left or right"。
到目前为止,我的函数接受对象状态(方向、位置和移动)的组成部分和 return 所有移动完成后或所有位置沿方式。
对象的状态采用 Sstate (Dir dx dy) (Pos px py) [m]
形式,移动包括递归应用移动列表的头部以生成新状态
即Sstate (Dir 1 0) (Pos 0 0) "fff" -> Sstate (Dir 1 0) (Pos 0 1) "ff"
type Mov = Char
data Pos = Pos Int Int
deriving (Show, Eq)
data Dir = Dir Int Int
deriving (Show, Eq)
data Sstate = Sstate Dir Pos [Mov]
deriving (Show, Eq)
move :: Sstate -> Sstate
move (Sstate (Dir dx dy) (Pos px py) [] ) = Sstate (Dir dx dy) (Pos px py) []
move (Sstate (Dir dx dy) (Pos px py) (m:ms))
| m == 'f' = ss dx dy (dx + px) (dy + py) ms
| m == 'l' = ss (-dy) dx px py ms
| m == 'r' = ss dy (-dx) px py ms
| otherwise = ss dy dx px py []
where
ss a b c d = Sstate (Dir a b) (Pos c d)
end :: Dir -> Pos -> [Mov] -> Sstate
end d p ms = iterate move initialState !! n
where
initialState = Sstate d p ms
n = length ms + 1
position :: Sstate -> Pos
position (Sstate _ p _) = p
route :: Dir -> Pos -> [Mov] -> [Pos]
route d p ms = map position . take n . iterate move $ initialState
where
initialState = Sstate d p ms
n = length ms + 1
给予
λ: let x = Sstate (Dir 0 1) (Pos 0 2) "ff"
λ: move x
Sstate (Dir 0 1) (Pos 0 3) "f"
λ: end (Dir 0 1) (Pos 0 2) "ff"
Sstate (Dir 0 1) (Pos 0 4) ""
λ: route (Dir 0 1) (Pos 0 2) "ff"
[Pos 0 2,Pos 0 3,Pos 0 4]
这似乎可行,但似乎这也是需要 State monad
的东西。我觉得 State monad
令人困惑,但我觉得如果有人足够友好地展示它如何在这里使用,它会帮助我理解。
这里是一些简单的 'starter' 代码,通过在状态方面进行一些重新表述来扩展您的模块。我认为,您需要在摆弄它们的同时查看像 LYAH 章节这样的教程。我省略了签名,它变得越来越复杂,但查询 ghci 中的类型将很有启发性。您需要添加
import Control.Monad.State
import Control.Monad.Writer -- for the position-remembering example
那么下面的内容应该都可以使用你对 move
step = do -- step the state once with your `move`,
sstate <- get -- whatever the state is
put (move sstate)
-- this little program could also be written: `modify move` which shows the
-- link between what you wrote and State a little more clearly
steps = do -- repeatedly apply `step` to the state
Sstate _ _ ls <- get -- til there are no moves, then stop
if null ls
then return () -- could be: unless (null ls) $ do step ; steps
else step >> steps
stepsIO = do -- do all steps as with `steps`, but print
sstate@(Sstate a b ls) <- get -- the current state at each state update
liftIO $ print sstate
if null ls then liftIO (putStrLn "Done!")
else step >> stepsIO
stepsPrintPosition = do -- do all steps as with `steps`, printing
Sstate _ b ls <- get -- only position at each state update
liftIO $ do putStr "current position: "
print b
if null ls then liftIO (putStrLn "Done!")
else do step
stepsPrintPosition
stepsAccumulatePositions = do -- move through all states as with `steps`
sstate@(Sstate a b ls) <- get -- but use `tell` to keep adding the current
tell [b] -- position to the underlying list
if null ls then return () -- of positions
else step >> stepsAccumulatePositions
example = Sstate (Dir 0 1) (Pos 0 2) "ffff"
要使用 step
、steps
、stepsIO
等,我们应用 runState
;这给了我们一个从一个状态到一个新状态的函数
runStateT :: StateT s m a -> s -> m (a, s)
这当然只是新类型定义的访问器
newtype StateT s m a = StateT {runStateT :: s -> m (a, s)}
包装允许我们使用更简单的 s -> m (a, s)
位来编写花哨的 s -> m (a, s)
东西,但在新类型引擎盖下,它始终只是我们在 do 中编写的函数 s -> m (a, s)
符号。
当然,一旦我们用 runStateT
解包并得到我们的函数 s -> m (a, s)
,我们需要为它提供一个初始状态。通过在 ghci
>>> example
Sstate (Dir 0 1) (Pos 0 2) "ffff"
>>> runStateT step example -- we step the state once with move
((),Sstate (Dir 0 1) (Pos 0 3) "fff")
>>> runStateT steps example -- we keep stepping till there are no moves
((),Sstate (Dir 0 1) (Pos 0 6) "")
>>> runStateT stepsIO example -- we print state at each state update
Sstate (Dir 0 1) (Pos 0 2) "ffff"
Sstate (Dir 0 1) (Pos 0 3) "fff"
Sstate (Dir 0 1) (Pos 0 4) "ff"
Sstate (Dir 0 1) (Pos 0 5) "f"
Sstate (Dir 0 1) (Pos 0 6) ""
Done!
((),Sstate (Dir 0 1) (Pos 0 6) "")
>>> runStateT stepsPrintPosition example -- print position only at state updates
current position: Pos 0 2
current position: Pos 0 3
current position: Pos 0 4
current position: Pos 0 5
current position: Pos 0 6
Done!
((),Sstate (Dir 0 1) (Pos 0 6) "")
-- the WriterT examples accumulate a 'monoid' of things you keep
-- adding to with `tell xyz` Here we accumulate a [Position]
-- execXYZ and evalXYZ, where they exist, return less information than runXYZ
>>> runWriterT $ runStateT stepsAccumulatePositions example
(((),Sstate (Dir 0 1) (Pos 0 6) ""),[Pos 0 2,Pos 0 3,Pos 0 4,Pos 0 5,Pos 0 6])
>>> execWriterT $ evalStateT stepsAccumulatePositions example
[Pos 0 2,Pos 0 3,Pos 0 4,Pos 0 5,Pos 0 6]
在上面的代码中,我使用 mtl
类型 classes 然后使用 runStateT
和 runWriterT
到 "interpret" 或专门化 class 涉及签名。这些属于 Control.Monad.Trans.{State/Writer}
中定义的具体类型 StateT
和 WriterT
可以省略 classes,直接用这些具体类型编写,导入那些模块。唯一的区别是,在我组合两种效果、状态和书写或任何你想称呼它的情况下,你需要做 lift $ tell [b]
。
关于您正在使用的状态分析有很多要说的,但如果您仔细考虑以上内容,就会发现您可以如何对其进行修改。
注意这里不需要直接使用State
monad,因为end
和route
可以用foldl'
和[=18=来写] 一旦您将 Mov
视为对某个状态 操作 的东西,而不是状态的 一部分 。 Sstate
的记录语法也有帮助。
type Mov = Char
data Pos = Pos Int Int deriving (Show, Eq)
data Dir = Dir Int Int deriving (Show, Eq)
data Sstate = Sstate {direction :: Dir, position :: Pos} deriving (Show, Eq)
-- A helper to simplify move
changeDir :: Mov -> Dir -> Dir
changeDir 'l' (Dir x y) = Dir (-y) x
changeDir 'r' (Dir x y) = Dir y (-x)
changeDir m (Dir x y) = Dir y x
move :: Mov -> Sstate -> Sstate
move 'f' oldState@(Sstate (Dir dx dy) (Pos px py)) = oldState { position = Pos (dx + px) (dy + py) }
move m oldState@(Sstate dir _) = oldState { direction = changeDir m dir }
end :: [Mov] -> Sstate -> Sstate
end ms initialState = foldl' (flip move) initialState ms
route :: [Mov] -> Sstate -> [Pos]
route ms initialState = map position $ scanl' (flip move) initialState ms
然后你的例子变成:
λ: let x = Sstate {direction = Dir 0 1, position = Pos 0 2}
λ: move 'f' x
Sstate {direction = Dir 0 1, position = Pos 0 3}
λ: end "ff" x
Sstate {direction = Dir 0 1, position = Pos 0 4}
λ: route "ff" x
[Pos 0 2,Pos 0 3,Pos 0 4]
我会把它留作练习(或留给比我知识渊博且懒惰的人)以适应
move :: Mov -> Sstate -> Sstate
end :: [Mov] -> Sstate -> Sstate
route :: [Mov] -> Sstate -> [Pos]
到
move :: Mov -> State Sstate ()
-- or possibly move :: Mov -> State Sstate Sstate ?
-- Like I said, more knowledgeable than me
end :: [Mov] -> State Sstate Sstate
route :: [Mov] -> State Sstate [Pos]