Advanced features of Functional Programming (Haskell) Polymorphism and overloading January 10, 2017
Monomorphic and polymorphic types A (data) type specifies a set of values. Examples: Bool: the type of boolean values True and False Int: the type of integer value, e.g., 2916, [Int]: the type of lists of integers, e.g., [7,3,2] (Int,Bool): the type of pairs made of an integer and a boolean, e.g., (78,True) [Int]->Int: the type of functions that take a list of Ints, and returns an Int value. For example, length is a function of this type. Notation: f ::T indicates that f has type T Typing rule: If f ::A->B and v::a then (f v) has type B All previous types are monomorphic. A polymorphic type uses type variables to indicate that some component types are arbitrary. E.g.: [a]->int is the set of functions that accept a list of any type a, and return an Int value. Such functions are called polymorphic. Example: length::[a]->int
Why are polymorphic types useful? Write one implementation that works for all instances of the type variables. Example In languages without polymorphic types, we must write different definitions for all instances, e.g., for length Int ::[Int]->Int, for length Bool ::[Bool]->Int, etc., although the definitions looks the same. length::[a]->int length [] = 0 length (x:xs) = 1 + length xs Several programming languages allow us to define our own data types. In Haskell, we can define our own polymorphic types, also known as algebraic types. (see next slide)
Algebraic types Example 1: A type of geometric shapes The data declaration defines a new type together with a collection of data constructors. l h a Rectangle l h b RtTriangle a b data Shape = Rectangle Float Float RtTriangle Float Float area::shape->float area Rectangle l h = l * h area RtTriangle a b = a * b / 2
Algebraic types Example 2: A type for binary trees data Tree a = Nil Node a (Tree a) (Tree a) Nil is the empty tree Node v t 1 t 2 is the binary tree with value v of type a stored in the root node, left child t 1 of type Tree a, and right child t 2 of type Tree a. The type Tree a is polymorphic (it depends on type variable a) and recursive depth::tree a->int depth Nil = 0 depth Node _ t1 t2 = 1+(if d1>d2 then d1 else d2) where d1 = depth t1 d2 = depth t2
Algebraic types The general form of data declarations data Typename a 1... a p = Con 1 t 11... t 1k1 Con 2 t 21... t 2k2... Con n t n1... t nkn where Typename is the newly defined type a 1,..., a p are the type variable on which Typename depends Con 1,...,Con n are its data constructors
Overloading Motivation There are many situations when we wish to use the same function name for operations with different implementations. E.g., in Java, + can be used for different purposes To add various kinds of numbers: 1 + 2 : computes 3 To concatenate strings: Java + Script computes JavaScript Remarks: 1 These two operations have different implementations 2 It is the job of the compiler (more precisely, the type checker) to detect which implementation of + to call. 3 There are many ways to implement overloading: In Haskell, overloaded functions are specified by type classes
Overloading Motivating example Consider defining the boolean operation elem x l which takes as inputs a value x of type a a list l of elements of type a and returns True if and only if x is an element of l. First attempt (almost ok) elem::a->[a]->bool elem x [] = False elem x (y:ys) = if x == y then True else (elem x ys) Q: What is wrong with this implementation? A: It does not work for types a whose values can not be compared for equality (==) Example: For a=int->int is undecidable to decide if two such functions are equal.
Overloading with type classes Motivating example: Second attempt elem:: Eq a => a->[a]->bool elem x [] = False elem x (y:ys) = if x == y then True else (elem x ys) The additional specification Eq a constrains a to be a type for which the operation ==::a->a->bool is defined. In Haskell, such constraints are defined with type classes (compare with interface declarations in OOP): class Eq a where (==) :: a -> a -> Bool Instances of a class are the types for which the class constraint holds. They are defined with instance declarations: instance Eq Bool where True == True = True False == False = True _ == _ = False instance Eq Integer where x == y = IntegerEq x y
Overloading with type classes More complex instance definitions instance Eq a => Eq (Tree a) where Nil == Nil = True Node x x1 x2 == Node y y1 y2 = x==y && x1==y1 && x2==y2 _ == _ = False In reality, Eq is predefined in Haskell as follows: class Eq a where (==), (/=) :: a->a->bool x /= y = not (x == y) x == y = not (x /= y) provides default implementations for each operator An instance of Eq a must provide the implementation for only one operator the definition of the other operator is given by the default implementation in the class declaration
Class inheritance Similar to subclassing in OOP: class Eq a => Ord a where (<), (<=), (>=), (>) :: a->a->bool max, min :: a->a->bool It inherits ==::a->a->bool from Eq a Example of instance declaration for class Ord: instance Ord a => Ord (Tree a) where Nil < Node _ = True Node x x1 x2 < Node y y1 y2 = x<y (x==y&&x1<x2) (x=y&&x1==x2&&y1<y2) t1 <= t2 = t1<t2 t1==t2...
Predefined classes Eq a, Ord a Show a: instances of this class can be converted to character strings with the function show :: (Show a) => a -> String Num a for numeric types (e.g., Int, Float, etc.), defined by class (Eq a,show a) => Num a where (+),(-),(*) :: a->a->a negate :: a->a abs,signum :: a->a frominteger :: Integer->a...
Predefined type classes and types The class hierarchy of numeric types
Derived instances Recall the previous definitions of class instances instance Eq a => Eq (Tree a) where... instance Ord a => Ord (Tree a) where... These instance definitions are quite boring: their definition is boilerplate for algebraic datatypes: Two values are equal if all their components are equal To check if t 1 is lest than or equal to another value t 2, we compare lexicographically (from left to right) the component values of t 1 and t 2 These implicit instance definitions are derived automatically by Haskell for data declarations with deriving clauses: data Tree a = Nil Node a (Tree a) (Tree a) deriving (Eq,Ord)
References Paul Hudak: The Haskell School of Expression: Learning Functional Programming Through Multimedia. Cambrodge University Press 2000. Simon Thompson: Haskell. The Craft of Functional Programming. Second Edition. Addison Wesley Logman Limited 1999.