# Higher-order functions in ML

## Presentation on theme: "Higher-order functions in ML"— Presentation transcript:

Higher-order functions in ML

Higher-order functions
A first-order function is one whose parameters and result are all "data" A second-order function has one or more first-order functions as parameters or result In general, a higher-order function has one or more functions as parameters or result ML supports higher-order functions

Doubling, revisited doubleAll [1,2,3,4,5];
fun doubleAll [ ] = [ ] | doubleAll (h::t) = 2 * h :: (doubleAll t); val doubleAll : int list -> int list = fn doubleAll [1,2,3,4,5]; val it : int list = [2, 4, 6, 8, 10] This is the usual heavy use of recursion It's time to simplify things

map map applies a function to every element of a list and returns a list of the results map f [x, y, z] returns [f x, f y, f z] Notice that map takes a function as an argument Ignore for now the fact that map appears to take two arguments!

Doubling list elements with map
fun double x = 2 * x; fun doubleAll lst = map double lst; val doubleAll : int list -> int list = fn doubleAll [1,2,3,4,5]; val it : int list = [2, 4, 6, 8, 10] The definition of doubleAll is simpler, but... ...now we need to expose double to the world

Anonymous functions An anonymous function has the form (fn parameter => body) Now we can define doubleAll as fun doubleAll lst = map (fn x => x+x) lst; This final definition is simple and doesn't require exposing an auxiliary function

The mysterious map ML functions all take a single argument, but...
map double [1,2,3] works map (double, [1,2,3]) gives a type error Even stranger, (map double) [1,2,3] works! map double; val it : int list -> int list = fn map double looks like a function...how?

Currying In ML, functions are values, and there are operations on those values Currying absorbs a parameter into a function, creating a new function map takes one argument (a function), and returns one result (also a function)

Order of operations fun add (x, y) = x + y; But also consider:
val add : (int * int) -> int = fn But also consider: fun add x y = x + y; val add : int -> int -> int = fn add x y is grouped as (add x) y and int -> int -> int as int -> (int -> int)

Writing a curried function I
fun add x y = x + y; val add : int -> int -> int = fn That is, add : int -> (int -> int) Our new add takes an int argument and produces an (int -> int) result (add 5) 3; (* currying happens *) val it : int = 8

Writing a curried function II
MLWorks> val add5 = add 5; val add5 : int -> int = fn Notice the use of val; we are manipulating values MLWorks> add5 3; (* use our new fn *) val it : int = 8

Function composition The function composition operator is the infix lowercase letter o (f o g) x gives the same result as f(g(x)) But composition gives you a way to bundle the functions for later use; f(g(x)) requires an argument x right now val h = f o g; is perfectly legal ML

Defining higher-order functions I
fun apply1(f, x) = f(x); val apply1 : (('a -> 'b) * 'a) -> 'b = fn apply1 (tl, [1,2,3]); val it : int list = [2, 3] But: apply1 tl [1,2,3]; error: Function applied to argument of wrong type

Defining higher-order functions II
fun apply2 f x = f(x); val apply2 : ('a -> 'b) -> 'a -> 'b = fn apply2 tl [1,2,3]; val it : int list = [2, 3] apply2 (tl, [1,2,3]); error: Function applied to argument of wrong type Advantage: this form can be curried

A useful function: span
span finds elements at the front of a list that satisfy a given predicate Example: span even [2,4,6,7,8,9,10] gives [2, 4, 6] span isn't a built-in; we have to write it

Implementing span fun span f L = if f(hd L) then (hd L) :: span f (tl L) else []; span even [2,4,6,7,8,9,10]; val it : int list = [2, 4, 6]

Extending span: span2 span returns the elements at the front of a list that satisfy a predicate Suppose we extend it to also return the remaining elements We can do it with the tools we have, but more tools would be convenient

Generalized assignment
val (a, b, c) = (8, 3, 6); val a : int = 8 val b : int = 3 val c : int = 6 val x::xs = [1,2,3,4]; val x : int = 1 val xs : int list = [2, 3, 4] Generalized assignment is especially useful when a function returns a tuple

Defining local values with let
let declaration ; declaration ; in expression end let helps avoid redundant computations

Example of let fun circleArea (radius) = let val pi = ; fun square x = x * x in pi * square (radius) end;

Implementing span2 fun span2 f list = if f(hd list) then let val (first, second) = span2 f (tl list) in ((hd list :: first), second) end else ([], list); val span2 : ('a -> bool) -> 'a list -> ('a list * 'a list) = fn span2 even [2,4,6,7,8,9,10]; val it : (int list * int list) = ([2, 4, 6], [7, 8, 9, 10])

Another built-in function: partition
Partition breaks a list into two lists: those elements that satisfy the predicate, and those that don't Example: partition even [2,4,6,7,8,9,10]; val it : (int list * int list) = ([2, 4, 6, 8, 10], [7, 9])

Quicksort Choose the first element as a pivot:
For [3,1,4,1,5,9,2,6,5] choose 3 as the pivot Break the list into elements <= pivot, and elements > pivot: [1, 1, 2] and [4, 5, 9, 6, 5] Quicksort the sublists: [1, 1, 2] and [4, 5, 5, 6, 9] Append the sublists with the pivot in the middle: [1, 1, 2, 3, 4, 5, 5, 6, 9]

Quicksort in ML fun quicksort [ ] = [ ] | quicksort (x :: xs) = let val (front, back) = partition (fn n => n <= x) xs in (quicksort (x :: (quicksort back)) end; val quicksort : int list -> int list = fn quicksort [3,1,4,1,5,9,2,6,5,3,6]; val it : int list = [1, 1, 2, 3, 3, 4, 5, 5, 6, 6, ..]

foldl foldl op basis list repeatedly computes (element op basis), starting with the basis and using the list elements starting from the left foldl (op -) [1, 20, 300, 4000]; val it : int = 13719 ( (300 - (20 - ( ))));

foldr foldr op basis list repeatedly computes (element op basis), starting with the basis and using the list elements starting from the right foldr (op -) [1, 20, 300, 4000]; val it : int = 6281 (1 - (20 - (300 - ( ))));

Testing if a list is sorted
The following code tests if a list is sorted: fun sorted [] = true | sorted [_] = true | sorted (x::y::rest) = x <= y andalso sorted (y::rest); This applies a (boolean) test to each adjacent pair of elements and "ANDs" the results Can we generalize this function?

Generalizing the sorted predicate
fun sorted [] = true | sorted [_] = true | sorted (x::y::rest) = x <= y andalso sorted (y::rest); The underlined part is the only part specific to this particular function We can replace it with a predicate passed in as a parameter

pairwise fun pairwise f [] = true | pairwise f [_] = true | pairwise f (x::y::rest) = f(x,y) andalso pairwise f (y::rest); Here are the changes we have made: Changed the name from sorted to pairwise Added the parameter f changed x <= y to f(x, y)

Using pairwise pairwise (op <=) [1, 3, 5, 5, 9];
val it : bool = true pairwise (op <=) [1, 3, 5, 9, 5]; val it : bool = false pairwise (fn (x, y) => x = y - 1) [3,4,5,6,7]; pairwise (fn (x, y) => x = y - 1) [3,4,5,7];

The End

Similar presentations