Defining Type Classes and Instances
So how is this mysterious type class Eq
defined? Here is its definition:
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
Every class definition starts with the keyword class
, followed by the name of
the class (here, Eq
), followed by a type variable used to refer to the type
that is an instance of this class (here, a
).1 This is
followed by where
and a bunch of function signatures that refer to the type
a
. An instance of the type class must provide an implementation for each of
the function signatures listed in the class definition. We'll discuss this
shortly.
The definition of the Eq
type class says that any type a
that is an instance
of Eq
must provide implementations of the two functions (==)
and (/=)
, of
type a -> a -> Bool
. These two functions implement equality and inequality
testing, respectively.
Standard types such as Int
, Integer
, Bool
, etc. are all instances of this
type class, but type classes would have fairly limited use if we couldn't make
the types we define ourselves instances of different type classes.
Let's look at our banner number type:
newtype BannerNumber = B Int
Out of the box, this type is not an instance of any type class. We can't even
test BannerNumber
s for equality. That's a good thing, because it allows us to
control exactly what operations our types should support. Remember, when I
introduced the BannerNumber
type in the chapter on data types, I said that we
want this type to be distinct from Int
because, for example, adding two banner
numbers together is generally nonsense. And, indeed, as we discuss below, the
types that support arithmetic operations are those in the Num
type class.
Unless we make BannerNumber
an instance of Num
, we're safe: the compiler
complains if we try to add two banner numbers:
>>> newtype BannerNumber = B Int
>>> B 1 + B 2
<interactive>:82:5: error:
• No instance for (Num BannerNumber) arising from a use of ‘+’
• In the expression: B 1 + B 2
In an equation for ‘it’: it = B 1 + B 2
The error message is similar to the one we encountered for our initial
implementation of elem
. Given that we're trying to add two banner numbers
together, the compiler concludes that banner numbers should support the addition
operation (+)
. This is one of the functions provided by the Num
type class,
so the compiler further concludes that to add two banner numbers together,
BannerNumber
must be an instance of Num
. Since we defined no such instance,
we get an error.
Similarly, we currently don't have equality tests for banner numbers, so testing whether a list of banner numbers contains a specific number also doesn't work:
>>> newtype BannerNumber = B Int
>>> B 1 `elem` [B 1, B 2, B 3]
<interactive>:2:5: error:
• No instance for (Eq BannerNumber) arising from a use of ‘elem’
• In the expression: B 1 `elem` [B 1, B 2, B 3]
In an equation for ‘it’: it = B 1 `elem` [B 1, B 2, B 3]
We probably do want to be able to test whether two banner numbers are equal.
Searching a list of banner numbers for a specific one is one example where this
would be useful. To do so, we need to make BannerNumber
an instance of Eq
.
Clearly, we want two banner numbers to be the same if the Int
s they wrap are
the same. Thus, we declare BannerNumber
an instance of Eq
as follows:
instance Eq BannerNumber where
B x == B y = x == y
B x /= B y = x /= y
This says that BannerNumber
is an instance of Eq
and provides the two
required implementations of (==)
and (/=)
for banner numbers. Here, two
banner number B x
and B y
are equal (B x == B y
) if and only if x == y
,
and they are unequal (B x /= B y
) if and only if x /= y
. Pretty
straightforward.
Now we can test banner numbers for equality:
>>> :{
| instance Eq BannerNumber where
| B x == B y = x == y
| B x /= B y = x /= y
| :}
>>> B 1 == B 2
False
>>> B 1 /= B 2
True
>>> B 3 == B 3
True
>>> B 3 /= B 3
False
Searching a list for a specific banner number also works now:
>>> B 1 `elem` [B 1, B 2, B 3]
True
>>> B 4 `elem` [B 1, B 2, B 3]
False
What about asking whether a banner number is less than another?
>>> B 1 < B 2
<interactive>:98:5: error:
• No instance for (Ord BannerNumber) arising from a use of ‘<’
• In the expression: B 1 < B 2
In an equation for ‘it’: it = B 1 < B 2
Nope, this doesn't work. And this shouldn't be surprising. We only provided
implementations of (==)
and (/=)
. That's the only thing guaranteed by the
Eq
type class. We didn't provide implementations of (<)
, (>)
, (<=)
or
(>=)
. Those are part of the Ord
type class we discuss shortly. That's
exactly what GHCi complains about here: "No instance for (Ord BannerNumber)
arising from a use of '<
'."
You may question why one would want to support equality tests but not less-than
and greater-than tests. Here's an example. Consider two binary trees. It makes
perfect sense to ask whether two binary trees are the same, and there exists a
natural recursive implementation, which we will discuss shortly. In general,
there is no meaningful way to define that one binary tree is less than another
binary tree. So binary trees should be instances of Eq
but not of Ord
.
Similarly, there are types where it doesn't even make sense to support equality
testing. Functions are one example. Well, mathematically, it actually makes
perfect sense to ask whether two functions are the same. The reason why we can't
provide an Eq
instance for function types is that the only way to test whether
two functions are the same is to verify whether they have the same type—that's
easy—and they produce the same result for every possible argument value—that's
the part that takes way too long in general or can't even be done at all for
functions unbounded argument types, such as Integer
.
-
There also exist some type classes with multiple type variable arguments. For a type class with two arguments, for example, the instances are no longer individual types but pairs of types. Where a one-argument type class, such as
Eq
, specifies some properties of all types that are instances of this type class, you should think about a multi-parameter type class as establishing a relationship between pairs or tuples of types. We'll encounter these multi-parameter type classes later in this book. ↩