Lecture 7: Higher-Order Functions: Jean-No El Monette
Lecture 7: Higher-Order Functions: Jean-No El Monette
Jean-No el Monette
1 / 50
Today
Introductory Examples Higher-order functions on lists More Examples Example: Polymorphic Ordered Binary Tree Higher-Order Functions on Trees Lazy Evaluation
2 / 50
Introductory Examples
Table of Contents
Introductory Examples Higher-order functions on lists More Examples Example: Polymorphic Ordered Binary Tree Higher-Order Functions on Trees Lazy Evaluation
3 / 50
Introductory Examples
Reminder
An anonymous function: a function without a name. fun double x = 2x val double = fn x => 2x are equivalent. So are double 42 ( fn x => 2x) 42
4 / 50
Introductory Examples
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 ); How to sort in general (not only integer)?
5 / 50
Introductory Examples
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 dierent functions? Exercise (at home): redene the merge sort to be polymorphic.
6 / 50
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, ve , six ,seven ]; val it = [ ve ,four,one,seven, six ,three,two]: string list > 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, ve ),(6, six ),(7, sev val it = [(5, ve ),(4, four ),(1, one ),(7, seven ),(6, six ),(3, thre (2,two)]: ( int string ) list
Jean-No el Monette (UU) Lecture 7: Higher-Order Functions 7 / 50
Introductory Examples
Higher-order functions
A higher-order function: a function that either takes functions as arguments or returns functions as result. Remarks This is hard to do in an imperative programming language This is a very powerful mechanism
8 / 50
Introductory Examples
fun sum [] = 0 | sum (x:: l ) = x + sum l; There are only two places in the function denition that are specic for computing a sum: +, combining data 0, result for empty list
9 / 50
Introductory Examples
A generalization
Lets dene a function reduce so that reduce + 0 is equal to sum fun reduce f z [] = z | reduce f z (x :: l ) = f(x,reduce f z l ); fun reduce f z = (fn [] => z | (x :: l ) => f(x,reduce f z l )); Note the similarity between reduce and sum.
10 / 50
Introductory Examples
Generalization (cont.)
Now, sum can be dened as fun sum xs = reduce (op +) 0 xs; val sum = reduce (op +) 0;
11 / 50
Introductory Examples
[[1,2],[34],[5,6,7,89]];
12 / 50
Introductory Examples
Function composition
A function that takes two functions and returns a function! fun o ( f ,g) x = f(g(x )); inx o; o is predened. No need to dene it. fun add1 x = x + 1; (add1 o add1) 42;
Exercise: Find values id1 and id2 so that (id1 o f ) x = f x ( for all f , x ) ( f o id2) x = f x ( for all f , x )
Jean-No el Monette (UU) Lecture 7: Higher-Order Functions 13 / 50
Introductory Examples
Pair
Another way of building functions: > 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
14 / 50
Table of Contents
Introductory Examples Higher-order functions on lists More Examples Example: Polymorphic Ordered Binary Tree Higher-Order Functions on Trees Lazy Evaluation
15 / 50
Several functions are very useful to dene operations on lists. > map; val it = fn: > foldr ; val it = fn: > foldl ; val it = fn: > List . lter val it = fn: ( a > b) > a list > b list ( a b > b) > b > a list > b ( a b > b) > b > a list > b ; ( a > bool) > a list > a list
Those functions are predened in ML, but we will see the details.
16 / 50
17 / 50
> 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
18 / 50
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?
19 / 50
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?
20 / 50
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?
21 / 50
Examples (cont.)
Compare foldr ( op ::) []; foldl ( op ::) []; Are they equivalent? Which one would you use? Why?
22 / 50
Examples (cont.)
What does this function compute? fun rstEven (x,NONE) = if x mod 2 = 0 then SOME x else NONE | rstEven ( , SOME y) = SOME y; Compare foldl rstEven NONE; foldr rstEven NONE; Are they equivalent? Which one would you use for what?
23 / 50
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) [];
24 / 50
Filter
( PRE: (none) POST: the list of elements of the list for which p is true ) fun lter p [] = [] | lter p (x :: xs) = if p x then x :: lter p xs else lter p xs ; Examples: lter ( fn (x) => x<6); lter ( fn (x) => x<6) [6,3,0,1,8,5,9,3];
25 / 50
More Examples
Table of Contents
Introductory Examples Higher-order functions on lists More Examples Example: Polymorphic Ordered Binary Tree Higher-Order Functions on Trees Lazy Evaluation
26 / 50
More Examples
Fold over integers (really natural numbers) gives us a general way to recurse over natural numbers fun foldint | foldint foldint ( op foldint ( op foldint ( fn f b0 =b f b n = foldint f ( f (b,n)) (n1); + ) 0 5; ) 1 5; ((a,b), ) => (a+b, a)) (1,1) 10;
27 / 50
More Examples
fun td a = foldint ( fn (b,n) => b andalso (n <= 1 orelse a mod n <> 0)) true (a1); fun primes n = foldint ( fn ( l , n) => if td n then n:: l else l ) [] n;
28 / 50
More Examples
We can dene 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 (n1) f (f x ); ntimes 42 twice ;
29 / 50
Table of Contents
Introductory Examples Higher-order functions on lists More Examples Example: Polymorphic Ordered Binary Tree Higher-Order Functions on Trees Lazy Evaluation
30 / 50
The order can be dened for any type. We will consider the values to be pairs:
The rst element is the key. The second is the value that we want to associate with the key.
31 / 50
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 possible to build a bsTree that is not ordered.
32 / 50
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 ;
33 / 50
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;
34 / 50
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 dicult.)
35 / 50
Functional Datatype
Consider again the previous denition of bsTree: The functional argument compare must be passed at each call The compare order is not global to the binary search tree Lets 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;
36 / 50
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;
37 / 50
Table of Contents
Introductory Examples Higher-order functions on lists More Examples Example: Polymorphic Ordered Binary Tree Higher-Order Functions on Trees Lazy Evaluation
38 / 50
Like for lists, it is possible to dene generic functions on trees. mapbt applies some function on all elements of the tree. Dierent folding functions can be dened. We cover here binary trees, but the same might be done for other types of trees.
39 / 50
40 / 50
Exercises
Use reducebt to compute if a btree is ordered (according to some given order). Use reducebt to transform a btree into a bsTree. Dene a function map on trees (as in assignment 2).
41 / 50
Lazy Evaluation
Table of Contents
Introductory Examples Higher-order functions on lists More Examples Example: Polymorphic Ordered Binary Tree Higher-Order Functions on Trees Lazy Evaluation
42 / 50
Lazy Evaluation
Lazy evaluation
Idea: Do not evaluate the arguments of a function call before we know that they will be needed. Similarly, when computing a datastructure or a list, dont compute the components before we know that they are needed. In a functional programming language with lazy evaluation, it is meaningful to write a function that creates (for example) a list of all integers or a list of all prime numbers. A second function might call the rst and print the rst 100. fun ints n = n :: ints (n+1); does not terminate in SML.
43 / 50
Lazy Evaluation
In SML (or any other programming language with eager evaluation and higher-order functions) we can simulate a value computed by lazy evaluation by a function. Indeed, in the following code, the addition is only perfomed in the second line: val lazyFive = (fn () => 3 + 2); val ve = lazyFive ();
44 / 50
Lazy Evaluation
fun mult x y = if x = 0 then 0 else x y; fun lazyMult x y = if x() = 0 then 0 else x() y (); What are the types of the functions?
45 / 50
Lazy Evaluation
mult (11) (3 div 0); ( fn x => fn y => if x=0 then 0 else xy) (11) (3 div 0); ( fn x => fn y => if x=0 then 0 else xy) 0 (3 div 0); ( fn y => if 0=0 then 0 else 0y) (3 div 0); ( fn y => if 0=0 then 0 else 0y) ( raise Div); raise Div; lazyMult ( fn () => 11) (fn() => 3 div 0); ( fn x => fn y => if x()=0 then 0 else x()y()) ( fn () => 11) (fn() => 3 div 0); ( fn y => if (fn () => 11)()=0 then 0 else (fn() => 11)()y()) (fn() => 3 div 0); if ( fn () => 11)()=0 then 0 else (fn() => 11)()(fn() => 3 div 0)(); if 0=0 then 0 else (fn () => 11)()(fn() => 3 div 0)(); if true then 0 else ( fn () => 11)()(fn() => 3 div 0)(); 0;
46 / 50
Lazy Evaluation
Innite sequences can either be represented: as a function f i = xi where xi is the ith element or as a pair datatype composed of
the rst value, and a function that gives us the next pair.
Question: how would you dene basic list operations such as hd, tl, map for these representations?
47 / 50
Lazy Evaluation
First approach
fun ints i = i; fun zeroes i = 0; fun fToList n f = let fun aux m = if m>=n then [] else f m :: aux (m+1); in aux 0 end; fToList 7 ints ;
48 / 50
Lazy Evaluation
Second approach
datatype (a) seq = Pair of a ( unit > a seq); fun ints n = Pair(n,( fn () => ints (n+1))); fun sToList 0 f = [] | sToList n f = let val Pair(v,next) = f() in v :: sToList (n1) next end; sToList 7 ( fn () => ints 0);
49 / 50
Lazy Evaluation
End Note
A good implementation of a lazy languages must make sure that the value of a subexpression is never computed more than once. The solutions presented here do not have that property. val val val val lazyFive = (fn () => 3 + 2); six = lazyFive() + 1; seven = lazyFive() + 2; height = lazyFive() + 3;
50 / 50