Overview Declarative Languages D7012E Lecture 4: The Haskell type system Fredrik Bengtsson / Johan Nordlander Overloading & polymorphism Type classes instances of type classes derived type classes Type checking type inference monomorphic type checking polymorphic type checking type checking classes Overloading Use the same name for different functions distinguish by type So, the same name used with different types can refer to different functions Not the same as polymorhpism one function definition works for several types Not the same as Java/C++ subtyping a function of a "small" type is also a function of any "larger" type Overloading Polymorphism Subtyping Int->[Int] f = expr1 Int->[Int] Char->[Char] Bool->[Bool] overloading Char->[Char] f = expr2 a->[a] f = expr polymorphism Food->Animal Food->Bird f = expr subtyping (not in Haskell)
Overloading Why overloading? why not use different names for different functions? Can be unconvenient consider the == operator we would have to have one for each type Overloaded functions can be polymorphic! using an overloaded name in a polymorphic function makes it overloaded as well An example Consider function elem :: a -> [a] -> Bool checks if a is a member of [a] but the type a has to have equality defined otherwise we cannot check anything! How to express this requirement? type classes requirements on type in terms of required functions The Eq type class class Eq a where (==) :: a -> a -> Bool Specifies the need for the == operator Members of a type class: instances Instances of Eq Int, Float, Bool, Char, Expressing requirement Example allequal :: Eq a => a -> a -> a -> Bool allequal m n p = (m==n) && (n==p) Eq a declares that a must be a member of Eq The => notation expresses requirements (left-hand side) on the variables of a type (right-hand side)
Type classes Declaring a type class class <name> <type variable> where <function signature> <function signature> <type variable> present in the signatures assigned a type when declaring instances of the class Instances of class Eq instance Eq Bool where True == True = True False == False = True _ == _ = False defines the == operator for the type Bool of class Eq instances defines the functions that was required by the class Instance of a type class instance <class instance> where <definition of required functions> <definition of required functions> functions required by the class defined in the class instance the instance makes a type a member of the class Default definitions in type classes The real definition of Eq: class Eq a where (==), (/=) :: a -> a -> Bool x /= y = not (x==y) x == y = not (x/=y) A class can include default definitions /= and == in this case works for any instance we automatically get /= if we define == (or vice versa) an instance can override the defaults
Subclassing Consider the Ord type class (types having a total order defined) class Eq a => Ord a where (<), (<=), (>), (>=) :: a -> a -> Bool max, min :: a -> a -> a compare :: a -> a -> Ordering x <= y = (x < y x == y) x > y = y < x We define class Ord A subclass of Eq inherits everything from Eq and adds some more requirements defines defaults for <= and > Visible-example A class Visible: class Visible a where tostring :: a -> String size :: a -> Int Instance for lists: instance Visible a => Visible [a] where tostring = concat. map tostring size = foldr (+) 1. map size Multiple constraints We can have multiple class constraints in a type vsort :: (Ord a,visible a) => [a] -> String a has should have total order be visible (possible to print on screen) Multiple constraints in an instance instance (Eq a,eq b) => Eq (a,b) where (x,y) == (z,w) = x==z && y==w specifies that a pair of Eq types also is in Eq Multiple inheritance for type classes class (Ord a,visible a) => OrdVis a the class OrdVis includes everything from both Ord and Visible we could add more constraints Haskell built-in classes Eq equality Ord total order (Ordering enumerates the the results of a comparison) Enum enumerable type Bounded type having max- and min-value Show type convertable to a string Read values convertable from a string Num numerical ops (+ - *) Integral integral numbers (Num with div mod) Fractional fractional numbers (Num with /) Floating floating point numbers (exp sqrt sin )
Haskell classes & instances A Haskell class is a collection of types the types that are instances of the class A Haskell class is not a type itself Each class is associated with a set of overloadable names Instances are created at compile time via an instance declaration Each instance has specific definitions of the overloaded names Java/C++ classes & instances A Java/C++ class is a collection of objects run-time data with a particular layout A Java/C++ class is the equivalent of a type Each class is associated with specific definitions for the class methods Instances (objects) are created at run-time via the new command Each instance has specific values for the class attributes Subclassing differences Subclassing differences In Haskell: An overloaded definition is selected on basis of the static types of all arguments and results same type means same definition In Java/C++: A method definition is selected on basis of the original class of the object argument same type same definition due to upcasts class type type different implementations data objects type = class
Type checking Monomorphic type checking Polymorphic type checking Type checking with classes Type inference Most often no need to declare types correct types are inferred by haskell Haskell infers the most general type no uneccesary restrictions are placed on a type example: does not infer Int if a is sufficient Complication when using polymorphic types unification of types have to realize what type satisfies all constraints Monomorphic type checking Consider example: ord 'c' we know 'c' :: Char we know ord :: Char -> Int Then whole expression is Int, since argument type matches what the function expects function result is Int Trivial unification Types required to match are already identical Every type unifies with itself Type checking a monomorphic function with a signature Function: f :: t 1 -> t 2 -> -> t k -> t f p 1 p 2 p k g 1 = e 1 g 2 = e 2 g n = e n Each guard g i should be Bool Each body e i must be of the same type t Each pattern p j must be of type t j
Polymorphic type checking Invents fresh type variables where no previous type info is known Each part of expression produces constraints on participating types Process of satisfying constraints: unification if not possible type error if possible a refined resulting type Polymorphic type checking Consider example: [1] ++ [2,3] we know (++) :: [a] -> [a] -> [a] we know [1] :: [Int] we know [2,3] :: [Int] Then the whole expression is [Int] since left argument [1] :: [Int] unifies with [a] by refining a to Int right argument [2,3] :: [Int] unifies with (refined) expected type [Int] operator result is (refined) type [Int] Non-trivial unification some non-identical types can be made identical by refining type variables A more elaborate example g :: (Int, [b]) -> Int f :: (a, Char) -> (a, [Char]) (.) :: (y->z) -> (x->y) -> (x->z) h = g. f Both f and g are functions types unifiable with type of (.) Unify (a,[char]) with y and y with (Int,[b]) several small steps resulting type y is now refined to (Int, [Char]) so a=int and b=char So: h :: (Int, Char) -> Int Polymorphic definitions and variables Consider Expression: expr = length ([]++[True]) + length ([]++[2,3,4]) What is type of constructor [] here? [Int] and [Bool] This is ok, because [] :: [a] and a polymorphic definition can be instantiated repeatedly
Polymorphic definitions and variables Now, replace [] with a parameter xs: funny xs = length (xs++[true]) + length (xs++[2,3,4]) Compare to previous: expr = length ([]++[True]) + length ([]++[2,3,4]) What is type of parameter xs here? [Int] and [Bool]??? No, this is not allowed! Again: Polymorphic definitions and variables funny xs = length (xs++[true]) + length (xs++[2,3,4]) For this to type-check, xs must be polymorphic [Int] and [Bool] But there's no way of expressing the corresponding function type in Haskell! The naive attempt funny :: [a] -> Int allows too many types of arguments: funny [1,2,3] funny [False,True] funny ['x', 'y'] Rule: function are not allowed to use parameters polymorphically Type checking classes The context: part of type specifying classes of type variables places restriction on how variables may be replaced a set of class membership requirements implied by function usage for example: using == gives rise to context (Eq a)since (==) :: (Eq a) => a -> a -> Bool carried over to resulting type as a context of the infered type Typechecking classes: example member :: Eq a => [a] -> a -> Bool e :: Ord b => [[b]] Consider member e Unify type expressions: member e :: [b] -> Bool because member :: [[b]] -> [b] -> Bool and e :: [[b]] Resulting context: (Eq [b], Ord b) Now check and simplify context!
Checking the context (Eq [b], Ord b) Requirements need to be on type variables not lists, like Eq [b] use instance declaration to reduce the context instance Eq a => Eq [a] where no instance found type error New context: (Eq b, Ord b) Try to remove redundant requirements use subclass definition class Eq a => Ord a where to remove Eq a Ord a is stronger requirement Resulting context: Ord a Resulting type Given member :: Eq a => [a] -> a -> Bool e :: Ord b => [[b]] Then member e :: Ord b => [b] -> Bool Algebraic types Next lecture