Part 18

Type Classes and Constraints

What are type classes?

How can Haskell’s + work on both Ints and Doubles? Why can I compare all sorts of things with ==? We’ve briefly mentioned constrained types earlier. Let’s see what they really mean. Let’s look at the types of == and +.

(==) :: (Eq a) => a -> a -> Bool

The type (Eq a) => a -> a -> Bool means: for all types a that belong to the class Eq, this is a function of type a -> a -> Bool. That is, if the type a is a member of the class Eq, you can give two values of type a to == and get a Bool result.

(+) :: (Num a) => a -> a -> a

Similarly, the type (Num a) => a -> a -> a means: for all types a that belong to the class Num, this is a function of type a -> a -> a. That is, you can give two values of the same type a to + and get out a third value of type a, as long as a is a member of Num.

Num and Eq are type classes. A type class is a way to group together types that support similar operations.

Note! A type class is a collection of types. It doesn’t have much to do with the classes of object oriented programming! In some situations, type classes can act like interfaces in object oriented programming. Unfortunately the functions in a type class are often called methods, adding to the confusion.

PS. remember how using type variables for polymorphism was called parametric polymorphism? The fancy word for what type classes achieve is ad-hoc polymorphism. The difference is that with parametric polymorphism the function (e.g. head) has the same implementation for all types, whereas with ad-hoc polymorphisms there are multiple implementations (consider == on numbers and strings).

Type constraints

When you’re working with a concrete type (not a type variable), you can just use type class functions (in this case, (==)):

f :: (Int -> Int) -> Int -> Bool
f g x = x == g x

Of course, if the type in question isn’t a member of the right class, you get an error. For example:

addTrue :: Bool -> Bool
addTrue b = b + True

error:No instance for (Num Bool) arising from a use of+’
    • In the expression: b + True
        In an equation foraddTrue: addTrue b = b + True

However in a polymorphic function, you need to add type constraints. This doesn’t work:

f :: (a -> a) -> a -> Bool
f g x = x == g x

Luckily the error is nice:

error:No instance for (Eq a) arising from a use of==Possible fix:
        add (Eq a) to the context of
            the type signature for:
            f :: (a -> a) -> a -> BoolIn the expression: x == g x
        In an equation forf: f g x = x == g x

To signal that f only works on types that are members of the Eq class, we add a type constraint (Eq a) => to the type annotation.

f :: (Eq a) => (a -> a) -> a -> Bool
f g x = x == g x

If you don’t have a type annotation, type inference can provide the constraints!

Prelude> f g x = x == g x
Prelude> :type f
f :: (Eq a) => (a -> a) -> a -> Bool

You can also have multiple constraints:

bothPairsEqual :: (Eq a, Eq b) => a -> a -> b -> b -> Bool
bothPairsEqual left1 left2 right1 right2 = left1 == left2 && right1 == right2

Or multiple constraints on the same type:

sumIfEqual :: (Eq a, Num a) => a -> a -> a
sumIfEqual x y = if x == y then x+y else x

Note that, including a type constraint like Num does not mean you immediately also get Eq or Ord.

Exercises

All exercises can be found in Set4a and Set4b. Please pay attention in the title of the exercise in which file the exercises of this section can be found.

Exercises from 4a:

You have reached the end of this section! Continue to the next section:

You can check your current points from the blue blob in the bottom-right corner of the page.