Higher-Order Functions Tjark Weber Functional Programming 1 Based on notes by Pierre Flener, Jean-Noël Monette, Sven-Olof Nyström Tjark Weber (UU) Higher-Order Functions 1 / 1
Tail Recursion http://xkcd.com/1270/ Tjark Weber (UU) Higher-Order Functions 2 / 1
Exercise: A Simple Matrix Can you find a linear tail-recursive function, a linear function that is not tail-recursive, a quadratic but tail-recursive function, and a quadratic function that is not tail-recursive? Tail-recursive Not tail-recursive Linear?? Quadratic?? Tjark Weber (UU) Higher-Order Functions 3 / 1
Higher-Order Functions Tjark Weber (UU) Higher-Order Functions 4 / 1
Today Tjark Weber (UU) Higher-Order Functions 5 / 1
Definition, Introductory Examples Table of Contents Tjark Weber (UU) Higher-Order Functions 6 / 1
Definition, Introductory Examples Higher-Order Functions A higher-order function is a function that Remarks: takes functions as arguments or returns a function. This is hard to do in most imperative programming languages. This is a very powerful mechanism. Abstraction, code re-use Tjark Weber (UU) Higher-Order Functions 7 / 1
Definition, Introductory Examples An Example From Mathematics The derivative in calculus maps (differentiable) functions to functions. Tjark Weber (UU) Higher-Order Functions 8 / 1
Definition, Introductory Examples An Example From Last Lecture fun time f = l e t v a l t i m e r = Timer. startcputimer ( ) v a l = f ( ) ( do the a c t u a l work ) i n Timer. checkcputimes t i m e r end Here, f is a function (of type unit > a). Thus, time is a higher-order function. Tjark Weber (UU) Higher-Order Functions 9 / 1
Definition, Introductory Examples Another Example: Function Composition A function that takes two functions and returns a function! fun o (f,g) x = f (g x); infix o; o is predefined in SML. No need to define it yourself. Example: fun add1 x = x + 1; (add1 o add1) 42; Tjark Weber (UU) Higher-Order Functions 10 / 1
Definition, Introductory Examples Insertion Sort on Integers fun insert x [] = [x] insert x (y :: ys) = if x < y then x :: y :: ys else y :: ( insert x ys) fun isort [] = [] isort (x :: xs) = insert x ( isort xs) Which part of the code is specific to integers? Tjark Weber (UU) Higher-Order Functions 11 / 1
Definition, Introductory Examples Insertion Sort on Any Type fun insert order x [] = [x] insert order x (y :: ys) = if order (x,y) then x :: y :: ys else y :: ( insert order x ys) fun isort order [] = [] isort order (x :: xs) = insert order x ( isort order xs) What is the type of the different functions? Tjark Weber (UU) Higher-Order Functions 12 / 1
Definition, Introductory Examples Example (cont.) > isort (op <) [6,3,0,1,7,8,5,9,2,4]; val it = [0,1,2,3,4,5,6,7,8,9]: int list > isort (op >) [6,3,0,1,7,8,5,9,2,4]; val it = [9,8,7,6,5,4,3,2,1,0]: int list > isort String.< [ one, two, three, four, five, six ]; val it = [ five, four, one, six, three, two ]: string > isort (op <); val it = fn: int list > int list > isort String.< ; val it = fn: string list > string list > isort (fn ((,s1 ),(,s2)) => String.< (s1,s2)) [(1, one ),(2, two ),(3, three ),(4, four ),(5, five ),(6, six )]; val it = [(5, five ),(4, four ),(1, one ),(6, six ),(3, three ), (2, two )]: ( int string ) list list Tjark Weber (UU) Higher-Order Functions 13 / 1
Definition, Introductory Examples Another Example: pair > fun pair (f,g) x = (f x, g x); val pair = fn: ( a > b) ( a > c) > a > b c > pair (add1, add1 o add1); val it = fn: int > int int > it 42; val it = (43,44): int int > pair ( pair (add1, add1 o add1), add1); val it = fn: int > (int int) int > it 42; val it = ((43,44),43): ( int int ) int Tjark Weber (UU) Higher-Order Functions 14 / 1
Higher-Order Functions on Lists Table of Contents Tjark Weber (UU) Higher-Order Functions 15 / 1
Higher-Order Functions on Lists Reflection on the Definition of sum fun sum [] = 0 sum (x::xs) = x + sum xs There are only two places in the function definition that are specific for computing a sum: +, combining data 0, result for empty list Tjark Weber (UU) Higher-Order Functions 16 / 1
Higher-Order Functions on Lists A Generalization Let s define a function reduce so that reduce + 0 is equal to sum: fun reduce f z [] = z reduce f z (x :: xs) = f (x,reduce f z xs) fun reduce f z = (fn [] => z (x :: xs) => f (x,reduce f z xs)) Note the similarity between reduce and sum. Tjark Weber (UU) Higher-Order Functions 17 / 1
Higher-Order Functions on Lists Generalization (cont.) Now, sum can be defined as fun sum xs = reduce (op +) 0 xs val sum = reduce (op +) 0 Tjark Weber (UU) Higher-Order Functions 18 / 1
Higher-Order Functions on Lists Other Uses of reduce What will the following compute? reduce (op ) 1 [1,2,3,4]; reduce Int. max 0 [1,2,3,44,5,6]; reduce (fn(x,y) => x::y) [] [1,2,3,4]; reduce (fn(x,y) => x::y) [5,6,7] [1,2,3,4]; reduce (fn(x,y) => x::x::y) [] [1,2,3,4]; reduce (op @) [] [[1,2],[34],[5,6,7,89]]; Tjark Weber (UU) Higher-Order Functions 19 / 1
Higher-Order Functions on Lists Higher-Order Functions on Lists Several functions are very useful to define operations on lists. > map; val it = fn: ( a > b) > a list > b list > foldr ; val it = fn: ( a b > b) > b > a list > b > foldl ; val it = fn: ( a b > b) > b > a list > b > List. filter ; val it = fn: ( a > bool) > a list > a list Those functions are predefined in ML, but we will see the details. Tjark Weber (UU) Higher-Order Functions 20 / 1
Higher-Order Functions on Lists The map Function Apply the same operation on all the elements of a list. ( PRE: (none) POST: [f(a 1),f(a 2),..., f(a n )], if L = [a 1,a 2,..., a n] ) > fun map f [ ] = [ ] map f (x :: xs) = f x :: map f xs; val map = fn: ( a > b) > a list > b list > fun square x = x x; val square = fn: int > int > map square [1,2,3,4]; val it = [1,4,9,16]: int list > map (fn(x)=> if x < 3 then 0 else x) [1,2,3,4]; val it = [0,0,3,4]: int list Tjark Weber (UU) Higher-Order Functions 21 / 1
Higher-Order Functions on Lists The map Function (cont.) > map square; val it = fn: int list > int list > map (map square); val it = fn: int list list > int list list > map (map square) [[1,2,34],[5]]; val it = [[1,4,1156],[25]]: int list list > map String.<; val it = fn: ( string string ) list > bool list > map String.< [( a, ab ), ( hello, bye )]; val it = [true, false ]: bool list Tjark Weber (UU) Higher-Order Functions 22 / 1
Higher-Order Functions on Lists Foldr and foldl fun foldr f b [] = b foldr f b (x :: xs) = f(x, foldr f b xs ); fun foldl f b [] = b foldl f b (x :: xs) = foldl f (f (x, b)) xs; Do they remind you of any function you have seen before? Which one is tail-recursive? Tjark Weber (UU) Higher-Order Functions 23 / 1
Higher-Order Functions on Lists Examples Sum the elements of the list. foldr (op +) 0 [0,2,21,4,6]; foldl (op +) 0 [0,2,21,4,6]; Are they equivalent? Which one would you use? Why? Tjark Weber (UU) Higher-Order Functions 24 / 1
Higher-Order Functions on Lists Examples (cont.) Check that all elements of a list are even foldr (fn (x,y) => y andalso x mod 2 = 0) true [0,2,21,4,6]; foldl (fn (x,y) => y andalso x mod 2 = 0) true [0,2,21,4,6]; Are they equivalent? Which one would you use? Why? Tjark Weber (UU) Higher-Order Functions 25 / 1
Higher-Order Functions on Lists Examples (cont.) Compare foldr (op ::) []; foldl (op ::) []; Are they equivalent? Which one would you use? Why? Tjark Weber (UU) Higher-Order Functions 26 / 1
Higher-Order Functions on Lists Examples (cont.) What does this function compute? fun firsteven (x,none) = if x mod 2 = 0 then SOME x else NONE firsteven (, SOME y) = SOME y; Compare foldl foldr firsteven NONE; firsteven NONE; Are they equivalent? Which one would you use for what? Tjark Weber (UU) Higher-Order Functions 27 / 1
Higher-Order Functions on Lists More Examples Try foldl (fn(x,n) => n+1) 0 foldr (fn(x,n) => n+1) 0 foldr (fn(x,n) => x) 0 foldl (fn(x,n) => x) 0 fun mystery f = foldr (fn (x,n) => f x :: n) []; Tjark Weber (UU) Higher-Order Functions 28 / 1
Higher-Order Functions on Lists Filter ( PRE: (none) POST: the list of elements of the list for which p is true ) fun filter p [] = [] filter p (x :: xs) = if p x then x :: filter p xs else filter p xs; Examples: filter (fn x => x<6); filter (fn x => x<6) [6,3,0,1,8,5,9,3]; Tjark Weber (UU) Higher-Order Functions 29 / 1
More Examples Table of Contents Tjark Weber (UU) Higher-Order Functions 30 / 1
More Examples Folding Over Integers Fold over integers (really natural numbers) gives us a general way to recurse over natural numbers: fun foldint f b 0 = b foldint f b n = foldint f (f(b,n)) (n 1); foldint (op + ) 0 5; foldint (op ) 1 5; foldint (fn ((a,b), ) => (a+b, a)) (1,1) 10; Tjark Weber (UU) Higher-Order Functions 31 / 1
More Examples Folding Over Integers (cont.) fun td a = foldint (fn (b,n) => b andalso (n <= 1 orelse a mod n <> 0)) true (a 1); fun primes n = foldint (fn ( l, n) => if td n then n:: l else l ) [] n; Tjark Weber (UU) Higher-Order Functions 32 / 1
More Examples twice and ntimes We can define a function that tells us how to do something twice: fun twice f = f o f ; fun add1 x = x+1; twice add1 56; Or more generally, do something n times: fun ntimes 0 f x = x ntimes n f x = ntimes (n 1) f (f x); ntimes 42 twice ; Tjark Weber (UU) Higher-Order Functions 33 / 1
Example: Polymorphic Ordered Binary Tree Table of Contents Tjark Weber (UU) Higher-Order Functions 34 / 1
Example: Polymorphic Ordered Binary Tree Binary Search Trees A Binary search tree is a binary tree which is ordered, i.e. All values in the left subtree are smaller than the value of the root. All values in the right subtree are larger than the value of the root. The order can be defined for any type. We will consider the values to be pairs: The first element is the key. The second is the value that we want to associate with the key. i.e. we define a dictionary. Tjark Weber (UU) Higher-Order Functions 35 / 1
Example: Polymorphic Ordered Binary Tree Binary Search Trees datatype ( a, b) bstree = Void Bst of ( a, b) bstree ( a b) ( a, b) bstree; Note: We want the key to be of some equality type. The order is not part of the datatype. It is (unfortunately) possible to build a bstree that is not ordered. Tjark Weber (UU) Higher-Order Functions 36 / 1
Example: Polymorphic Ordered Binary Tree Retrieving an Element ( PRE: the tree is ordered. POST: if the tree contains the key, return the corresponding value embedded in SOME. NONE otherwise ) fun retrieve lessthan k Void = NONE retrieve lessthan key (Bst ( left,( k,v), right )) = if key = k then SOME v else if lessthan (key,k) then retrieve lessthan key left else retrieve lessthan key right ; Tjark Weber (UU) Higher-Order Functions 37 / 1
Example: Polymorphic Ordered Binary Tree Retrieving an Element (cont.) We may use the datatype order instead: fun retrieve compare k Void = NONE retrieve compare key (Bst ( left,( k,v), right )) = case compare (key,k) of EQUAL => SOME v LESS => retrieve compare key left GREATER => retrieve compare key right; Tjark Weber (UU) Higher-Order Functions 38 / 1
Example: Polymorphic Ordered Binary Tree Inserting an Element ( PRE: The tree is ordered. POST: If the tree contains the key, a tree with the value replaced. Otherwise, a tree with the (key, value) pair inserted such that the tree is ordered. ) fun insert compare (key,value) Void = Bst(Void,(key,value ), Void) insert compare (key,value) (Bst ( left,( k,v), right )) = case compare (key,k) of EQUAL => Bst (left,(k,value),right) LESS => Bst(insert compare (key,value) left, (k,v), right ) GREATER => Bst(left,(k,v),insert compare (key,value) right ); Exercise: Specify and realise the exists and delete functions. (Delete is a bit more difficult.) Tjark Weber (UU) Higher-Order Functions 39 / 1
Example: Polymorphic Ordered Binary Tree Functional Datatype Consider again the previous definition of bstree and its shortcomings: The functional argument compare must be passed at each call. The compare order is not global to the binary search tree. Let s introduce the order in a new datatype: datatype ( a, b) bstree = Void Bst of ( a, b) bstree ( a b) ( a, b) bstree; type ( a) ordering = ( a a) > order; datatype ( a, b) obstree = OrdBsTree of a ordering ( a, b) bstree; Tjark Weber (UU) Higher-Order Functions 40 / 1
Example: Polymorphic Ordered Binary Tree Inserting an Element fun emptytree compare = OrdBsTree (compare, Void); fun insert (key, value) OrdBsTree (compare, tree) = let fun insert Void = (key,value) insert (Bst ( left,( k,v), right )) = case compare (key,k) of EQUAL => Bst (left,(k,value),right) LESS => Bst(insert left, (k,v), right ) GREATER => Bst(left,(k,v),insert right ) in OrdBstTree (compare, insert tree ) end; Tjark Weber (UU) Higher-Order Functions 41 / 1
Higher-Order Functions on Trees Table of Contents Tjark Weber (UU) Higher-Order Functions 42 / 1
Higher-Order Functions on Trees Higher-Order Functions on Trees Like for lists, it is possible to define generic functions on trees. mapbt applies some function on all elements of the tree. Different folding functions can be defined. We cover here binary trees, but the same might be done for other types of trees. Tjark Weber (UU) Higher-Order Functions 43 / 1
Higher-Order Functions on Trees Mapping and Reducing a Binary Tree datatype ( a) btree = Vd Bt of a a btree a btree ; fun mapbt f Vd = Vd mapbt f (Bt(v, l, r)) = Bt(f v, mapbt f l, mapbt f r ); fun reducebt f z Vd = z reducebt f z (Bt(v, l, r)) = f (v,reducebt f z l,reducebt f z r ); mapbt abs; reducebt (fn (v, l, r) => 1 + l + r) 0; reducebt (fn (v, l, r) => 1 + Int.max(l,r)) 0; reducebt (fn (v, l, r) => l @ [v] @ r) []; reducebt (fn (v, l, r) => Bt(v,r,l)) Vd; Tjark Weber (UU) Higher-Order Functions 44 / 1
Higher-Order Functions on Trees Exercises Use reducebt to compute if a btree is ordered (according to some given order). Use reducebt to transform a btree into a bstree. Define a function map on finitely branching trees (cf. assignment 2). Tjark Weber (UU) Higher-Order Functions 45 / 1