我有类型卡,包括西装和等级,
data Suit = A|B deriving (Show, Eq)
data Rank = 1|2 deriving (Show, Eq)
data Card = Card Suit Rank deriving (Show, Eq)
数据Rank函数似乎是错误的,因为Int不能是类型构造函数,如果我的卡是A1 | B1 | A2 | B2,如何创建一个正确的函数
谢谢
它可能看起来好像是声明:
data Suit = A | B
只定义一个东西,类型Suit
,作为任意对象的集合/集合。实际上,它定义了三个方面:类型Suit
和两个构造函数A
和B
用于创建该类型的值。
如果定义:
data Rank = 1 | 2
实际上工作,它不会将Rank
定义为数字1
和2
的集合,它将重新定义数字1
和2
作为新类型Rank
的构造函数/值,你将不再能够使用它们作为常规数字。 (例如,表达式n + 1
现在将是一个类型错误,因为(+)
期望一个数字,并且1
将被重新定义为Rank
)。
幸运或遗憾的是,Haskell不接受数字作为构造函数名称 - 它们需要是以大写字母开头的有效标识符(或以冒号开头的运算符)。
因此,有两种常用的方法来定义类似Rank
的类型,它意味着代表一些数字子集。如评论中所述,第一个是像您已经拥有的那样定义它,但是通过在大写字母前加上前缀将数字更改为有效的标识符:
data Rank = R1 | R2
这样做的好处是它保证只能表示有效的等级。这里只允许排名1和2。如果有人试图在某处编写R3
,它就行不通,因为该构造函数尚未定义。最大的缺点是,这很快变得难以驾驭。如果这些是扑克牌,定义将是:
data Rank = R1 | R2 | R3 | R4 | R5 | R6 | R7 | R8 | R9 | R10 | R11 | R12 | R13
和一个函数,比如说,为rummy卡分配点值看起来像:
points :: Rank -> Int
points R1 = 10 -- Ace worth 10
points R2 = 2
points R3 = 3
...
points R9 = 9
points R10 = 10 -- 10 and face cards all worth 10
points R11 = 10
points R12 = 10
points R13 = 10
(在实际代码中,您将使用更高级的Haskell功能,如派生的Enum
实例来处理此问题。)
第二种方法是根据现有数字类型定义您的排名:
data Rank = Rank Int -- actually, `data` would probably be `newtype`
这定义了两个东西,一个名为Rank
的类型和一个名为Rank
的构造函数。 (这没关系,因为类型和构造函数存在于不同的名称空间中。)
在这个定义中,不是将Rank
定义为由显式构造函数给出的一组离散值,每个值有一个构造函数,这个定义必不可少,使得Rank
类型成为用构造函数Int
“标记”的Rank
。
这种方法的缺点是现在可以创建无效的排名(因为有人可以写Rank 14
)。优点是它通常更容易使用。例如,您可以从排名中提取整数,因此points
可以定义为:
points :: Rank -> Int
points (Rank 1) = 10 -- Ace is worth 10
points (Rank r) | r >= 10 = 10 -- 10 and face are worth 10
| otherwise = r -- rest are worth their rank
请注意,使用这组定义:
data Suit = A | B deriving (Show, Eq)
newtype Rank = Rank Int deriving (Show, Eq)
data Card = Card Suit Rank deriving (Show, Eq)
你可以使用像Card
这样的表达式为你的“A1”牌构建Card A (Rank 1)
值。
实际上有第三种方法。有些人可能会完全跳过定义Rank
类型并写入:
data Suit = A | B deriving (Show, Eq)
data Card = Card Suit Int deriving (Show, Eq)
或使用类型别名编写等效代码:
data Suit = A | B deriving (Show, Eq)
type Rank = Int
data Card = Card Suit Rank deriving (Show, Eq)
请注意,此处的类型别名实际上仅用于文档。在这里,Rank
和Int
是完全相同的类型,可以互换使用。使用Rank
只是通过明确程序员希望Int
代表Rank
而不是用于某些其他目的的整数来使代码更容易理解。
这种方法的主要优点是你可以避免在很多地方包括Rank
这个词(例如,卡片写的是Card A 1
而不是Card A (Rank 1)
,并且点的定义不需要模式匹配Rank r
上的参数等)主要的缺点是它模糊了Rank
和其他整数之间的区别,并且使编程错误更容易,比如使用Rank
,你打算使用这些点,反之亦然。