(* Meeting 11: SML: Higher-Order Functions *) exception Unimplemented; (*******************************************************************) (* REVIEW *) (* Datatypes *) (* Parameterized by types *) datatype 'a option = NONE | SOME of 'a (* Is a built-in type. *) (* Dynamic Typing *) (* In dynamically-typed languages, we have the ability create hetereogenous data structures, like [3, "abc", 4, "def"] In SML, we can create such structures as well. How? *) datatype int_or_string = Int of int | String of string val x = [Int 3, String "abc", Int 4, String "def"] (* Recursive datatypes *) (* EXERCISE: Define a binary tree datatype that is either empty or node with some data of type 'a and two subtrees. Then, define a function height : 'a tree -> int that returns the height of the tree. Int.max : int * int -> int returns the max of two integer values. *) datatype 'a tree = Empty | Node of 'a tree * 'a * 'a tree fun height Empty = 0 | height (Node (l, _, r)) = 1 + Int.max (height l, height r) (* EXERCISE: Write a function flatten : 'a tree -> 'a list that returns an in-order traversal of the nodes. *) fun flatten t = let fun flatten' (Empty, acc) = acc | flatten' (Node (l, d, r), acc) = flatten' (l, d :: flatten' (r, acc)) in flatten' (t, []) end (* Abstract Syntax *) (* One of the main benefits of using a language like SML is that it is easy to work with abstract syntax. *) (* Our favorite arithmetic expression language translates directly into a datatype. e ::= n | e + e | e * e *) datatype expr = Num of int | Plus of expr * expr | Times of expr * expr (* EXERCISE: Write an evaluation function for this language. eval : expr -> int *) fun eval (Num n) = n | eval (Plus (e1, e2)) = (eval e1) + (eval e2) | eval (Times (e1, e2)) = (eval e1) * (eval e2) (*******************************************************************) (* Higher-Order Functions and Currying *) (* We have already seen functions that take other functions as values. *) (* EXERCISE: Write the map function on lists: applies a function to each element in the list. map : ('a -> 'b) * 'a list -> 'b list *) fun map (f, nil) = nil | map (f, h :: t) = (f h) :: map (f, t) (* Can also have functions that return functions. *) (* EXERCISE: Write a function that takes two functions and composes them (i.e., compose (f,g) is f o g). What is the type of compose? *) fun compose (f, g) = (fn x => f (g x)) (* EXERCISE: Write a function constantly : 'a -> ('b -> 'a) that when applied to a value k returns a function that yields k whenever it is applied *) val constantly = fn k => (fn a => k) (* also write the type 'a -> 'b -> 'a. -> is right associative. *) fun constantly k = (fn a => k) fun constantly k a = k; (* Observe the white space between k and a *) constantly 42; (* Creates a new function bundled with its environment (i.e., a closure) (not declared anywhere else). Observe that this is a new instance of the code (fn a => k). This makes ML higher-order functions more powerful than C's function pointers. *) (* Slightly more interesting. A function plusbyn : int -> (int -> int) that creates an "incrementer by n" function. *) fun plusbyn n = (fn x => x + n) val plusby2 = plusbyn 2; plusby2 7; plusby2 21; fun plusbyn n x = x + n val plusby3 = plusbyn 3; plusby3 7; plusbyn 3 4; (* plusbyn looks just like plus, a function that takes two ints and adds them *) fun plus (x,y) = x + y (* The process of converting plus to plusbyn is called currying. *) fun curry f x y = f (x, y) (* What's the type of curry? *) (* It's not different with functions on datatypes. *) fun map f nil = nil | map f (h :: t) = (f h) :: (map f t) (* What's the type of this map? *) (* Currying allows us to stage computation. *) (* Fibonacci, the slow way. *) fun slowfib 0 = 1 | slowfib 1 = 1 | slowfib n = slowfib (n - 1) + slowfib (n - 2) (* Add to the nth fibonacci number *) fun fibplus (n: int, x: int) = let val fib = slowfib n in fib + x end; (* fibplus (40, 2); fibplus (40, 5); fibplus (40, 7); *) (* Curry fibplus. *) fun fibplus_curried (n: int) (x: int) = let val fib = slowfib n in fib + x end (* val fibnplus = fibplus_curried 40; fibnplus 2; fibnplus 5; fibnplus 7; *) (* Curry fibplus, but stage the computation *) fun fibplus_staged (n: int) = let val fib = slowfib n (* as soon as we have n, find the nth fib number *) in fn x => fib + x (* return function that adds x *) end val fibnplus_staged = fibplus_staged 40; (* slowfib 40 computed here *) (* fibnplus_staged 2; fibnplus_staged 5; fibnplus_staged 7; *) (*******************************************************************) (* Higher-Order Functions and Control *) (* EXERCISE: Write a function add_list : int list -> int that adds the numbers up in a list. *) fun add_list nil = 0 | add_list (h :: t) = h + add_list t (* How about mul_list? *) fun mul_list nil = 1 | mul_list (h :: t) = h * mul_list t (* EXERCISE: Write a function reduce : ('a * 'b -> 'b) -> 'a list -> 'b -> 'b that captures the commonality of add_list and mul_list. reduce f [x,y,z,...,w] base returns f (x, f (y, f (z, ... f (w, base)))) *) fun reduce f nil base = base | reduce f (h :: t) base = f (h, reduce f t base) (* Now define add_list and mul_list using reduce. *) fun add_list l = reduce ( op + ) l 0 fun mul_list l = reduce ( op * ) l 1 (* Seem familiar? Such functions are also called "fold" functions. *) (* EXERCISE: Write the reduce/fold function that works left through the list. You can do this tail recursively. *) (*******************************************************************)