我是Haskell的新手,我正在努力学习基础知识。我需要声明一个名为Pos的类型,它将具有两个整数,然后我需要方向北,南,西,东,以便我可以根据方向改变位置。 一旦我这样做,我需要创建一个称为移动的函数,它将获取移动列表和初始位置,并在所有移动后返回位置。 这是我的代码,但我仍然坚持我必须遍历移动列表。
type Pos = (Int, Int)
data Direction = North | South | East | West
move :: Direction -> Pos -> Pos
move North (x,y) = (x, y+1)
move West (x,y) = (x-1, y)
move South (x,y) = (x, y-1)
move East (x,y) = (x+1, y)
moves :: [Direction] -> Pos -> Pos
moves [] (x,y) = (x,y)
moves (h:xs) (x,y)
| h == North = move North (x,y)
| h == West = move West (x,y)
| h == South = move South (x,y)
| otherwise = move East (x,y)
我在这里缺少什么?
答案 0 :(得分:9)
将一系列事物组合成一个东西称为折叠列表。 (大约)有两个列表折叠:foldl
和foldr
。倾向于使用折叠是学习Haskell的重要一步。我们需要foldl
,其类型为
foldl :: (a -> b -> a) -> a -> [b] -> a
现在foldl
使用组合函数和初始值来组合列表中的东西,例如
foldl (£) s [x,y,z] = (((s £ x) £ y) £ z)
(l
中的foldl
是左边的缩写,因此可以帮助您记住,您的起始值s
将在左侧结束,但更重要的是,括号为与左边相关联。)
第三个参数是列表参数[b]
。我们会将其用于移动列表,因此b
类型将为Direction
。
第二个参数是a
类型的起始值,因此我们将其用于您的初始位置,因此a
类型将为Pos
。
第一个参数是将列表中的内容与当前值组合在一起的函数。现在我们知道类型b
和a
是Direction
和Pos
,我们知道我们的合并函数必须具有类型Pos -> Direction -> Pos
。移动函数几乎正是我们需要的,除了我们需要交换参数。 flip
函数执行此操作,因此flip move
具有我们需要的类型。
因此我们将foldl
的类型专门化为
foldl :: (Pos -> Direction -> Pos) -> Pos -> [Direction] -> Pos
并定义
moves :: [Direction] -> Pos -> Pos
moves ds i = foldl (flip move) i ds
现在foldl
有一个名为foldl'
的“严格”版本,在这种情况下更快,所以如果你在快节奏的游戏中使用它,或者处理大量的动作,你想要使用那个。
与往常一样,您可以通过在hoogle上搜索其姓名或类型来查找功能。
还有一个折叠函数,它以不同的方式折叠列表。您可以在this question中了解它们之间的区别。简而言之,foldr
的工作原理如下:
foldr (?) s [x,y,z] = (x ? (y ? (z ? s)))
(r
中的foldr
是对的缩写,所以它可以帮助您记住您的起始值s
将在右侧结束但更重要的是括号是与权利相关联。)
答案 1 :(得分:3)
在对应h
的单个移动之前或之后,您需要对其余的moves
进行{em>递归调用Direction
。如果之前,那么每个案例都会变成:
move North (moves xs (x,y))
如果你希望它在之后,这可能是给出问题规范的正确方法,那么它将是:
moves xs (move North (x,y))
然而,重复moves
中的案例分析有点不必要,因为您可以直接使用move
拨打h
,例如
moves (h:xs) (x,y) = moves xs (move h (x,y))
事实上,您甚至不需要在(x,y)
元组上明确地进行模式匹配;
moves (h:xs) pos = moves xs (move h pos)
正如另一个答案所指出的,这与内置foldl
函数(直到参数顺序)相同,但最好先了解如何手动完成。