Learn You a Haskell for Great Good! - Miran Lipovaca [63]
...
As mentioned previously, there are a lot of places where we can cram in class constraints. So this is just like writing class Num a where, but we state that our type a must be an instance of Eq. We’re essentially saying that we need to make a type an instance of Eq before we can make it an instance of Num. Before some type can be considered a number, it makes sense that we can determine whether values of that type can be equated.
That’s all there is to subclassing—it’s just a class constraint on a class declaration! When defining function bodies in the class declaration or in instance declarations, we can assume that a is a part of Eq, so we can use == on values of that type.
Parameterized Types As Instances of Type Classes
But how are the Maybe or list types made as instances of type classes? What makes Maybe different from, say, TrafficLight is that Maybe in itself isn’t a concrete type—it’s a type constructor that takes one type parameter (like Char) to produce a concrete type (like Maybe Char). Let’s take a look at the Eq type class again:
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
x == y = not (x /= y)
x /= y = not (x == y)
From the type declarations, we see that a is used as a concrete type because all the types in functions must be concrete. Remember that you can’t have a function of the type a -> Maybe, but you can have a function of the type a -> Maybe a or Maybe Int -> Maybe String. That’s why we can’t do something like this:
instance Eq Maybe where
...
The a must be a concrete type, and Maybe is not; it’s a type constructor that takes one parameter and then produces a concrete type.
It would also be tedious if we needed to make a separate instance for every possible type that Maybe’s type parameter could take on. If we needed to write instance Eq (Maybe Int) where, instance Eq (Maybe Char) where, and so on for every type, we would get nowhere. That’s why we can just leave the parameter as a type variable, like so:
instance Eq (Maybe m) where
Just x == Just y = x == y
Nothing == Nothing = True
_ == _ = False
This is like saying that we want to make all types of the form Maybe something an instance of Eq. We actually could have written (Maybe something), but using single letters conforms to the Haskell style.
The (Maybe m) here plays the role of the a from class Eq a where. While Maybe isn’t a concrete type, Maybe m is. By specifying a type parameter as a type variable (m, which is in lowercase), we said that we want all types that are in the form of Maybe m, where m is any type, to be an instance of Eq.
There’s one problem with this though. Can you spot it? We use == on the contents of the Maybe, but we have no assurance that what the Maybe contains can be used with Eq! That’s why we modify our instance declaration like this:
instance (Eq m) => Eq (Maybe m) where
Just x == Just y = x == y
Nothing == Nothing = True
_ == _ = False
We needed to add a class constraint! With this instance declaration, we say that we want all types of the form Maybe m to be part of the Eq type class, but only those types where the m (what’s contained inside the Maybe) is also a part of Eq. This is actually how Haskell would derive the instance.
Most of the time, class constraints in class declarations are used for making a type class a subclass of another type class, and class constraints in instance declarations are used to express requirements about the contents of some type. For instance, here we required the contents of the Maybe to also be part of the Eq type class.
When making instances, if you see that a type is used as a concrete type in the type declarations (like the a in a -> a -> Bool), you need to supply type parameters and add parentheses so that you end up with a concrete type.
Take into account that the type you’re trying to make an instance of will replace the parameter in the class declaration. The a from class Eq a where will be replaced with a real type when you make an instance, so try to mentally put your type into the function type