我正在尝试跟踪一个物体在二维平面上的移动,该物体已经被给定了一系列指令:“前进”,“左转”或“右转”。
到目前为止,我已经编写了函数,可以接收物体状态的各个组件(方向、位置和移动),并在所有移动完成或沿途通过所有位置后返回最终状态。
物体的状态采用以下形式: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"很令人困惑,但觉得如果有人能友善地展示在这里如何使用它,那将有助于我的理解。
State
单子。 - Bakuriua -> b
的函数变成了类型为a -> s -> (b, s)
的函数。柯里化让你把它看作是接受类型为a
的值并返回一个新函数,当给定状态时,可以返回类型为b
和新状态的值 (a -> (s -> (b, s))
)。通过>>=
运算符,单子就是让你链接这样的函数。最终,你会得到一个类型为s -> (t, s)
的函数,它将初始状态转换为类型为t
的值。 - chepnerdata Mov = Fwd | Lft | Rght | ... deriving (Eq, Show)
这样的东西来代替将其作为Char
的同义词。这将使得在使用-Wall
或-fwarn-incomplete-patterns
时更容易让 GHC 帮助你查看是否捕获了所有情况,并且通常会使你的意图更加清晰。 - dfeuer