如果你想要一个超短的“ulta-Haskelly”版本:
enumAll :: (Bounded a, Enum a) => [a]
enumAll = [minBound..maxBound]
positions :: [Pos]
positions = (,) <$> enumAll <*> enumAll
(您还需要为<$>
and<*>
运算符导入 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
的;这是一个Row
可能有多种可能性的值,但我们无法确定它实际上是哪一种。
所以我们正在做的是将函数(,)
(它只需要两个参数来构建一对)应用于两个非确定性值,以获得一个非确定性对(这是我们需要使用 and 的“特殊”版本的函数应用程序的地方<$>
;<*>
如果我们(,)
通常使用(,) enumAll enumAll
或直接构建对,(enumAll, enumAll)
然后我们得到的只是一对正常的非确定性值,而不是一对非确定性的正常值 - ([a], [b])
vs [(a, b)]
) 。如果我从一个有界枚举的所有可能性和另一个有界枚举的所有可能性中取一对,那么我应该得到对的所有可能性!这正是发生的事情。
positions :: [Pos]
这里实际上需要类型签名。这就是告诉 Haskell 我们正在构建一个(Row, Column)
对列表而不是任何其他类型的枚举对,这就是它知道第一个enumAll
枚举所有行和第二个 wsa 枚举所有列的方式。最通用的类型(,) <$> enumAll <*> enumAll
实际上是(Enum a, Bounded a, Enum b, Bounded b) => [(a, b)]
,它可以正常工作,但在 ghci 中以交互方式使用会很痛苦,因为每当您尝试打印内容时,您都会不断获得模棱两可的类型变量。