Typeclasses
Typeclasses add constraints to polymorphic types:
- As usual, one can (with the
ExplicitForall
extension), write this:forall a. Eq a => (a, a) -> Bool
, which you may find clearer.
The type of notEqual
states: for any type a
such that a
is an instance of the Eq
typeclass, give me a pair of a
s and I will return a Bool
.
Note
Eq a
is not a type, but rather a different kind of entity, called a constraint.
Eq
is referred to as a typeclass.
Typeclasses¶
Typeclasses, such as Eq
, are defined as follows:
- The actual definition has a second method,
(/=)
, omitted here for clarity.
Here, (==)
is a method of the Eq
typeclass.
To make a type be an instance of a type class, one writes a definition of the method(s) for the type in question:
Automatically deriving instances¶
> data Piece = Bishop | Knight deriving (Eq, Ord, Show)
> Bishop == Knight
False
> show Bishop
"Bishop"
> import Data.List
> sort [Knight, Bishop]
[Bishop,Knight]
Constraint implication (classes)¶
One typeclass may depend on another:
What this means is that any instance of Monoid must first be an instance of Semigroup (as well as implementing the Monoid
method mempty
).
This means that if you encounter a type that is an instance of Monoid
, then it will be an instance of Semigroup
and so you can use the method <>
. For this reason, this is often called inheritance , although the relationship to inheritance in other languages is not direct.
Constraint implication (instances)¶
Read this as saying: for any type a
, if a
is an instance of Eq
, then [a]
is also an instance of Eq
.
Warning
Haskell can be a little picky about when you are allowed to do this, but bear in mind that mostly you will be using typeclasses and instances, rather than writing your own.
This allows Haskell's type checker to make potentially quite complex deductions. For example:
Haskell knows that ==
can be called on x
and y
. How?
- It knows that
x
andy
both have type[a]
- It knows that
a
is an instance ofOrd
(from the type signature) - It knows that
Ord a
impliesEq a
- It knows that
Eq a
impliesEq [a]
Note
Libraries like lens use the ability of the type checker to make these deductions in sophisticated ways.
Typeclass error messages¶
> import Data.List
> data Piece = Bishop | Knight deriving Eq
> sort [Knight, Bishop]
"No instance for (Ord Piece) arising from a use of ‘sort’..."
The error is raised because sort
has type sort :: Ord a => [a] -> [a]
, which means that it expects as input, a list of values of a type which is an instance of the Ord
class.
Using typeclasses¶
Warning
It is recommended that you avoid creating your own type classes unless it is entirely necessary. This is because:
- There is usually a solution to a problem which doesn't require typeclasses.
- It is easy to create a typeclass that is badly designed.
Instead, rely on existing type classes from libraries.
Constraints propagate¶
Because notEqual
is called on (x, x)
, x
must be of a type that is an instance of Eq
.
The compiler will reason in this way, even if you don't write a type signature.
Tip
A common difficulty that you may encounter is that you don't know what instance of a typeclass is being invoked:
-- first example
{-# LANGUAGE OverloadedStrings #-}
import Data.Text
example :: Text
example = "hello" `append` mempty
In this case, you know the type of mempty
, which is mempty :: forall a. Monoid a => a
. However, you do not know which instance of Monoid
is being used when mempty
is called. Mousing over mempty
in VSCode will reveal that the instance is Text
.
You can then look up Text
on Hackage and find the source, which gives the definition of mempty
for Text
.
Type class recursion¶
Type class instances may use the very method they are defining in the definition.
Note
Here, ==
on the right hand side of the definition is the Eq
method for Int
, but on the right hand side, it is the method for (Int, Int)
.
Type classes over others kinds¶
Created: January 8, 2023