我正在解决一本书中的一些练习,现在我有一些困难:在这个练习中,我将把Card实现为类Ord的一个实例。但是我不知道我该如何实现它,所以我很感激你的帮助。
到目前为止,我的代码是这样的:data Suit = Diamond | Club | Spade | Heart
data Rank = Seven | Eight | Nine | Ten | Jack | Queen | King | Ace
data Card = Card Suit Rank
instance Ord Card where
....
现在我不知道如何确切地实现这个,我很想理解它。提前感谢您的解释。
我们可以向GHCi询问Ord
的信息。
:info Ord
这显示了类定义,后面是一个实例列表。
type Ord :: * -> Constraint
class Eq a => Ord a where
compare :: a -> a -> Ordering
(<) :: a -> a -> Bool
(<=) :: a -> a -> Bool
(>) :: a -> a -> Bool
(>=) :: a -> a -> Bool
max :: a -> a -> a
min :: a -> a -> a
{-# MINIMAL compare | (<=) #-}
-- Defined in ‘GHC.Classes’
它的超类是Eq
,而:info Eq
告诉我们:
type Eq :: * -> Constraint
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
{-# MINIMAL (==) | (/=) #-}
-- Defined in ‘GHC.Classes’
因此,为了实现Card
的Ord
,我们需要两件事:
instance Ord Card
,定义为compare
或(<=)
instance Eq Card
与(==)
或(/=)
的定义
由于这些实例编写起来非常机械,通常我们要求编译器自动派生它们:
data Suit = Diamond | Heart | Spade | Club
deriving (Eq, Ord)
data Rank = Seven | Eight | Nine | Ten | Jack | Queen | King | Ace
deriving (Eq, Ord)
data Card = Card Suit Rank
deriving (Eq, Ord)
如果你将-ddump-deriv
标志传递给GHC,或者在GHCi中使用:set -ddump-deriv
,它将打印出它为这些实例生成的代码。
然而,任务是理解如何手工实现这些实例,所以让我们一步一步地来完成它。
我们从Eq
开始。对于Suit
和Rank
,我们需要指定每个构造函数都等于自己(又称反身性),所有其他构造函数都不相等。
instance Eq Suit where
Diamond == Diamond = True
Heart == Heart = True
Spade == Spade = True
Club == Club = True
_ == _ = False
instance Eq Rank where
Seven == Seven = True
Eight == Eight = True
Nine == Nine = True
Ten == Ten = True
Jack == Jack = True
Queen == Queen = True
King == King = True
Ace == Ace = True
_ == _ = False
解决了这个问题,我们可以用类似的方式为Card
定义==
,通过对形式为Card suit rank
的值进行模式匹配。
instance Eq Card where
-- Two cards are equal if…
Card suit1 rank1 == Card suit2 rank2
-- …their suits are equal,
-- and their ranks are equal.
= …
有两种定义主体的常规方法。一种是逐字拼写:suit1 == suit2 && rank1 == rank2
。另一种是使用元组:(suit1, rank1) == (suit2, rank2)
.
Ord
,我们可以从枚举Suit
和Rank
的实例开始。我们有两种指定方式:(<=)
或compare
。直接的选择是列出可能的情况表,在可能的情况下缩写情况:
instance Ord Suit where
Diamond <= _ = True
Heart <= Diamond = False
Heart <= _ = True
Spade <= Diamond = False
Spade <= Heart = False
Spade <= _ = True
Club <= Club = True
Club <= _ = False
instance Ord Rank where
…
对于Card
,我们现在只需要遵循与Eq
相同的基本形式。由于我们为Suit
和Rank
定义了(<=)
,请注意,对于这些类型,我们会自动获得其他比较函数(如compare
和(<)
)的默认实现。
instance Ord Card where
-- Two cards are in order if…
Card suit1 rank1 <= Card suit2 rank2
-- …one rank is less than the other,
-- or their ranks are equal and suits are in order.
= …
同样,您可以使用(<)
,(||)
,(==)
,(&&)
,(<=)
逐字翻译此注释。
尝试基于compare
而不是(<=)
实现这些实例。提示:使用Data.Ord
中的comparing
和instance Monoid Ordering
。
将deriving (Enum)
添加到Rank
和Suit
中-尝试使用fromEnum
来简化Eq
和Ord
的定义
我无法想象这个练习实际上需要您手动编写必要的实例。特别是对于Suit
和Rank
,它们的编写将非常繁琐(并且容易出错)。
相反,您可能希望使用deriving
关键字来自动派生必要的实例。如果你写:
data Suit = Diamond | Heart | Spade | Club
deriving (Eq, Ord)
将自动派生Eq
和Ord
实例。(Eq
实例需要Ord
实例。)派生的Eq
实例是自解释的,派生的Ord
实例使用构造函数在声明中出现的顺序,因此Diamond
最小,Club
最大。如果需要桥接顺序,使用:
data Suit = Club | Diamond | Heart | Spade
deriving (Eq, Ord)
。类似地,
data Rank = Seven | Eight | Nine | Ten | Jack | Queen | King | Ace
deriving (Eq, Ord)
将Seven
(最小)排序到Ace
(最大)。然后,如果你写:
data Card = Card Rank Suit
deriving (Eq, Ord)
派生实例根据第一个字段Rank
对Cards
进行排序,被Suit
打破。如果你写:
data Card = Card Suit Rank
deriving (Eq, Ord)
它会先按Suit
排序,然后按Rank
排序。
一些代码来说明:
data Suit = Diamond | Heart | Spade | Club
deriving (Eq, Ord)
data Rank = Seven | Eight | Nine | Ten | Jack | Queen | King | Ace
deriving (Eq, Ord)
data Card = Card Rank Suit
deriving (Eq, Ord)
main = do
let heart8 = Card Eight Heart
spade8 = Card Eight Spade
diamond7 = Card Seven Diamond
print $ heart8 == heart8 -- True
print $ heart8 == spade8 -- False
print $ heart8 < spade8 -- True
print $ diamond7 < spade8 -- True