Haskell Type Classes
Recall that Haskell's type system is polymorphic, so that we can define useful abstractions can be defined to apply in many situations.
For example, the map
functional can be used to process a list of elements of one type (say a
) to produce a corresponding list of elements of another type (say b
). The polymorphic type signature of map
reflects this in a fully general form.
map :: (a -> b) -> [a] -> [b]
Here the types a
and b
can stand for any types whatsoever.
Constrained Polymorphism
Sometimes, however, we need to impose constraints on the types that are used for a polymorphic abstraction.
A common constraint is that a type support the equality (==
) operator. For example, if we need a function to check whether a particular value is found as an element of a list, we might produce the following definition.
isElem x [] = False isElem x (a:as) | x == a = True | otherwise = isElem x as
Here, this function could work for lists of any types of element, so long as the ==
operator is defined.
Type Constraints
If we load this implementation into Haskell, we see that Haskell reflects the requirement for the ==
operator with a type constraint.
Prelude> :load isElem.hs [1 of 1] Compiling Main ( isElem.hs, interpreted ) Ok, modules loaded: Main. *Main> :t isElem isElem :: Eq a => a -> [a] -> Bool *Main>
Here, the notation Eq a =>
means that the type a
must be an instance of the type class Eq
, the class of types supporting the ==
operator.
The Eq
Class
The Haskell standard library includes a definition of the type class Eq
.
class Eq a where
(==), (/=) :: a -> a -> Bool
-- Minimal complete definition:
-- (==) or (/=)
x /= y = not (x == y)
x == y = not (x /= y)
In essence, this states two properties of the Eq
class.
- it is the class of types that support the two operators
==
and/=
, and - if one of the operators is defined, then the other is automatically available as well.
In fact, many standard types are automatically defined to be members of the Eq
class, so we can use our isElem
function in many contexts without further ado.
*Main> isElem 'a' "back" True *Main> isElem [1] [[1], [2]] True
Can you think of any types that are not members of the ==
class?
Instance Declarations
To create an instance of the Eq
class, we can use an instance declaration. For example, suppose we want a rational number data type to be the ratio of two integers, and to assert that two ratios are equal according to the usual rules of arithmetic. The following definition suffices.
data RationalNum = Ratio Int Int
instance Eq RationalNum where
(Ratio a b) == (Ratio c d) = a * d == b * c
Given this definition, we can now automatically use our isElem
function on RationalNum
s.
*Main> isElem (Ratio 3 4) [Ratio 1 1, Ratio 6 8] True
Another way that we can define instances of the Eq
class is through a deriving
declaration. For example, consider
the following NumPair
algebraic data type.
data NumPair = Pair Int Int deriving Eq
This declaration tells Haskell to automatically define the ==
function for this data type, based on structural equality of the components of the type.
*Main> isElem (Pair 3 4) [Pair 1 1, Pair 6 8] False *Main> isElem (Pair 3 4) [Pair 1 1, Pair 3 4] True
Multiple Constraints
You can use multiple type constraints in a single definition. Here is an example from the Classes and Types section of the Haskell Wikibook.
foo :: (Num a, Show a, Show b) => a -> a -> b -> String
foo x y t =
show x ++ " plus " ++ show y ++ " is " ++ show (x+y) ++ ". " ++ show t
The Num
Class
The Num
class is another important example: the class of
all numeric types. It allows polymorphic functions
such as (+)
to be defined for all numeric types.
(+) :: (Num a) => a -> a -> a
Here the type context (Num a) =>
states that a
is an instance of type clase Num
, i.e., a numeric type.
Three important instances of type class Num
are
Int
- fixed precision integers based on machine register size (typically 32-bit or 64-bit).Integer
- arbitrary precision integers.Float
- single-precision floating point numbers.
Numeric literals such as 17
and 5.21
are also polymorphic.
Prelude> :type 17 17 :: Num a => a Prelude> :type 5.21 5.21 :: Fractional a => a
Fractional
is a subclass of Num
that excludes Int
and Integer
. Depending on context, type inference narrows types to ones which support given operations.
Prelude> :type 17 + 5.21 17 + 5.21 :: Fractional a => a Prelude> 17 + 5.21 22.21
In the example, 17
is first narrowed from Num
to Fractional
to match the polymorphic Fractional
type.
The Ord
Class
The Ord
Class is for types that have an ordering that supports the (<)
, (<=)
, (>=)
and (>)
functions.
Many standard types are members of the Ord
class automatically.
Prelude> [3] < [4] True
What does it mean for one list to be less than another?
Instance declarations for Ord
only need to define one
of the comparison functions.
instance Ord RationalNum where
(Ratio a b) <= (Ratio c d) = a * d <= b * c
The other functions are automatically defined.
*Main> Ratio 3 4 < Ratio 1 1 True *Main> Ratio 3 4 < Ratio 6 8 False
Instances can also be generated automatically with the deriving
keyword.
data NumPair = Pair Int Int deriving (Eq, Ord)
The comparison functions are generated automatically.
*Main> Pair 1 2 < Pair 1 3 True *Main> Pair 3 4 < Pair 6 8 True *Main> Pair 3 4 < Pair 3 1 False
Type Classes vs. OOP Classes
Although there are some similarities, Haskell type classes are quite different from classes in OOP. Do not confuse them.