Type Classes and Constraints
What are type classes?
How can Haskell’s +
work on both Int
s and Double
s? 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 for ‘addTrue’: 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 -> Bool
• In the expression: x == g x
In an equation for ‘f’: 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 can check your current points from the blue blob in the bottom-right corner of the page.