无法解析data / newtype声明中的数据构造函数

问题描述 投票:0回答:1

我有类型卡,包括西装和等级,

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,如何创建一个正确的函数

谢谢

haskell
1个回答
3
投票

它可能看起来好像是声明:

data Suit = A | B

只定义一个东西,类型Suit,作为任意对象的集合/集合。实际上,它定义了三个方面:类型Suit和两个构造函数AB用于创建该类型的值。

如果定义:

data Rank = 1 | 2

实际上工作,它不会将Rank定义为数字12的集合,它将重新定义数字12作为新类型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)

请注意,此处的类型别名实际上仅用于文档。在这里,RankInt是完全相同的类型,可以互换使用。使用Rank只是通过明确程序员希望Int代表Rank而不是用于某些其他目的的整数来使代码更容易理解。

这种方法的主要优点是你可以避免在很多地方包括Rank这个词(例如,卡片写的是Card A 1而不是Card A (Rank 1),并且点的定义不需要模式匹配Rank r上的参数等)主要的缺点是它模糊了Rank和其他整数之间的区别,并且使编程错误更容易,比如使用Rank,你打算使用这些点,反之亦然。

© www.soinside.com 2019 - 2024. All rights reserved.