这将是一个TicTacToe实现:
data Row = A | B | C deriving (Show, Read, Eq, Ord, Enum, Bounded)
data Column = X | Y | Z deriving (Show, Read, Eq, Ord, Enum, Bounded)
type Pos = (Row, Column)
data Player = Player String
data Field = Field [(Pos, Player)]
initialField :: Field
initialField = Field []
正如你所看到的,initialField只是一个空列表,当玩家进行移动时,(pos,player)tupels将被添加到列表中。到现在为止还挺好。但是现在我在编写possibleMoves :: Field -> [Pos]
函数以获取空字段时遇到了麻烦。如何迭代Haskell中的所有Row,Column可能性?我觉得我的方法是错的,但我是Haskell的新手,所以我没有更好的想法。
答案 0 :(得分:5)
我们可以通过列表推导获得所有职位(另见其他答案)
positions :: [Pos]
positions = [(r,c) | r <- [A,B,C], c <- [X,Y,Z]]
和所有玩地图
occupied :: Field -> [Pos]
occupied (Field l) = fmap fst l
然后我们可以定义possibleMoves(您需要导入Data.List以获取\\
,列出差异):
possibleMoves :: Field -> [Pos]
possibleMoves f = positions \\ (occupied f)
更紧凑的版本利用了列表理解约束:
possibleMoves :: Field -> [Pos]
possibleMoves (Field l) = [(r,c) | r <- [A,B,C], c <- [X,Y,Z], (r,c) `notElem` occupied]
where occupied = fmap fst l
答案 1 :(得分:3)
所有行(ref = Getting a list of all possible data type values in Haskell):
Prelude> [(minBound :: Row) ..]
[A,B,C]
所有栏目:
Prelude> [(minBound :: Column) ..]
[X,Y,Z]
计算笛卡尔乘积以找到所有可能性(ref = Cartesian product of 2 lists in Haskell):
Prelude> [(x, y) | x <- [(minBound :: Row)..], y <- [(minBound :: Column)..]]
[(A,X),(A,Y),(A,Z),(B,X),(B,Y),(B,Z),(C,X),(C,Y),(C,Z)]
答案 2 :(得分:2)
如果你想要一个超短的“ulta-Haskelly”版本:
enumAll :: (Bounded a, Enum a) => [a]
enumAll = [minBound..maxBound]
positions :: [Pos]
positions = (,) <$> enumAll <*> enumAll
(您还需要为<$>
和<*>
运营商导入Control.Applicative
然后在ghci:
*Main> positions
[(A,X),(A,Y),(A,Z),(B,X),(B,Y),(B,Z),(C,X),(C,Y),(C,Z)]
这到底是怎么回事?
enumAll
只是一个通用的辅助函数(我很惊讶不能在标准库中快速找到它;我可能错过了它,你甚至不需要自己定义它)。它为您提供了任何有界可枚举类型的每种可能性列表。这很简单; Bounded
表示类型包含minBound
和maxBound
,Enum
表示您可以使用[a..b]
语法获取从a
到b
的所有内容的列表(,)
。
:t (,)
只是配对功能。如果您在ghci中输入(,) :: a -> b -> (a, b)
,它会告诉您<$>
。
现在,那些奇怪的<*>
和(,)
符号呢?它们基本上是“特殊”的功能应用形式。因此,您可以阅读它,就像我们只是将enumAll
应用于两个参数enumAll
和(enumAll, enumAll)
一样。但是因为它是“特殊”应用程序,它不仅仅给我们Applicative
对。
我们在这里做的是使用[Row]
实例列表。我不打算详细介绍它,但这样做有助于我们认为[Row]
不是行值列表,而是单个“未知”行值。这是可能列表中任何元素的值,但我们不知道哪一个。通常用于此的技术术语是Row
可以被视为 nondeterministic Row
;它是一个(,)
值可能是多种可能性,但我们无法确定它实际上是哪一种。
所以我们正在做的是将函数<$>
(只需要两个参数构建一对)应用于两个 nondeterminsitic 值,以获得一个非确定对(这是我们需要<*>
和(,)
的“特殊”版本的函数应用程序;如果我们通常使用(,) enumAll enumAll
应用(enumAll, enumAll)
或直接使用([a], [b])
构建对然后我们得到的是一对正常的非确定值,而不是一对非确定性的正常值 - [(a, b)]
vs positions :: [Pos]
)。如果我从一个有界枚举的所有可能性和另一个有界枚举的所有可能性中取出一对,那么我应该得到所有可能的对!这正是发生的事情。
此处实际需要类型签名(Row, Column)
。这就是告诉Haskell我们正在构建enumAll
对的列表而不是任何其他类型的枚举,这就是它知道第一个(,) <$> enumAll <*> enumAll
枚举所有行而第二个wsa枚举所有行的方式列。最常见的类型(Enum a, Bounded a, Enum b, Bounded b) => [(a, b)]
实际上是{{1}},它可以很好地工作但是在ghci中以交互方式工作是一种痛苦,因为每当你尝试打印东西时你都会不断得到模糊的类型变量。