Skip to content

Default Function Implementations

The first thing you probably noticed is that our implementation of Eq for BannerNumber was a bit redundant:

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

Clearly, B x == B y is True if and only if B x /= B y is False. We really shouldn't have to implement both (==) and (/=). When defining type classes, we can provide default implementations of the functions in these type classes, just as we can provide default implementations of methods in Java interfaces and default implementations of functions in traits in Rust or Go:

class Eq a where

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

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

The default implementation of (==) is as the negation of (/=), and vice versa. We can now make BannerNumber an instance of Eq by defining only (==):

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

This replaces the default implementation of (==) with the one given here that is specific to BannerNumber. For inequality testing, BannerNumber uses the default implementation because we did not provide a specific implementation as part of the instance definition.

The two default implementations of (==) and (/=) in their current form create a problem. Since default implementations for both methods are provided, the compiler won't complain if we use the default implementations for both (==) and (/=), not overriding any of them:

instance Eq BannerNumber

If we now try to compare two banner numbers, we run into trouble:

GHCi
>>> :{
  | class Eq a where
  |     (==) :: a -> a -> Bool
  |     x == y = not (x /= y)
  |
  |     (/=) :: a -> a -> Bool
  |     x /= y = not (x == y)
  | :}
>>> newtype BannerNumber = B Int
>>> instance Eq BannerNumber
>>> B 1 == B 2
^CInterrupted.

The reason should be obvious. To decide whether B 1 == B 2, the default implementation calls B 1 /= B 2 and negates the result. To decide whether B 1 /= B 2, the default implementation calls B 1 == B 2 and negates the result. To decide whether B 1 == B 2, ..., we have ourselves a nice infinite recursion.

To break this recursion, we need to provide a custom implementation of at least one of (==) and (/=). The Haskell compiler allows us to annotate the class definition to enforce that every instance definition does exactly this. Here is what this looks like for Eq:

class Eq a where

  {-# MINIMAL (==) | (/=) #-}

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

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

This says that a minimal implementation must provide at least one of (==) and (/=). It is okay for an instance of Eq to provide implementations for both (==) and (/=), but this isn't required. We won't go any further into these annotations, as we can write perfectly correct code without them. When defining type classes to be used by others, however, it is important to add these annotations because the user of your class should not have to check the default implementations to figure out which subset of them need to be overriden to break infinite recursive dependencies between them.

If you exit and restart GHCi to get rid of our problematic definition of Eq, then you're back to using the definition provided as part of the standard library, which is the one that includes the MINIMAL pragma. With this definition of Eq, we can no longer make BannerNumber an instance of Eq without providing any implementation of (==) or (/=):

GHCi
>>> newtype BannerNumber = B Int
>>> instance Eq BannerNumber

<interactive>:2:10: warning: [-Wmissing-methods]
    • No explicit implementation for
        either ‘==’ or ‘/=’
    • In the instance declaration for ‘Eq BannerNumber’

The compiler gives exactly the expected error message: We are required to provide a BannerNumber-specific implementation of at least one of (==) or (/=). We didn't provide either.1

If we provide an implementation of (==), then both (==) and (/=) work, the latter via the default implementation in the Eq class:

GHCi
>>> :{
  | instance Eq BannerNumber where
  |     B x == B y = x == y
  | :}
>>> B 1 == B 2
False
>>> B 1 /= B 2
True

  1. Actually, the compiler only gave us a warning. This means that if we really wanted to, GHCi would still let us try to compare banner numbers at this point, with the same disastrous effect as before. In my opinion, failing to provide required implementations of default methods in type class instances should be an error because no good will come of ignoring this problem in our code.