Let’s think about moods. Psychologists all agree there are only 5 real emotional states.
data Mood = Awful | QuiteBad | OK | Good | Great
Which one is better? Is Great
better than Awful
?
broken :: Bool
= Awful < Great broken
Shit!
No instance for (Ord Mood) arising from a use of ‘<’
• In the expression: Awful < Great
• In an equation for ‘broken’: broken = Awful < Great
These cannot be ordered! We need to implement the Ord
typeclass (short for “ordering”) so that we can compare these values and sort them.
What will we need to do that then?
We can find out more by firing up ghci
, the GHC repl.
You should see a prompt with the following:
Prelude>
Prelude
is telling us that we’ve loaded the Haskell Prelude, a standard library of functions. Ord
is part of that, so let’s get some info.
Enter :info Ord
into the repl (and press enter). You should see something like this:
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 | (<=) #-}
This is a list of functions that the Ord
typeclass implements. Looks like hard work. What’s interesting here?
Firstly - the Eq a =>
constraint means that we can only define an Ord
instance for something that has an Eq
instance. I guess if we can’t tell if two values are the same, how can we dream of putting them in some sort of order?
Secondly - the slighly cryptic {-# MINIMAL compare | (<=) #-}
line is telling us that we can define Ord
by either defining the compare
or <=
functions. Haskell can generate the other functions by using either of these ones. That’s great news because hard work is hard and we don’t want to do hard work.
data SuperMood = Worse | PrettyAverage | Fine deriving (Eq)
We’re auto-generating the Eq
because why have a bad time. Let’s make our Ord
instance.
instance Ord SuperMood where
<= Fine = True
_ Worse <= PrettyAverage = True
<= _ = False _
We’re implementing it in terms of <=
as it returns a Bool
which is more straightforward than the Ordering
datatype that compare
uses).
_ <= Fine = True
means that all values are equal to or less thanFine
.Worse <= PrettyAverage = True
means thatWorse
is less than or equal toPrettyAverage
._ <= _ = False
means any other combination returnsFalse
.
Even though we’ve only implemented one function, since the others can defined using it we get all of them for free:
yep :: Bool
= Worse < PrettyAverage
yep -- yep = True
yep2 :: Bool
= Fine >= Worse
yep2 -- yep2 = True
yep3 :: Bool
= Fine >= Fine
yep3 -- yep2 = True
nope :: Bool
= Fine < Fine
nope -- nope = False
We also get to use any function that requires an Ord
instance for free, like sort
from Data.List
, which has the following type signature:
sort :: Ord a => [a] -> [a]
This means “if you pass me a list of any a
which is orderable, I can return you a sorted list”. Thanks!
import Data.List
moods :: [SuperMood]
= [Fine, Fine, Worse, PrettyAverage]
moods
sorted :: [SuperMood]
= sort moods
sorted -- sorted = [Worse, PrettyAverage, Fine, Fine]
Great job!
Still seems like hard work though, can we auto generate this typeclass too? Yes!
data LazyMood = Sloppy
| Ploppy
| Poopy
| Nicey deriving (Eq, Ord)
lazySorted :: [LazyMood]
= sort [Nicey, Poopy, Ploppy, Sloppy]
lazySorted -- lazySorted = [Sloppy, Ploppy, Poopy, Nicey]
Nice.
Make sense? If not, why not get in touch?
Further reading: