Higher-order functions in ML (Ch. 9) Functionals More pattern matching Function values and anonymous functions Higher-order functions and currying Predefined higher-order functions 4/8/2015 IT 327 1 Pattern-Matching in the case expression fun f 0 = "zero" | f _ = "non-zero"; match fun f n = case n of 0 => "zero" | _ => "non-zero"; 4/8/2015 IT 327 2 Case Expressions <case-expr> ::= case <expression> of <match> <match> ::= <rule> <match> ::= <rule> | <match> <rule> - case = 3 = 2 = _ val it 4/8/2015 ::= <pattern> => <expression> 1+1 of => "three" | => "two" | => "hmm"; = "two" : string IT 327 case x of _::_::c::_ _::b::_ a::_ nil => => => => c | b | a | 0 3 Generalizes if if exp1 then exp2 else exp3 case exp1 of true => exp2 | false => exp3 The two expressions above are equivalent So if-then-else is really just a special case of case 4/8/2015 IT 327 4 Behind the Scenes if := an abbreviation of case This explains some odd SML/NJ error messages: -- if if 1=1 1=1 then then 11 else else 1.0; 1.0; Error: branches not [literal] agree Error: types types of of if rules don't do agree [literal] earlier rule(s): bool -> int then int -> real this branch: rule: bool else branch: real in rule: in expression: false => 1.0 if 1 = 1 then 1 else 1.0 New version of SML shows different message 4/8/2015 IT 327 5 Predefined Functions Predefined variables bound to functions: - ord; val it = fn : char -> int - ~; val it = fn : int -> int - val x = ~; val x = fn : int -> int - x 3; val it = ~3 : int 4/8/2015 IT 327 6 Quick Sort with < 7 5 8 14 6 1 16 2 11 10 9 13 3 15 12 4 7 5 8 4 6 1 3 2 11 10 9 13 16 15 12 14 11 10 9 12 16 15 13 14 < 2 3 1 4 6 8 5 7 < 2 1 3 4 < 1 2 < 6 5 < 3 4 8 7 9 < 5 6 < < 7 8 10 11 12 14 13 15 16 < 9 < < 10 11 12 13 14 15 16 < < < < < < < < < < < < < < < 4/8/2015 IT 327 7 Quick Sort with > 7 6 1 We can make a function parameter for the meaning of ordering 5 8 14 16 2 11 10 9 13 16 15 12 14 11 10 9 13 3 15 12 4 7 5 8 4 6 1 3 2 6 8 5 7 2 3 1 4 3 4 2 3 2 1 > 16 15 13 14 11 10 9 12 > > 16 15 13 14 11 12 10 > > 9 8 > 16 15 14 13 12 11 10 > > 9 7 5 6 > 8 7 > 6 5 > 4 3 > > > > > > > > > > > > > > > 4/8/2015 IT 327 8 fun split nil = (nil,nil) | split [a] = ([a],nil) | split [a,b] = if (a <= b) then ([a],[b]) else ([b],[a]) | split (a::b::c) = let val (x,y) = split (a::c) in if (a < b) then (x, b::y) else (b::x, y) end; fun quicksort nil = nil | quicksort [a] = [a] | quicksort a = let val (x,y) = split a in quicksort(x)@quicksort(y) end; 4/8/2015 IT 327 Quick Sort in ML This is more conceptual to the understanding of the problem 9 fun split (nil, _) = (nil,nil) | split ([a], _) = ([a],nil) | split ([a,b], f) = if f(a,b) then ([a],[b]) else ([b],[a]) | split (a::b::c, f) = let val (x,y) = split (a::c, f) in if f(a,b) then (x, b::y) else (b::x,y) end; Quick Sort with function parameter fun quicksort (nil,_) = nil | quicksort ([a],_) = [a] | quicksort (a,f) = let val (x,y) = split (a,f) in quicksort(x,f)@quicksort(y,f) end; 4/8/2015 IT 327 10 Anonymous Functions functions without a name -calculus 4/8/2015 IT 327 11 Function Values Functions in ML do not have names but can be given. The fun syntax does two separate things: 1. 2. Creates a new function value Binds that function value to a name - fun f x = x + 2; val f = fn : int -> int - f 1; val it = 3 : int 4/8/2015 IT 327 12 Anonymous Functions in ML Named function: - fun f x = x + 2; val f = fn : int -> int - f 1; val it = 3 : int Anonymous function: A match - fn x => x + 2; val it = fn : int -> int - (fn x => x + 2) 1; val it = 3 : int 4/8/2015 IT 327 13 The fn Syntax <fun-expr> ::= fn <match> We use fn to give an expression whose value is an (anonymous) function The followings are the same effect: 4/8/2015 – fun f x = x + 2 – val f = fn x => x + 2 IT 327 14 - fun less (a,b) = a < b; val intBefore = fn : int * int -> bool - quicksort ([1,4,3,2,5], less); val it = [1,2,3,4,5] : int list Using Anonymous Functions: - quicksort ([1,4,3,2,5], fn (a,b) => a<b); val it = [1,2,3,4,5] : int list - quicksort ([1,4,3,2,5], fn (a,b) => a>b); val it = [5,4,3,2,1] : int list Q: Can we do the same in Java or C++? 4/8/2015 IT 327 15 - *; Error: expression or pattern begins with infix identifier "*" The op keyword Binary operators are special functions op gives the underlying function - op *; val it = fn : int * int -> int - op <; val it = fn : int * int -> bool - quicksort ([1,4,3,2,5], op <); val it = [1,2,3,4,5] : int list 4/8/2015 IT 327 16 Higher-order Functions Every function has an order: – – A function that does not take any functions as parameters, and does not return a function value, has order 1 A function that takes a function as a parameter or returns a function value has order n+1, where n is the order of its highest-order parameter or returned value The quicksort we just saw is a second-order function A little digression with more precise notions on higher-ordered Functionals (CiE 2006) 4/8/2015 IT 327 17 Examples: -> is right-associative st order 1 int * int -> bool 2nd order int list * (int * int -> bool) -> int list int -> int -> int 2nd order 2nd order (int -> int) * (int -> int) -> (int -> int) int -> bool -> real -> string 3rd order nd ('a -> 'b) * ('c -> 'a) -> 'c -> 'b 2 order int -> int -> int -> int -> int -> int 4/8/2015 IT 327 5th order 18 Haskell B. Curry (1900-1982) Currying Curry-Howard logic a 2-tuple looks like two parameters: f (2,3); fun f (a,b) = a + b; Another way: fun g a = fn b => a+b; g 2 3; (fn b => 2+b) 3 The general name for this is currying 4/8/2015 IT 327 5 19 Curried Addition - fun f (a,b) = a+b; val f = fn : int * int -> int - fun g a = fn b => a+b; val g = fn : int -> int -> int - f(2,3); val it = 5 : int - g 2 3; val it = 5 : int -> is right-associative function application is left-associative So g 2 3 means ((g 2) 3) 4/8/2015 IT 327 20 Advantages No tuples: we get to write g 2 3 instead of f(2,3) The real advantage: we can specialize functions for particular initial parameters - fun g a = fn b => a+b; -val add2 = g 2; val add2 = fn : int -> int - add2 val it - add2 val it 4/8/2015 3; = 5 : int 10; = 12 : int S-m-n Theorem IT 327 21 Example: Let quicksort be written in the Currying form, in which the comparison function is a first, curried parameter. -quicksort (op <) [1,4,3,2,5]; val it = [1,2,3,4,5] : int list -val sortBackward = quicksort (op >); val sortBackward = fn : int list -> int list - sortBackward [1,4,3,2,5]; val it = [5,4,3,2,1] : int list -val sortForward = quicksort (op <); val sortForward = fn : int list -> int list - sortForward [1,4,3,2,5]; val it = [1,2,3,4,5] : int list 4/8/2015 IT 327 22 Multiple Curried Parameters -fun f (a,b,c) = a+b+c; val f = fn : int * int * int -> int - fun g a = fn b => fn c => a+b+c; val g = fn : int -> int -> int -> int -f (1,2,3); val it = 6 : int - g 1 2 3; val it = 6 : int 4/8/2015 IT 327 23 Notation For Currying Explicit intermediate anonymous functions fun g a = fn b => fn c => a+b+c; 4/8/2015 There is a simpler notation much easier to read and write IT 327 24 Easier Notation for Currying fun f a = fn b => a+b; same as: fun f a b = a+b; fun g a = fn b => fn c => a+b+c; Same as: fun g a b c = a+b+c; -fun f a b c d = a+b+c+d; val f = fn : int -> int -> int -> int -> int 4/8/2015 IT 327 25 Predefined Higher-Order Functions 4/8/2015 – map (map a function to a list) – foldr (fold an operation from the right) – foldl (fold an operation from the left) IT 327 26 The map Function Apply a function to every element of a list, and collect a list of results - map ~ [1,2,3,4]; val it = [~1,~2,~3,~4] : int list - map (fn x => x+1) [1,2,3,4]; val it = [2,3,4,5] : int list - map (fn x => x mod 2 = 0) [1,2,3,4]; val it = [false,true,false,true] : bool list - map (op +) [(1,2),(3,4),(5,6)]; val it = [3,7,11] : int list 4/8/2015 IT 327 27 The map Function Is Curried - map; val it = fn : ('a -> 'b) -> 'a list -> 'b list - map (fn x => x+1) [1,2,3,4]; val it = [2,3,4,5] : int list -val f = map (op +); val f = fn : (int * int) list -> int list - f [(1,2),(3,4)]; val it = [3,7] : int list 4/8/2015 IT 327 28 Define your own map fun mymap _ nil = nil | mymap f (a::t) = f a:: mymap f t; 4/8/2015 IT 327 29 The foldr Function Perform the binary operation on all the elements of a list from the right (tail) foldr f c x where x = [x1, …, xn] and computes: f x1 , f x2 , f xn 1 , f xn , c foldr (op +) 0 [1,2,3,4] evaluates as 1+(2+(3+(4+0)))=10 4/8/2015 IT 327 30 Examples -foldr (op +) 0 [1,2,3,4]; val it = 10 : int -foldr (op * ) 1 [1,2,3,4]; val it = 24 : int -foldr (op ^) "" ["abc","def","ghi"]; val it = "abcdefghi" : string - foldr (op ::) [5] [1,2,3,4]; val it = [1,2,3,4,5] : int list 4::[5] 4/8/2015 IT 327 31 The foldr Function Is Curried - foldr; val it = fn : ('a * 'b -> 'b) -> 'b -> 'a list -> 'b - foldr (op +); val it = fn : int -> int list -> int - foldr (op +) 0; val it = fn : int list -> int - val sum = foldr (op +) 0; val sum = fn : int list -> int - sum [1,2,3,4,5]; val it = 15 : int 4/8/2015 IT 327 32 The foldl Function Perform the binary operation on all the elements of a list from the left (head) foldl f c x where x = [x1, …, xn] and computes: f xn , f xn1 , f x2 , f x1 , c So foldl (op +) 0 [1,2,3,4] 4+(3+(2+(1+0)))=10 foldr (op +) 0 [1,2,3,4] 1+(2+(3+(4+0)))=10 4/8/2015 IT 327 33 Commutative foldl and foldr are the same if the function is associative and commutative, like + and * ^ is not commutative -foldr (op ^) "" ["abc","def","ghi"]; val it = "abcdefghi" : string -foldl (op ^) "" ["abc","def","ghi"]; val it = "ghidefabc" : string – is neither associative nor commutative -foldr (op -) 0 [1,2,3,4]; val it = ~2 : int - foldl (op -) 0 [1,2,3,4]; val it = 2 : int 4/8/2015 IT 327 34 Homework 6 Chapter 9. Exercises: 3, 6, 10, 26, 27, 28 plus 4 questions to be announced. 4/8/2015 IT 327 35