(* Meeting 08: SML: Declarations, Functions, and Tuples *) (*******************************************************************) (* A small SML program *) val four = 4 fun plusfour y = y + four val eight = plusfour four; (*******************************************************************) (* Integers *) 1 : int; ~1 : int; (* -1 : int; ERROR: syntax *) 3 + 4 : int; (2 : int) * 5; 7 div 2; (*******************************************************************) (* Reals *) 1.6 : real; 14.0 / 2.0; (* 14 / 2; ERROR: type *) (*******************************************************************) (* Strings *) "hello"; (*******************************************************************) (* Characters *) #"a"; (*******************************************************************) (* Booleans *) true; false; if true then 1 else 0; (*******************************************************************) (* Type errors *) (* 5.2 * 3; ERROR: type *) (* if 1<2 then 0 else (5.2 * 3); ERROR: type *) (*******************************************************************) (* Run-time exceptions *) (* 10 div 0; ERROR: run-time *) (*******************************************************************) (* Type binding *) type float = real; val pi : float = 3.14; (*******************************************************************) (* Limiting scope *) let val m = 3 val n = m * m in m * n end; (* m; ERROR: unbound variable *) local val two = 2 in val four = two * two end; (* two; ERROR: unbound variable *) (* What value does r get bound to? *) val m = 2 val r = let val m = 3 val n = m * m in m * n end * m; (*******************************************************************) (* Defining Functions *) val area: real -> real = fn r => pi * r * r fun area (r: real) : real = pi * r * r (* convenient form *) (*******************************************************************) (* Shadowing and Closures *) val area_r_2 = area 2.0 (* We shadow pi. What value should area_r_2' get bound to? Remember SML uses lexical/static scoping. *) val pi = 2.0 val area_r_2' = area 2.0 (* In order to get static scoping, the environment at the definition of area needs packaged with it. This package is called a closure. *) (*******************************************************************) (* 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); (*******************************************************************) (* Recursion *) (* With functional programs, recursion is a more natural way to repeat. *) (* The rec keyword indicates that the binding is self-referential, which we need to define a recurive function. *) val rec factorial : int -> int = fn 0 => 1 | n => n * factorial (n-1) (* The fun notation is the convenient syntax for defining recursive functions and is what one normally uses. *) fun factorial 0 = 1 | factorial n = n * factorial (n-1) (* EXERCISE: exponentiation. Function exp should return x^n. Hint: Remember exp : int * int -> int is a function that takes a tuple, and you can pattern match on tuples. *) fun exp (x, 0) = 1 | exp (x, n) = x * exp (x, n-1); exp (2,4); (* EXERCISE: iterative (i.e., tail recursive) exponentiation *) fun exp (x, n) = let (* This helper function exp' does the real work. It uses an extra parameter often called the "accumulator". The accumulator allows us to re-associate the multiplication to happen before the recursive call rather than afterwards. The accumulator can be thought of the current "state" of the exponentiation. *) fun exp' (0, acc) = acc | exp' (n, acc) = exp' (n-1, x*acc) in exp' (n, 1) end; exp (2,4);