These notes concern overloading.
Consider testing for membership of a Boolean list, where eqBool is an equality-testing function for booleans.
elemBool :: Bool -> [Bool] -> Bool elemBool x [] = False elemBool x (y:ys) = eqBool x y || elemBool x ys
The above is not very general. It works for booleans, but what if we want to handle lists of integers? or of characters?
Thus let's consider testing for membership of a general list, with the equality function as a parameter.
elemGen :: (a -> a -> Bool) -> a -> [a] -> Bool elemGen eqFun x [] = False elemGen eqFun x (y:ys) = eqFun x y || elemGen eqFun x ys elemBool :: Bool -> [Bool] -> Bool elemBool = elemGen (eqBool)
But really the function elemGen is too general for the intended function. Parameter eqFun could be any
a -> a -> Boolfunction, not just equality.
Another problem is that equality is a meaningless idea for some data types. For example, comparing functions for equality is a computationally intractable problem.
The alternative to the above to make (==) (i.e., equality) an overloaded function. We can then restrict the polymorphism in elem's type signature to those types for which (==) is defined.
We introduce the concept of type classes to to be able to define the group of types for which an overloaded operator can apply.
We can then restrict the polymorphism of a type signature to a class by using a context constraint as "Eq a =>" is used below:
elem :: Eq a => a -> [a] -> Bool
We can define class Eq to be the set of types for which we define the (==) (i.e., equality) operation.
For example, we might define the class as follows, giving the type signature(s) of the associated function(s) (also called the operations or methods of the class).
class Eq a where (==) :: a -> a -> Bool
A type is made a member or instance of a class by defining the signature function(s) for the type. For example, we might define Bool as instance of Eq as follows:
instance Eq Bool where True == True = True False == False = True _ == _ = False
Other types, such as the primitive types Int and Char, can also be defined as instances of the class. Comparison of primitive data types will often be implemented as primitive operations built into the computer hardware, as follows:
instance Eq Int where (==) = primEqInt instance Eq Char where (==) = primEqChar
An instance declaration can also be declared with a context constraint, such as in the equality of lists:
instance Eq a => Eq [a] where [] == [] = True (x:xs) == (y:ys) = x==y && xs==ys _ == _ = False
Within the class Eq, the (==) function is overloaded. The definition of (==) given for the types of its actual operands is used in evaluation.
In the Haskell standard prelude, the class definition for Eq includes both the equality and inequality functions. They may also have default definitions as follows:
class Eq a where (==), (/=) :: a -> a -> Bool -- Minimal complete definition: (==) or (/=) x /= y = not (x==y) x == y = not (x/=y)
In the case of Eq, inequality is defined as the negation of equality and vice versa.
An instance declaration must override (i.e., redefine) at least one of these functions (in order to break the circular definition), but the other function may either be left with its default definition or overridden.
Another example class is Visible, which might denote types whose values can be displayed as strings.
class Visible a where toString :: a -> String size :: a -> Int
Various data types can be made instances of this class:
instance Visible Char where toString ch = [ch] size _ = 1 instance Visible Bool where toString True = "True" toString False = "False" size _ = 1 instance Visible a => Visible [a] where toString = concat . map toString size = foldr (+) 1 . map size
Haskell supports the concept of class extension. That is, a new class can be defined that inherits all the operations of another class and adds additional operations.
For example, an ordering class Ord can be derived from the class Eq, perhaps as follows. (The definition in the standard prelude differs from the following.)
class Eq a => Ord a where (<), (<=), (>), (>=) :: a -> a -> Bool max, min :: a -> a -> a -- Minimal complete definition: (<) or (>) x <= y = x < y || x == y x < y = y > x x >= y = x > y || x == y x > y = y < x max x y | x >= y = x | otherwise = y min x y | x <= y = x | otherwise = y
Using the above definition, Ord is a subclass of Eq. Eq is a superclass of Ord.
A function such as isort (insertion sort) uses class Ord to defining sorting of ordered data items, as follows:
iSort :: Ord a => [a] -> [a] iSort [] = [] iSort (x:xs) = ins x (iSort xs) ins :: Ord a => a -> [a] -> [a] ins x [] = [x] ins x (y:ys) | x <= y = x:y:ys | otherwise = y : ins x ys
Haskell also permits classes to be constrained by two or more other classes.
Consider the problem of sorting a list and then displaying the results as a string:
vSort :: (Ord a,Visible a) => [a] -> String vSort = toString . iSort
To sort the elements, they need to be from an ordered type. To convert the results to a string, we need them to be from a Visible type.
The multiple contraints can be over two different parts of the signature of a function. Consider a program that displays the second components of tuples if the first component is equal to a given value:
vLookupFirst :: (Eq a,Visible b) => [(a,b)] -> a -> String vLookupFirst xs x = toString (lookupFirst xs x) lookupFirst :: Eq a => [ (a,b) ] -> a -> [b] lookupFirst ws x = [ z | (y,z) <- ws , y==x ]
Multiple constraints can occur in an instance declaration, such as might be used in extending equality to cover pairs:
instance (Eq a,Eq b) => Eq (a,b) where (x,y) == (z,w) = x==z && y==w
Multiple constraints can also occur in the definition of a class, as might be the case in definition of an ordered visible class.
class (Ord a,Visible a) => OrdVis a vSort :: OrdVis a => [a] -> String
The case where a class extends two or more classes, as above for OrdVis is called multiple inheritance.
See section 12.4 of the Thompson textbook for discussion of various classes in Haskell's standard prelude.
These notes are based on:
UP to CSCI 555 Lecture Notes root document?