(* Meeting 09: SML: Recursion, Pattern Matching, and Datatypes *) exception Unimplemented (*******************************************************************) (* REVIEW *) (* Tuples and Records *) type point = int * int val pt1 = (1, 2) fun first (pt: point) = let val (x, _) = pt (* tuple pattern *) in x end; first pt1; fun first ((x, _): point) = x; first pt1; (* "Multi-argument functions" are simply unary functions that take tuples *) fun add (x, y) = x + y (* One can return multiple results with tuples *) fun divrem (x, y) = (x div y, x mod y); (* 0-tuple is called unit - a type with only one value *) () : unit; (* Do we need a 1-tuple? *) (* Records *) type point = {x: int, y: int} val pt1 = {x=1, y=2} fun first pt = let val {x=x0, y=y0} = pt in x0 end; first pt1; fun first pt = let val {x, y} = pt in x end; (* shorthand pattern *) first pt1; fun first ({x,...}: point) = x; first pt1; fun first (pt: point) = #x pt; (* selector *) first pt1; (* In fact, tuples are simply names with indexes for labels *) #1(1, 2); (*******************************************************************) (* Pattern Matching *) (* Can match deeply. *) val (x, (y, z)) : int * (real * int) = (3, (4.5, 2)) (* What are x, y, and z bound to? *) val (x, y) : int * (real * int) = (3, (4.5, 2)) (* What are x and y bound to? *) (* What's different about the following pattern matches? *) val fst : int * int -> int = fn (x,y) => x (* tuple * is homogenous type *) val szero : int -> int = fn 0 => 1 (* int is a heterogenous type *) (* EXERCISE: Implement the function is_zero_or_one : int -> bool that returns true if the argument is 0 or 1 and otherwise false in multiple ways (at least one with pattern matching and at least one without pattern matching). *) fun is_zero_or_one n = n = 0 orelse n = 1 (* if n = 0 orelse n = 1 then true else false *) fun is_zero_or_one 0 = true | is_zero_or_one 1 = true | is_zero_or_one _ = false fun is_zero_or_one n = (case n of 0 => true | 1 => true | _ => false) val is_zero_or_one : int -> bool = fn 0 => true | 1 => true | _ => false (* Booleans: if-then-else is shorthand for case *) fun ite b x y = if b then x else y fun ite b x y = case b of true => x | false => y (* What happens with these functions? *) (* fun not True = false | not False = true fun factorial n = n * factorial (n - 1) | factorial 0 = 1 fun is_zero_or_one 0 = true | is_zero_or_one 1 = true *) (*******************************************************************) (* Recursion and Iteration *) (* Look again at factorial. *) val rec factorial : int -> int = fn 0 => 1 | n => n * factorial (n - 1) fun factorial 0 = 1 | factorial n = n * factorial (n-1) (* What if we call "factorial ~2" *) (* invariant: n >= 0 *) fun factorial 0 = 1 | factorial n = n * factorial (n-1) (* Let's check the invariant. *) fun factorial_checked n = if n < 0 then raise Domain else if n = 0 then 1 else n * factorial_checked (n - 1) (* What's inefficient about factorial_checked? *) (* What could we do? *) fun factorial_checked n = let fun f 0 = 1 | f n = n * f (n - 1) in if n >= 0 then f n else (raise Domain) (* raise = throw an exception *) end (* Let's look again at the basic factorial again. *) (* invariant: n >= 0 *) fun factorial 0 = 1 | factorial n = n * factorial (n-1) (* Let's write out the evaluation steps of "factorial 3" *) (* What do we observe about the size of the expression during evaluation? *) (* Consider this alternative definition for factorial. *) fun factorial_iter (n: int) : int = let fun helper (0: int, acc: int) : int = acc | helper (m: int, acc: int) : int = helper (m - 1, m * acc) in helper (n, 1) end (* Let's write out the evaluation steps of "factorial_iter 3" *) (* Observe that helper is "tail recursive". The accumulator parameter "acc" records partial results -- essentially the "state" of the computation. *) (* EXERCISE: Translate the following imperative code as a tail recursive function. sumloop : int * int * int -> int sum = 0; for (i = 0; i < n; i++) { sum += i; } *) fun sumloop (i, n, sum) = if i = n then sum else sumloop (i + 1, n, sum + i); val n = 10; sumloop (0, n, 0); (* EXERCISE: Let's write a general "for loop" with an accumlator. forloop : int * int * int * (int * int * int -> int) -> int The body parameter is the code for the loop body, which computes a new value of the accumulator given the current (acc, i, n). We could imagine a language construct for acc <- ..., i <- 0 to n do acc <- ... acc, i, n ... done *) fun forloop (acc, i, n, body) = raise Unimplemented; (*******************************************************************) (* Lists *) []; [3]; nil; 3 :: []; (* "cons" - prepend an element to a list*) nil; (op ::); (* val len : 'a list -> int Returns the length of a list. *) fun len l = if (l = nil) then 0 else 1 + len (tl l) (* tl : 'a list -> 'a list *) fun len nil = 0 | len (_ :: t) = 1 + len t (* EXERCISE: Write a function that appends two lists. *) fun append (l': 'a list, l: 'a list) : 'a list = raise Unimplemented (* append([3,4],[5,6]) = [3,4,5,6] *) fun append (nil, l) = l | append (h :: t, l) = h :: append (t, l) (* space and time linear in the first list *) (* Reverse *) fun rev nil = nil | rev (h :: t) = append (rev t, [h]) fun rev nil = nil | rev (h :: t) = rev t @ [h] (* using built-in append function *) (* quadratic reverse function *) (* EXERCISE: Write a tail recursive list reverse function. *) fun rev l = raise Unimplemented