Skip to content

Deriving Instances Automatically

Let's have one more look at our implementation of the Eq instance for our BannerNumber type:

newtype BannerNumber = B Int

instance Eq BannerNumber where
    B x == B y = x == y

It's better than the version we had before where we defined both (==) and (/=), but it's still all boilerplate. All we're doing is to remove the data constructor B and then test the two wrapped integers x and y for equality. Here that's not so bad, but think about implementing an equality test for student records that implements the logic that two student records are the same if they have all the same fields:

data StudentRecord = SR
    { bannerNumber :: BannerNumber
    , name         :: String
    , address      :: String
    , transcript   :: Transcript
    }

instance Eq StudentRecord where
    x == y =  bannerNumber x == bannerNumber y
           && name         x == name         y
           && address      x == address      y
           && transcript   x == transcript   y

This gets tedious very quickly. Thankfully, Haskell has a mechanism to autogenerate this type of code for us, making it necessary to provide custom implementations of standard type classes, such as Eq, Ord, etc. only if we want them to deviate from the default behaviour. Here is how we can get Eq, Ord, and Show instances for banner numbers and student records in one fell swoop, with minimal typing:

newtype BannerNumber = B Int
    deriving (Eq, Ord, Show)

data Transcript = T
    deriving (Eq, Ord, Show)

data StudentRecord = SR
    { bannerNumber :: BannerNumber
    , name         :: String
    , address      :: String
    , transcript   :: Transcript
    }
    deriving (Eq, Ord, Show)

That's it. The deriving clause at the end of a type definition tells the compiler to auto-generate instances of the listed type classes for the type we're defining. This doesn't work for all type classes. It is built into the standard library for Eq, Ord, Enum, Ix, Bounded, Read, and Show. There are also mechanisms to teach the compiler to auto-derive instances of new type classes we define, but this is beyond the scope of this book.

The standard instance for Eq behaves exactly like the one for StudentRecord we defined above: Two values of a given type are equal if and only if they were constructed using the same data constructor and the arguments to the data constructor are pairwise equal. In particular, this means that we can derive an Eq instance only if all argument types of data constructors are instances of Eq. That's why we had to derive Eq, Ord, and Show instances for Transcript above to ensure that the deriving clause for StudentRecord succeeds.

Similarly, the standard instance for Ord implements essentially lexicographic comparison:

  • If x and y were constructed using different data constructors, then x < y if the constructor used to build x comes before the constructor used to build y in the type definition.

    That's why with the definition data Bool = False | True, we have False < True.

  • If x and y were constructed using the same data constructor, then we compare the fields of x and y one by one. Let's call these fields x1 x2 ... xk and y1 y2 ... yk. If x1 /= y1, then compare x y = compare x1 y1. If x1 == y1 but x2 /= y2, then compare x y = compare x2 y2. This continues until we find the first i such that xi /= yi. If each field of x is equal to the corresponding field of y, then compare x y = EQ.

Thus, once again, we can auto-derive an instance of Ord for some type only if all fields of this type are instances of Ord.

We won't discuss the behaviour of the auto-derived implementations of Enum, Ix, Bounded, Read or Show.