CS312 : Recursion in the Substitution Model

Overview

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))

Evaluation of 'fun'

To start with, we could naturally assume that fun works as follows. Consider an example:
   let fun triple(z:int->int) = 3 * z in triple(14) end 
We will assume this is shorthand for saying

  let 
     val triple:int->int = (fn(z:int):int=> 3 * z) 
  in 
     triple(14)
  end

Evaluating recursive functions

With this definition, though, recursion doesn’t work. Try this:

  let
     val fact = fn(z:int):int => if (z = 1) then z else z * fact (z – 1)
  in
     fact(3)  
  end
Our 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 *)  

Eval rule for fun

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).

Examples:

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))

Use of the Semantics

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.

Induction summary (for proofs of program correctness).

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:

  1. A precise statement of what we are proving (given above, in our example). We may call this statement P[n]; it is a mathematical statement about n.
  2. What set N we are doing induction over, and what variable we are using to represent an element of that set. Often, there is more than one possibility; here, only N = Z+, induction on n.
  3. A proof for the base case, i.e. P[z] where z is the smallest element of N. Typically z = 1 (sometimes z = 0). More precisely, a proof of P[1] in our example.
  4. A proof that for any m Î N, P[m] Þ P[m+1] (weak in induction)

Example: fact(n) = n!

  1. fact(n) = n!
  2. Induction on n in Z+
  3. We need to show fact(1) = 1. See below.
  4. We need to show that for any m in Z+, fact(m) = m! => fact(m+1) = (m+1)! See below.
Then we do the base case.
  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)))

Þ

  1
Then we will continue proving the inductive step, and our proof will be done.