6. Typeclasses

In addition to the parametric polymorphism of type variables Haskell offers ad-hoc polymorphism via a concept called type classes. Conceptually a type class groups a set of types for which there exists a common behaviour.

Practically a typeclass is the same as an interface in Java or C#. It defines a set of methods which a must be implemented for a certain type.

6.1. Defininig classes

The class is defined with the keyword class (think interface in Java) followed by a name for the class. [1] Following this is a type variable [2] which is a reference for the actual type. This variable is subsequently used in the method signatures to reference the type. For instance Ord is used to implement ordering for values.

class Ord a where

6.1.1. Member functions

In the body of the definition follows a number of declaraions for whats called methods. Methods are functions which must be implemented for a type to be member of this class. Below is an excerpt of the Ord typeclass as an example.

class Ord a where

    compare :: a -> a -> Ordering
    (<=) :: a -> a -> Bool
    max :: a -> a -> a

6.1.2. Superclasses

Classes can have a so called superclasses. This essentially just defines another class to be a dependency for the declaration of an instance of this class. In short: A class can depend on another class.

class Eq a => Ord a where

    compare :: a -> a -> Ordering
    (<=) :: a -> a -> Bool
    max :: a -> a -> a

6.1.3. Default implementations

Lastly methods of the class can have default implementation in which may use both other methods of the class or methods of the superclasses.

class Eq a => Ord a where

    compare :: a -> a -> Ordering
    compare x y = if x == y then EQ
                else if x <= y then LT
                else GT

    (<=) :: a -> a -> Bool
    x <= y = case compare x y of { GT -> False; _ -> True }

    max :: a -> a -> a
    max x y = if x <= y then y else x

6.2. Constraining types

Unlike in Java where, if we wish to use an interface, we simply declare the type to be the interface in Haskell we constrain the type to implement the class. Constraints precede the arguments and return type in a type signature. Constraints are always placed on type variables. An ascii double arrow separates the constraints and the rest of the type signature.

max3 :: Ord a => a -> a -> a -> a
max3 a1 a2 a3 = a1 `max` a2 `max` a3

The advantage of this is that we can require multiple classes for a single type. In this case the constraints are listed comma separated and surrounded by parentheses.

showMax3 :: (Show a, Ord a) => a -> a -> a -> String
showMax3 a1 a2 a3 = show (max3 a1 a2 a3)

6.3. Implementing classes

The Haskell model of implementing classes is similar to that of Rust and Swift. Like in those languages an instance of the class (interface) can be declared anywhere. It does not have to happen at the place where the type is defined. The only constraint is that there must not be an existing instance to the class in scope. Typically the instances of the class are either defined where the type is defined or where the class is defined. This prevents situations where two instances of the same class for the same type are in scope.

Implementations of the class are done using the instance keyword, otherwise they are very similar to the class declaration. The instance keyword is followed by the class name and then the type name for which the instance is to be declared.

data MyType = TheSmallest | TheMiddle | TheLargest

instance Eq MyType where

instance Ord MyType where

In the body of the declaration follow definitions for each of the methods of the class.

data MyType = TheSmallest | TheMiddle | TheLargest

instance Eq MyType where
    TheSmallest == TheSmallest = True
    TheMiddle   == TheMiddle   = True
    TheLargest  == TheLargest  = True
    _           == _           = False

instance Ord MyType where

    compare TheSmallest TheSmallest = EQ -- EQual
    compare TheLargest TheLargest = EQ
    compare TheMiddle TheMiddle = EQ
    compare TheSmallest _ = LT -- Less Then
    compare TheLargest  _ = GT -- Greater Then
    compare _ TheLargest = LT
    compare _ TheSmallest = GT

    TheSmallest <= _ = True
    _ <= TheLargest = True
    TheLargest <= _ = False
    _ <= TheSmallest = False

6.3.1. Deriving classes

For some classes like Eq, Ord, Show and Read you may let GHC automatically define an instance for you. This is done using the deriving keyword after the type definition. The exact semantics of those derived classes can be found in the ghc manual.

data T = A | B Int deriving (Show, Eq, Ord)

footnotes

[1]The naming schema for class names is the same as for types and constructors.
[2]Using the MultiParamTypeClasses language extensions allows one to define type classes over multiple parameters.