H. Conrad Cunningham
7 Novemver 2017
Copyright (C) 2017, H. Conrad Cunningham
Acknowledgements: These slides accompany the first 10 sections of Chapter 9 “Overloading and Type Classes” of the in-work textbook “Introduction to Functional Programming Using Haskell”. This chapter is an incomplete draft.
Advisory: The HTML version of this document may require use of a browser that supports the display of MathML. A good choice as of November 2017 is a recent version of Firefox from Mozilla.
Code: The Haskell code for this chapter is file TypeClassMod.hs
.
Motivate overloading and type classes
Introduce type class laws
Extend concepts to inheritance and multiple constraints
Compare Haskell to other languages
Parametric polymorphism -- same name, same function definition for all types of arguments/results -- "polymorphism""
length: [a] -> Int
Overloading -- same name, same functionality, but different function definitions depending upon type
(+)
function takes two numbers of same type
elemBool :: Bool -> [Bool] -> Bool
elemBool x [] = False
elemBool x (y:ys) = eqBool x y || elemBool x ys
How can define eqBool
?
eqBool :: Bool -> Bool -> Bool
eqBool True False = False
eqBool False True = False
eqBool _ _ = True
Not general!
eqBool
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 elemGen
too general! any a -> a -> Bool
Equality meaningless for some types (functions?)
Overload ==
operator for set of types
Restrict polymorphism in elem
to types with (==)
defined
Introduce type class for equality, then constrain polymorphism to that context (used before)
elem :: Eq a => a -> [a] -> Bool
Define class Eq
as set of types with (==)
defined
class Eq a where
(==) :: a -> a -> Bool
Make type Bool
instance of class (also others like Int
, Char
)
instance Eq Bool where
True == True = True
False == False = True
_ == _ = False
Make list in Eq
if element is also
instance Eq a => Eq [a] where
[] == [] = True
(x:xs) == (y:ys) = x == y && xs == ys
_ == _ = False
Left side of euation defined by right side
Right side must be previously defined operation (e.g. x == y
) or terminating recursion (e.g. xs == ys
)
class Eq a where -- from Prelude library
(==), (/=) :: a -> a -> Bool
-- Minimal complete definition: (==) or (/=)
x /= y = not (x == y)
x == y = not (x /= y)
Equality is equivalence relation in math. For all x
, y
, and z
in type's set:
x == x
is True
.x == y
if and only if y == x
.x == y
and y == z
, then x == z
.x /= y
equivalent to not (x == y)
Type class laws -- must hold for all Eq
instances
Reality intervenes! x == x
for infinite? floating?
class Visible a where
toString :: a -> String
size :: a -> Int
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
Visible
No constraints on the conversion to strings
size
must return Int
, finite and bounded
Ord
is subclass of Eq
class Eq a => Ord a where -- from Prelude
(<), (<=), (>), (>=) :: a -> a -> Bool
max, min :: a -> a -> a
-- Minimal complete definition: (<) or (>)
-- Must break circular definition
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
Ord
Ord
instance implements total order on type's set. For operation <=
and all x
, y
, and z
in the type's set
Reflexivity: x <= x
is True
Antisymmetry: x <= y
and y <= x
, then x == y
Transitivity: if x <= y
and y <= z
, then x <= z
Trichotomy (comparability, totality): x <= y
or y <= x
==
(and /=
) satisfy Eq
type class laws
<
, >
, >=
, max
, and min
satisfy definition in declaration
isort
isort' :: Ord a => [a] -> [a]
isort' [] = []
isort' (x:xs) = insert' x (isort' xs)
insert' :: Ord a => a -> [a] -> [a]
insert' x [] = [x]
insert' x (y:ys)
| x <= y = x:y:ys -- overloaded <=
| otherwise = y : insert' x ys
vSort :: (Ord a,Visible a) => [a] -> String
vSort = toString . isort'
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 ]
instance
instance (Eq a, Eq b) => Eq (a,b) where
(x,y) == (z,w) = x == z && y == w
==
overloaded from different instancesclass
class (Ord a,Visible a) => OrdVis a
vSort :: OrdVis a => [a] -> String
OrdVis
has multiple inheritance -- reuse methods of both
Must satisfy type class laws for Eq
and Visible
Haskell class is collection of types; Java class similar to type
Haskell class similar to Java abstract class except Haskell has no data, Java only single inheritance
Haskell class similar to Java interface except Haskell has default method definitions (not in Java before Java 8)
Haskell instance is type, not object -- like concrete Java class that extends abstract classes or implements interface
Haskell separates type definition from method definition; Java mixes type and method definition
Haskell class methods correspond to Java instance methods
Each instance provides own definition for methods; class defaults correspond to default definitions in parent class
Haskell has no receiver object or mutable fields
Haskell class methods bound statically at compile time; Java bound dynamically at runtime
Java attaches identifying information to runtime object; Haskell only attaches logically
Haskell does not support C++ overloading where functions have different types and same name
Haskell objects cannot be implicitly coerced, types have no default parent
Java has cosmic base class Object
for all reference types
Haskell class separates type from access control, uses modules for access control
Java mixes
First appeared in Haskell, but adopted in newer languages
Rust supports traits, limited form of type classes
Scala has implicit classes and parameters, which enable type enrichment idiom similar to type classes
PureScript supports Haskell-like type classes
Idris supports interfaces, generalization of Haskell type classes
JavaScript has libraries such as Ramda
Overloading function giving same name, "same functionality" but different definitions for different types
Type classes and instances
Type class laws to define "same functionality"
Multiple constraints, inheritance
The Haskell code for this chapter is file TypeClassMod.hs
.