Last time: precise evaluation model and rewrite rules. This time: recursion. Recall (excerpts):
Expressions: e ::= c (* constants *) | id (* variables *) | let d in e end (* let expressions *)
Declarations: d ::= valid = e (* value declarations *)
Rule #E7 [let]: to evaluate let d in e end, evaluate the declaration d to get a substitution S. Perform the substitution S on e yielding a new expression e'. Then evaluate e' to get the final answer.
eval(let d in e end) = v where (0) eval_decl(d) = S (1) substitute(S,e) = e' (2) eval(e') = v
Rule #D1[val declarations]: to evaluate a declaration val id = e, evaluate e to a value v and match v against the identifier id to yield a substitution S. The substitution S is the result of the declaration.
eval_decl(val p = e) = S where (0) eval(e) = v (1) match(v,p) = S = [(p,v)]This allowed us to do fun things like
fun apply_twice (f:int->int, x:int):int = f (f (x)) val x = apply_twice(triple,4) (* 36 *) fun new_mul9(z:int):int = apply_twice(triple,z) (* pretend these have the obvious semantics *) type base = int type func = int -> int fun twice_func (f:func):func = fn (x:base) => f (f (x)) val newer_mul9 = twice_func(triple); fun compose (f:func, g:func):func = fn (x:base) => f (g (x))
let fun triple(z:int->int) = 3 * z in triple(14) endWe will assume this is shorthand for saying
let val triple:int->int = (fn(z:int):int=> 3 * z) in triple(14) end
let val fact = fn(z:int):int => if (z = 1) then z else z * fact (z – 1) in fact(3) endOur syntax will now include fun as well:
Expressions: e ::= c (* constants *) | id (* variables *) | let d in e end (* let expressions *) | (fun id(id1:t1):t2 = e) (* recursive functions *)
Declarations: d ::= valid = e (* value declarations *) | fun id(id1:t1):t2 = e (* function declarations *)
Rule #D2[fun declarations]: to evaluate a function declaration fun f(x:t):t' =e, return the substitution that maps f to the function expression fun f(x:t):t'=e.
eval_decl(fun f(x:t):t' = e) = [(f,fun f(x:t):t'=e)]
Rule #E8 [fun expressions]: to evaluate a recursive function expression (fun f(x:t):t' = e), we "unroll" the function once. In other words, we return the anonymous function (fn (x:t) => e') where e' is the result of substituting (fun f(x:t):t' = e) for the identifier f within the body e.
eval(fun f(x:t):t' = e) = (fn (x:t):t' => e') where substitute([f,(fun f(x:t):t' = e)],e) = e'.Note that in the non-recursive case, this simplifies to the rule for fun we showed before (i.e., the call to substitute returns its second argument, so e’ = e).
eval(let fun fact(z:int):int = if (z = 1) then z else z * fact (z – 1) in fact(3) end) By E7 Þ eval((fun fact(z:int):int = if (z = 1) then z else z * fact (z – 1)) (3)) By E8 Þ eval((fn (z:int):int => if (z = 1) then z else z * (fun fact(z:int):int = if (z = 1) then z else z * fact (z – 1)) (z – 1)) (3)) By E3 Þ eval(if (3 = 1) then 3 else 3 * (fun fact(z:int):int = if (z = 1) then z else z * fact (z – 1)) (3 - 1))) By E6 Þ eval(3*(fun fact(z:int):int = if (z = 1) then z else z * fact (z – 1)) (2))As we can see, the evaluation model is not as trivial as we thought it was. Note that there end up being deferred evaluations here; we can’t multiply 3 * fact(2) until we know what fact(2) is.
Now, try to use this new evaluation rule with other recursive funtions, like repeated.
fun repeated(f:func,n:int):func = if (n=0) then(fn(x:base) => x) else compose (f, repeated(f,n-1))
We have now a precise semantics for our ML subset, we can use our semantic in different areas. In particular we use it to prove properties of programs, i.e. correctness and efficiency.
There are 2 key tools for this. The first one is our precise semantics; without this, we have no definition of what an ML program does, hence no hope of proving anything about it. Our semantics is precise enough so that one can prove things with arbitrary detail; it’s good to know how to do this, but you won’t be required to do this in the course.
The second key tool is, of course, induction. Almost all proofs in CS are based on induction.
We are going to prove a mathematical property of a program, with respect to a semantics. For example we might prove that fact(n) = n!. To say this with complete precision, what we are proving is:
For all integers n > 0, eval(let fun fact(z:int):int = if (z = 1) then z else z * fact (z – 1) in fact(n) end) = n!This is, to be precise, an infinite set of statements, one for each value of n. Above, we started to prove it for n = 3. Not much fun for n = 106, etc…
We will prove this statement by induction. This means that for all n Î Z+, we will prove that fact(n) = n! We’ll do this just once, rather than À0 times.
An induction proof has 4 parts:
Example: fact(n) = n!
eval(let fun fact(z:int):int = if (z = 1) then z else z * fact (z – 1) in fact(1) end) Þ eval((fun fact(z:int):int = if (z = 1) then z else z * fact (z – 1)) (1)) Þ eval((fn (z:int):int => if (z = 1) then z else z * (fun fact(z:int):int = if (z = 1) then z else z * fact (z – 1)) (z – 1)) (1)) Þ eval(if (1 = 1) then 1 else 1 * (fun fact(z:int):int = if (z = 1) then z else z * fact (z – 1)) (1 – 1))) Þ 1Then we will continue proving the inductive step, and our proof will be done.