Search code examples
haskellalgebraic-data-typesord

How to implement custom ordering for data types in Haskell?


Let's say I have a data type that represents a deck of poker cards like so

data Suit = Clubs | Spades | Hearts | Diamonds deriving (Eq)

instance Show Suit where
    show Diamonds = "♦"
    show Hearts = "♥"
    show Spades = "♠"
    show Clubs = "♣"

data Value = Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten | Jack | Queen | King | Ace deriving (Eq, Ord)

data Card = Card {
      value :: Value
    , suit :: Suit
    } deriving (Eq, Ord)

instance Show Card where show (Card v s) = show s ++ show v

Haskell helps me already a lot by allow me to derive Eq and Ord without explicitly specifying those relations. This is especially useful, because I want to use Ord to ultimately compare the value of two hands according to Poker rules.

Now in Poker, the suits do not really matter in terms of ordering. Therefore I tried

instance Ord Suit where
    compare Clubs Spades = EQ
    compare Clubs Hearts = EQ
    compare Clubs Diamonds = EQ
    compare Spades Hearts = EQ
    compare Spades Diamonds = EQ
    compare Hearts Diamonds = EQ

This is already a bit verbose ... and it does not even work:

*P054> a
♠A
*P054> b
♣A
*P054> a < b
*** Exception: P054.hs:(12,9)-(17,36): Non-exhaustive patterns in function compare

So how can I properly define an ordering on Suit that expresses the fact that all suits are equal?


Solution

  • You are missing several combinations, e.g. all with Clubs on the right hand side. If all are equal, what are the possible results of compare, regardless of the input? There is only one: EQ. Therefore we don't even need to look at a or b:

    instance Ord Suit where
        compare _ _ = EQ
    

    However, that Ord instance is rather useless. Alternatively you can create a custom instance for Card,

    instance Ord Card where
        compare a b = compare (value a) (value b)
        -- compare = compare `on` value -- using `on` from Data.Function
        -- compare = comparing value    -- using `comparing` from Data.Ord