Commonly Used Typeclasses
Let us have a look at some typeclasses and their methods that you have already used.
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
class Num a where
(+) :: a -> a -> a
(-) :: a -> a -> a
(*) :: a -> a -> a
negate :: a -> a
abs :: a -> a
signum :: a -> a
fromInteger :: Integer a
Equality Comparisons
The Eq
typeclass describes types that are amenable to
equality comparisons; the Num
typeclass describes types that
can behave as numbers, with support for typical numeric operations like
addition, subtraction and so on. Haskell's Prelude already ships with
the instances of these typeclasses for commonly-used types, such as
instances for Num Int
and Eq String
.
Let us try defining our own instance of Eq
. Suppose we are
re-using the Fraction
algebraic data type defined in Chapter 2.3 (Types#Algebraic Data Types):
data Fraction = Fraction Int Int
We allow Fraction
to be amenable to equality comparisons
by implementing a typeclass instance for Eq Fraction
:
instance Eq Fraction where
(==) :: Fraction -> Fraction -> Bool
F a b == F c d = a == c && b == d
(/=) :: Fraction -> Fraction -> Bool
F a b /= F c d = a /= c || b /= d
Firstly, notice that we are performing equality comparisons between the
numerators and denominators. This is okay because we know that the
numerators and denominators of fractions are integers, and there is
already an instance of Eq Int
. Next, usually by definition,
a /= b
is the same as not (a == b)
. Therefore,
having to always define both (==)
and (/=)
for
every instance is cumbersome.
Minimal Instance Definitions
Let us inspect the definition of the Eq
typeclass:
ghci> :i Eq
type Eq :: * -> Constraint
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
{-# MINIMAL (==) | (/=) #-}
-- Defined in 'GHC.Classes'
Notice the MINIMAL
pragma—the pragma states that we only need to
define either (==)
or (/=)
for a complete
instance definition! Therefore, we can omit the definition of
(/=)
in our Eq Fraction
instance, and we would
still have a complete definition:
instance Eq Fraction where
(==) :: Fraction -> Fraction -> Bool
F a b == F c d = a == c && b == d
ghci> Fraction 1 2 == Fraction 1 2
True
ghci> Fraction 1 2 /= Fraction 1 2
False
A natural question to ask is, why not simply define Eq
to
only have (==)
and give (/=)
for free?
class Eq a where
(==) :: a -> a -> Bool
(/=) :: Eq a => a -> a -> Bool
x /= y = not (x == y)
By placing both functions as methods in the typeclass, programmers have
the option to define either (==)
or (/=)
, or
both, if specifying each implementation individually gives better performance or
different behaviour than the default.
Typeclass Constraints in Typeclasses and Instances
We can even define instances over polymorphic types. Here is an example of how we can perform equality comparisons over trees:
data Tree a = Node (Tree a) a (Tree a)
| Empty
instance Eq (Tree a) where
(==) :: Tree a -> Tree a -> Bool
Empty == Empty = True
(Node l v r) == (Node l' v' r') = l == l' && v == v' && r == r'
_ == _ = False
However, our instance will not type-check because the elements
a
of the trees also need to be amenable to equality
comparisons for us to compare trees! Therefore, we should constrain
a
with Eq
in the instance declaration, like
so:
data Tree a = Node (Tree a) a (Tree a)
| Empty
instance Eq a => Eq (Tree a) where
(==) :: Tree a -> Tree a -> Bool
Empty == Empty = True
(Node l v r) == (Node l' v' r') = l == l' && v == v' && r == r'
_ == _ = False
In fact, we can write typeclass constraints in typeclass declarations as
well. For example, the Ord
typeclass describes (total)
orders on types, and all (totally) ordered types must also be amenable
to equality comparisons:
class Eq a => Ord a where
(<) :: a -> a -> Bool
(<=) :: a -> a -> Bool
-- ...
Deriving Typeclasses
In fact, some typeclasses are so straightforward that defining instances
of these classes are a tedium. For example, the Eq
class is
(usually) very straightforward to define—two terms are equal if they
are built with the same constructor and their argument terms are
respectively equal. As such, the language should not require programmers
to implement straightforward instances of classes like Eq
.
Haskell has a deriving mechanism that allows the compiler to
automatically synthesize typeclass instances for us. It is able to do so
for Eq
, Ord
, and others like Enum
.
Doing so is incredibly straightforward:
data A = B | C
data Fraction = Fraction Int Int
deriving Eq -- deriving Eq Fraction instance
data Tree a = Empty | Node (Tree a) a (Tree a)
deriving (Eq, Show) -- deriving Eq (Tree a) and Show (Tree a)
deriving instance Eq A -- stand-alone deriving declaration
These declarations tell the compiler to synthesize instance declarations in the most obvious way. This way, we do not have to write our own instance declarations for these typeclasses!
ghci> x = Node Empty 1 Empty
ghci> y = Node (Node Empty 1 Empty) 2 Empty
ghci> x
Node Empty 1 Empty
ghci> x == y
False