310 likes | 429 Views
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
E N D
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 • 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 (fnparameter => body) • Now we can define doubleAll asfun 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; • 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 = 8val b : int = 3val 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 expressionend • let helps avoid redundant computations
Example of let fun circleArea (radius) = let val pi = 3.1416; 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, andelements > 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 front) @ (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 listrepeatedly computes (element op basis), starting with the basis and using the list elements starting from the left • foldl (op -) 10000 [1, 20, 300, 4000]; • val it : int = 13719 • (4000 - (300 - (20 - (1 - 10000)))); • val it : int = 13719
foldr • foldr op basis listrepeatedly computes (element op basis), starting with the basis and using the list elements starting from the right • foldr (op -) 10000 [1, 20, 300, 4000]; • val it : int = 6281 • (1 - (20 - (300 - (4000 - 10000)))); • val it : int = 6281
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]; • val it : bool = true • pairwise (fn (x, y) => x = y - 1) [3,4,5,7]; • val it : bool = false