I'm solving some exercises from a book and now I'm having some difficulties: In this exercise I shall implement Card as an instance of the class Ord, where "Rank" decides which card is higher and if there are two cards of the same rank, "Suit" decides. With the same "Rank" and the same "Suit" the two cards are also the same.
But I don't know how exactly I could implement it, so I would appreciate any help.
My code so far looks like this:
data Suit = Diamond | Heart | Spade | Club
data Rank = Seven | Eight | Nine | Ten | Jack | Queen | King | Ace
data Card = Card Suit Rank
instance Ord Card where
....
Now I don't know how exactly to implement this and I would very much like to understand it. Thanks in advance for the explanations.
CodePudding user response:
We can ask GHCi for information about Ord.
:info Ord
This shows the class definition, followed by a list of instances.
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’
Its superclass is Eq, and :info Eq tells us:
type Eq :: * -> Constraint
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
{-# MINIMAL (==) | (/=) #-}
-- Defined in ‘GHC.Classes’
So in order to implement Ord for Card, we need two things:
- An
instance Ord Card, with a definition ofcompareor(<=) - An
instance Eq Cardwith a definition of(==)or(/=)
Since these instances are very mechanical to write, normally we ask the compiler to derive them automatically:
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)
If you pass the -ddump-deriv flag to GHC, or use :set -ddump-deriv in GHCi, it will print out the code that it generates for these instances.
However, the task is to understand how to implement these instances by hand, so let’s go through it step by step.
We’ll start with Eq. For Suit and Rank, we need to specify that each constructor is equal to itself (a.k.a. reflexivity) and all other constructors are unequal.
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
With that out of the way, we can define == for Card in a similar way, by pattern-matching on values of the form 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.
= …
There are two conventional ways to define the body. One is spelling it out literally: suit1 == suit2 && rank1 == rank2. Another is to use tuples: (suit1, rank1) == (suit2, rank2).
Again, to define Ord, we can begin with the instances for the enumerations Suit and Rank. We have two choices of how to specify these: (<=) or compare. The direct option is to just list out the table of possible cases, abbreviating cases where we can:
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
…
For Card, we now just need to follow the same basic form as for Eq. Since we defined (<=) for Suit and Rank, note that we automatically get a default implementation of the other comparison functions like compare and (<) for those types.
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.
= …
Again you can translate this comment verbatim using (<), (||), (==), (&&), (<=).
Try implementing these instances based on compare instead of (<=). Hint: use comparing and instance Monoid Ordering from Data.Ord.
Add deriving (Enum) to Rank and Suit—try using fromEnum to simplify their Eq and Ord definitions.
CodePudding user response:
I can't imagine the exercise actually wanted you to manually write the necessary instances. Especially for Suit and Rank, they would be very tedious (and error prone) to write.
Instead, you probably want to use the deriving keyword to automatically derive the necessary instances. If you write:
data Suit = Diamond | Heart | Spade | Club
deriving (Eq, Ord)
this automatically derives Eq and Ord instances. (The Eq instance is required by the Ord instance.) The derived Eq instance is self-explanatory, and the derived Ord instance uses the order in which the constructors appear in the declaration, so Diamond is the smallest and Club is the biggest. If you want bridge order, use:
data Suit = Club | Diamond | Heart | Spade
deriving (Eq, Ord)
instead. Similarly,
data Rank = Seven | Eight | Nine | Ten | Jack | Queen | King | Ace
deriving (Eq, Ord)
orders the rank from Seven (smallest) to Ace (largest). Then, if you write:
data Card = Card Rank Suit
deriving (Eq, Ord)
the derived instance sorts Cards according to the first field Rank, with ties broken by Suit. If you wrote:
data Card = Card Suit Rank
deriving (Eq, Ord)
it would instead order them by Suit, then Rank.
Some code to illustrate:
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
