Haskell实例函数



我正在解决一本书中的一些练习,现在我有一些困难:在这个练习中,我将把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’

因此,为了实现CardOrd,我们需要两件事:

  • 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开始。对于SuitRank,我们需要指定每个构造函数都等于自己(又称反身性),所有其他构造函数都不相等。

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,我们可以从枚举SuitRank的实例开始。我们有两种指定方式:(<=)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相同的基本形式。由于我们为SuitRank定义了(<=),请注意,对于这些类型,我们会自动获得其他比较函数(如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中的comparinginstance Monoid Ordering

deriving (Enum)添加到RankSuit中-尝试使用fromEnum来简化EqOrd的定义

我无法想象这个练习实际上需要您手动编写必要的实例。特别是对于SuitRank,它们的编写将非常繁琐(并且容易出错)。

相反,您可能希望使用deriving关键字来自动派生必要的实例。如果你写:

data Suit = Diamond | Heart | Spade | Club
deriving (Eq, Ord)

将自动派生EqOrd实例。(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)

派生实例根据第一个字段RankCards进行排序,被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

最新更新