Classes and Instances

13 minute read

We’ve seen class constraints like Eq a => in types. We know how to use existing classes with existing types. But how do we use existing classes with our own types? How can we define our own classes?

Here’s how to make your own type a member of the Eq class:

data Color = Black | White

instance Eq Color where
  Black == Black  = True
  White == White  = True
  _     == _      = False

A class instance is an instance block that contains definitions for the functions in that class. Here we define how == works on Color.

Syntax of Classes and Instances

A type class is defined using class syntax. The functions in the class are given types. Here’s a class Size that contains one function, size:

class Size a where
  size :: a -> Int

Instances of a class are defined with instance syntax we’ve just seen. Here is how we make Int and [a] members of the Size class:

instance Size Int where
  size x = abs x

instance Size [a] where
  size xs = length xs

Our class Size behaves just like existing type classes. We can use size anywhere where a function can be used, and Haskell can infer types with Size constraints for us:

Prelude> :t size
size :: Size a => a -> Int
Prelude> size [True,False]
2
Prelude> let sizeBoth a b = [size a, size b]
Prelude> :t sizeBoth
sizeBoth :: (Size a1, Size a2) => a1 -> a2 -> [Int]

A class can contain multiple functions, and even constants. Here we define a new version of the Size class with more content.

class Size a where
  empty :: a
  size :: a -> Int
  sameSize :: a -> a -> Bool

instance Size (Maybe a) where
  empty = Nothing

  size Nothing = 0
  size (Just a) = 1

  sameSize x y = size x == size y

instance Size [a] where
  empty = []
  size xs = length xs
  sameSize x y = size x == size y

Sidenote: Restrictions on Instances

The Haskell 2010 allows only a very specific type of class instance. Let’s look at some instances that aren’t allowed. The examples use the Size class:

class Size a where
  size :: a -> Int

We saw a Size [a] instance above. Why not define an instance just for lists of booleans?

instance Size [Bool] where
  size bs = length (filter id bs)   -- count Trues
error:
    • Illegal instance declaration for ‘Size [Bool]’
        (All instance types must be of the form
          (T a1 ... an)
         where a1 ... an are *distinct type variables*,
         and each type variable appears at most once in the
           instance head.
         Use FlexibleInstances if you want to disable this.)
    • In the instance declaration for ‘Size [Bool]’

Dang. As the error tries to tell us, we can only define instances where all type parameters are different type variables. That is, we can define instance Size (Either a b) but we can’t define:

  • instance Size (Either String a) – since String is not a type variable
  • instance Size (Either a a) – since the type variables aren’t different
  • instance Size [[a]] – since [a] is not a type variable

Why is this? This rule guarantees that it’s simple for the compiler to look up the correct type class instance. It can just look at what the top-level type constructor is, and then pick an instance.

The GHC Haskell implementation has many extensions to the type class system.

Default Implementations

Did you notice how in the previous example we gave sameSize the same definition in both instances? This is a very common occurrence, and it’s why Haskell classes can have default implementations. As a first example, here’s an Example type class for giving example values of types.

class Example a where
  example :: a          -- the main example for the type `a`
  examples :: [a]       -- a short list of examples
  examples = [example]  -- ...defaulting to the main example

instance Example Int where
  example = 1
  examples = [0,1,2]

instance Example Bool where
  example = True

Here’s how Example works. Note how the default implementation of examples got used in the Bool case but not in the Int case. Also note the need for explicit type signatures to tell GHCi which instance we’re interested in. Without them, we would get an “Ambiguous type variable” error.

Prelude> example :: Bool
True
Prelude> example :: Int
1
Prelude> examples :: [Bool]
[True]
Prelude> examples :: [Int]
[0,1,2]

The standard type classes use lots of default implementations to make implementing the classes easy. Here is the standard definitions for Eq (formatted for readability).

class Eq a where
  (==) ::  a -> a -> Bool
  x == y  = not (x /= y)

  (/=) ::  a -> a -> Bool
  x /= y  = not (x == y)

Note how both operations have a default implementation in terms of the other. This means we could define an Eq instance with no content at all, but the resulting functions would just recurse forever. In practice, we want to define at least one of == and /=.

When there are lots of default implementations, it can be hard to know which functions you need to implement yourself. For this reason class documentation usually mentions the minimal complete definition. For Eq, the docs say “Minimal complete definition: either == or /=.”

Let’s look at Ord next. Ord has 7 operations, all with default implementations in terms of each other. By the way, note the quirky way of defining multiple type signatures at once. It’s okay, it’s a feature of Haskell, this is how Ord is defined in the standard. (We’ll get back to what the (Eq a) => part means soon.)

class  (Eq a) => Ord a  where
  compare              :: a -> a -> Ordering
  (<), (<=), (>=), (>) :: a -> a -> Bool
  max, min             :: a -> a -> a

  compare x y | x == y    = EQ
              | x <= y    = LT
              | otherwise = GT

  x <= y  = compare x y /= GT
  x <  y  = compare x y == LT
  x >= y  = compare x y /= LT
  x >  y  = compare x y == GT

  max x y | x <= y    =  y
          | otherwise =  x
  min x y | x <= y    =  x
          | otherwise =  y

With this definition it’s really hard to know what the minimal complete definition is. Luckily the docs tell us “Minimal complete definition: either compare or <=.”

As a final word on default implementations, if there is never a need to override the default definition, the function can be moved out of the class for simplicity. Consider a class like Combine below:

class Combine a where
  combine :: a -> a -> a
  combine3 :: a -> a -> a -> a
  combine3 x y z = combine x (combine y z)

It’s hard to think of a case where combine3 would be given any other definition, so why not move it out of the class:

class Combine a where
  combine :: a -> a -> a

combine3 :: Combine a => a -> a -> a -> a
combine3 x y z = combine x (combine y z)

As an example, here are the Eq and Ord instances for a simple pair type. Note how the definition uses the minimal complete definition rules by only defining == and <=.

data IntPair = IntPair Int Int
  deriving Show

instance Eq IntPair where
  IntPair a1 a2 == IntPair b1 b2  =  a1==b1 && a2==b2

instance Ord IntPair where
  IntPair a1 a2 <= IntPair b1 b2
     | a1<b1     = True
     | a1>b1     = False
     | otherwise = a2<=b2
*Main> (IntPair 1 2) < (IntPair 2 3)
True
*Main> (IntPair 1 2) > (IntPair 2 3)
False
*Main> compare (IntPair 1 2) (IntPair 2 3)
LT
*Main Data.List> sort [IntPair 1 1,IntPair 1 4,IntPair 2 1,IntPair 2 2]
[IntPair 1 1,IntPair 1 4,IntPair 2 1,IntPair 2 2]

Useful Stuff

Deriving

As we’ve seen many times already, deriving is a way to get automatically generated class instances. The Read and Show classes should pretty much always be derived to get the standard behaviour. The derived instance for Eq is typically what you want. It requires constructors and fields to match.

The derived Ord instance might not be what you want. It orders constructors left-to-right, and then compares fields inside constructors left-to-right. An example:

data Person = Dead | Alive String Int
  deriving (Show, Eq, Ord)
Prelude> Dead < Alive "Bob" 35
True
Prelude> Alive "Barbara" 35 < Alive "Clive" 17
True
Prelude> Alive "Clive" 17 < Alive "Clive" 30
True

Asking GHCi About Classes

You can use the :info command in GHCi to get the contents and instances of a class. These days the info even includes the minimal complete definition (see the MINIMAL pragma). For example:

Prelude> :info Num
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
  {-# MINIMAL (+), (*), abs, signum, fromInteger,
      (negate | (-)) #-}
instance Num Word -- Defined in ‘GHC.Num’
instance Num Integer -- Defined in ‘GHC.Num’
instance Num Int -- Defined in ‘GHC.Num’
instance Num Float -- Defined in ‘GHC.Float’
instance Num Double -- Defined in ‘GHC.Float’

Hierarchies

Both classes and instances can form hierarchies. This means that a class or instance depends on another class or instance.

Instance Hierarchies

Let’s try to define an Eq instance for a simple pair type:

data Pair a = MakePair a a
  deriving Show

instance Eq (Pair a) where
  (MakePair x y) == (MakePair a b)   =   x==a && y==b
error:
  • No instance for (Eq a) arising from a use of ‘==’
    Possible fix: add (Eq a) to the context of the
    instance declaration
  • In the first argument of ‘(&&)’, namely ‘x == a’
    In the expression: x == a && y == b
    In an equation for ‘==’:
        (MakePair x y) == (MakePair a b) = x == a && y == b

The compiler is trying to tell us that our Eq (Pair a) instance needs an Eq a instance to work. How can we compare pairs of values of type a if we can’t compare values of type a? To solve this we need to add a type constraint to the instance declaration, just like we’ve added type constraints to function definitions.

instance Eq a => Eq (Pair a) where
  (MakePair x y) == (MakePair a b)   =   x==a && y==b

Now we can compare pairs, as long as the element type is comparable. However, we can’t compare, say, pairs of functions, since functions don’t have an Eq instance.

Prelude> MakePair 1 1 == MakePair 1 1
True
Prelude> MakePair reverse reverse == MakePair reverse reverse

<interactive>:15:1: error:
  • No instance for (Eq ([a0] -> [a0])) arising from
      a use of ‘==’ (maybe you haven't applied a function
      to enough arguments?)
  • In the expression:
      MakePair reverse reverse == MakePair reverse reverse
    In an equation for ‘it’:
        it = MakePair reverse reverse ==
               MakePair reverse reverse

Let’s continue with another example. Here’s a simple type class and an instance

class Check a where
  check :: a -> Bool

instance Check Int where
  check x = x > 0

Now we can write a function that checks a list:

checkAll :: Check a => [a] -> Bool
checkAll = and (map check xs)

In order to turn this into a Check [a] instance, we need to add a constraint to the instance declaration. Our Check [a] is based on the Check a instance.

instance Check a => Check [a] where
  check xs = and (map check xs)

This means that our Check [a] instance is only valid when there is a corresponding Check a instance. For example, if we try to invoke a Check [Bool] instance, we get an error about the missing Check Bool instance:

Prelude> check [True,False]

<interactive>:1:1: error:
    • No instance for (Check Bool) arising from
      a use of ‘check’
    • In the expression: check [True, False]
      In an equation for ‘it’: it = check [True, False]

Also, if we try to define Check [a] instance without the constraint, we get an error (with a pretty good suggestion!)

  • No instance for (Check a) arising from a use of ‘check’
    Possible fix:
      add (Check a) to the context of the instance
      declaration

If you think about it, this instance hierarchy allows us to circumvent the limitation that we can’t make a Check [Int] instance.

Finally, sometimes multiple constraints are needed. Consider for example the Eq instance for Either:

instance (Eq a, Eq b) => Eq (Either a b) where
  Left x  == Left y   =  x==y
  Right x == Right y  =  x==y
  _       == _        =  False

Class Hierarchy

Respectively, a class can depend on another class. This is useful for instance when you want to use functions from another class in your default implementations:

class Size a where
  size :: a -> Int

class Size a => SizeBoth a where
  sizeBoth :: a -> a -> Int
  sizeBoth x y = size x + size y

In cases like this we say SizeBoth is a subclass of Size. Note again the confusion with object oriented programming. Examples of subclasses in the standard library include:

class Eq a => Ord a where
  ...
class Num a => Fractional a where
  ...

Another way to look at subclasses is that if you have a class Main a => Sub a, you must provide an instance Main MyType in order to be able to declare instance Sub MyType.

Self Checks

Check 1

What are the functions in the Eq class?

  1. (==), (/=)
  2. (==)
  3. (==), (<), (>)

Check 2

For which of the following classes can we get automatic instances with deriving?

  1. Num
  2. Ord
  3. Size

Check 3

Which of the following instance declarations is legal?

  1. instance Eq Maybe
  2. instance Eq (a,a)
  3. instance Eq (Maybe Int)
  4. instance Eq (a,b)

Check 4

Given the following definition of the class BitOperations

class BitOperations a where
  bitNot :: a -> a
  bitNot x = bitNand bitTrue x
  bitAnd :: a -> a -> a
  bitAnd x y = bitNot (bitOr (bitNot x) (bitNot y))
  bitOr :: a -> a -> a
  bitOr x y = bitNot (bitAnd (bitNot x) (bitNot y))
  bitNand :: a -> a -> a
  bitNand x y = bitNot (bitAnd x y)

which set of operations is not a minimal complete definition of BitOperations?

  1. bitNand, bitAnd
  2. bitAnd, bitOr
  3. bitAnd, bitNot
  4. bitNot, bitOr

Check 5

The declaration instance Num a => Eq (Pair a) tells me that

  1. All instances of Num are instances of Eq
  2. Pair a is an instance of Eq if a is an instance of Num
  3. The instance Eq (Pair a) inherits the instance Num a

Check 6

The declaration class Num a => Fractional a tells me that

  1. All instances of Fractional must be instances of Num
  2. All instances of Num must be instances of Fractional
  3. If I define an instance for Fractional, I also get an instance for Num
  4. If I define an instance for Num, I also get an instance for Fractional

Acknowledgment:

This reading was originally written by Joel Kaasinen and John Lång in their open source textbook. The book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License, which allows users to copy, modify, and distribute the book.

This reading was modified by Titus H. Klinge in 2021 and presented above under the same license in order to better serve the students of this course.