(* 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 val szero : int -> int = fn 0 => 1 (* 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 x = x = 0 orelse x = 1 (* There are other non-pattern matching implementations. *) fun is_zero_or_one 0 = true | is_zero_or_one 1 = true | is_zero_or_one _ = false fun is_zero_or_one x = case x of 0 => true | 1 => true | _ => false fun is_zero_or_one x = (fn 0 => true | 1 => true | _ => false) x (* 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? *) fun factorial_checked_once n = let fun helper 0 = 1 | helper n = n * helper (n - 1) in if n < 0 then raise Domain else helper n 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 (n: int, acc: int) : int = helper (n - 1, n * 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 *) fun sumloop (i, n, sum) = if i < n then sumloop (i + 1, n, sum + i) else sum (* 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) = if i < n then forloop (body (acc, i, n), i + 1, n, body) else acc (*******************************************************************) (* Lists *) []; [3]; nil; 3 :: []; 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) fun len nil = 0 | len (_ :: t) = 1 + len t (* EXERCISE: Write a function that appends two lists. *) fun append (nil, l) = l | append (h :: t, l) = h :: append (t, l) (* 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 *) (* EXERCISE: Write a tail recursive list reverse function. *) fun rev l = let fun rev' (nil, acc) = acc | rev' (h :: t, acc) = rev' (t, h :: acc) in rev' (l, nil) end