CS 3110 Recitation 6
More on the Substitution Model of Evaluation

In this recitation we will review the substitution model we have seen in last lecture, and we will give a few more examples. At the end on the lecture, we will also spend some time on lexical and dynamic scoping. In the following, e will stand for expressions, v for values, p for patterns and x,y,z for variables.

Review of the evaluation rules

Last time we have seen a few rules for evaluating. In these rules, the arrow → just means "reduces to" and is completely different from the arrows in the function types.

The two main rules for the if statements:

if true then e1 else e2e1
if false then e1 else e2e2
To get an expression of the above form, the first thing the evaluator does is try to reduce the conditional expression to a boolean value, either true or false ; so if ev, then:
if e then e1 else e2   →  if v then e1 else e2

The rule for the let statement is:

let x=v in e1e1{v/x}
And similarly to what happens for if statements, the first thing the evaluator does is try to reduce the value that will be assigned to x, so that if ev, then:
let x=e in e1   →  let x=v in e1

Here, we used the notation e1{v/x} whose idea is "replace all free occurrences of x in e1 by v. More precisely, this notation can be defined recursively by:

x{e/x}  =  e
x{e/y}  =  x
(fun y->e1) {e/x}  =  (fun y->e1{e/x})
(fun x->e1) {e/x}  =  (fun x->e1)
(let y=e2 in e1) {e/x}  =  (let y=e2{e/x} in e1{e/x})
(let x=e2 in e1) {e/x}  =  (let x=e2{e/x} in e1)
f(x) { fun y->e1 / f } =  (fun y->e1)(x)
Finally, we want to be able to evaluate function applications. To do that, we first want to evaluate the argument, then replace the name of the argument by the value of that argument in the body of the function. The first rule involved here is the fact that if ev, then:
(fun x -> e1) e   →  (fun x -> e1) v
And the other rule describes the actual replacement:
(fun x -> e1) ee1{v/x}
At that point, the only prob lem remaining was that we are not able to evaluate recursive functions. A way to understand this is that a recursive function is an infinite unfolding of the original definition. More precisely:
let rec f = fun x -> e in e'  e'{f'/f}
    (with global binding f' = fun x -> e{f'/f}, f' fresh)
[Note to instructor:] Leave all these rules on the side of the board so that students can refer to them at all times when running through the examples.

First example

Let us consider this difficult-to-understand code:
let rec f(g,n) =
  if n=1 then g(0)
  else g(0) + f((fun x->n), n-1)
in
  f((fun x->10), 3)
Here is the complete evaluation of that code:
f'((fun x->10), 3)      (with f' = fun (g,n) ->
                                    if n=1 then g(0)
                                    else g(0) + f'((fun x->n), n-1))
→
(fun (g,n) ->
    if n=1 then g(0)
    else g(0) + f'((fun x->n), n-1)) ((fun x->10), 3)
→
    if 3=1 then (fun x->10)(0)
           else (fun x->10)(0) + f'((fun x->3), 3-1)
→
    if false then (fun x->10)(0)
	     else (fun x->10)(0) + f'((fun x->3), 3-1)
→ (fun x->10)(0) + f'((fun x->3), 3-1)
→ 10 + f'((fun x->3), 3-1)
→ 10 + (fun (g,n) -> ...) ((fun x->3), 3-1)
→ 10 + (fun (g,n) -> ...) ((fun x->3), 2)
→ 10 + if 2=1 then (fun x->3)(0) else (fun x->3)(0) + f'(fun x->2, 2-1)
→ 10 + if false then (fun x->3)(0) else (fun x->3)(0) + f'(fun x->2, 2-1)
→ 10 + (fun x->3)(0) + f'(fun x->2, 2-1)
→ 10 + 3 + f'(fun x->2, 2-1)
→ 10 + 3 + (fun (g,n) -> ...)(fun x->2, 2-1)
→ 10 + 3 + (fun (g,n) -> ...)(fun x->2, 1)
→ 10 + 3 + if 1=1 then (fun x->2)(0) else ...
→ 10 + 3 + (fun x->2)(0)
→ 10 + 3 + 2
→ 10 + 5
→ 15

The evil example

Last time we did not have time to completely and carefully go through the tricky example we had [Note to instructor: really go through this example, it is probably going to take 15 min or so but it is worth it]:

let rec evil(f1, f2, n) =
  let f(x) = 10 + n in
    if n = 1 then f(0) + f1(0) + f2(0)
    else evil(f, f1, n-1)
and dummy(x) = 1000
in
  evil(dummy, dummy, 3)
Let us first try by changing the last line to evil(dummy, dummy, 1). The evaluation goes as follows:
evil(fun(x)->1000, fun(x)->1000, 1)

→ let f(x) = 10 + 1 in
  if 1 = 1 then f(0) + (fun(x)->1000)(0) + (fun(x)->1000)(0)
  else evil(f, (fun(x)->1000), 1-1)

→ (fun(x)->10+1)(0) + (fun(x)->1000)(0) + (fun(x)->1000)(0)

→  2011
Now, let us try by changing the last line to evil(dummy, dummy, 2). Here is the evaluation:
evil(fun(x)->1000, fun(x)->1000, 2)

→ let f(x) = 10 + 2 in
  if 2 = 1 then f(0) + (fun(x)->1000)(0) + (fun(x)->1000)(0)
  else evil(f, (fun(x)->1000), 2-1)

→ evil(fun(x)->10+2, fun(x)->1000, 1)

→ let f(x) = 10 + 1 in
  if 1 = 1 then f(0) + (fun(x)->10+2)(0) + (fun(x)->1000)(0)
  else evil(f, (fun(x)->10+2), 1-1)

→ (fun(x)->10+1)(0) + (fun(x)->10+2)(0) + (fun(x)->1000)(0)

→ 1023
Finally, the real case with evil(dummy, dummy, 3)
evil(fun(x)->1000, fun(x)->1000, 3)

→ let f(x) = 10 + 3 in
  if 3 = 1 then f(0) + (fun(x)->1000)(0) + (fun(x)->1000)(0)
  else evil(f, (fun(x)->1000), 3-1)

→ evil(fun(x)->10+3, fun(x)->1000, 2)

→ let f(x) = 10 + 2 in
  if 2 = 1 then f(0) + (fun(x)->10+3)(0) + (fun(x)->1000)(0)
  else evil(f, (fun(x)->10+3), 2-1)

→ evil(fun(x)->10+2, fun(x)->10+3, 1)

→ let f(x) = 10 + 1 in
  if 1 = 1 then f(0) + (fun(x)->10+2)(0) + (fun(x)->10+3)(0)
  else evil(f, (fun(x)->10+2), 1-1)

→ (fun(x)->10+1)(0) + (fun(x)->10+2)(0) + (fun(x)->10+3)(0)

→ 36
What about evil(dummy, dummy, 4)? evil(dummy, dummy, 5)?Can you guess? (Answer: 36 for any n>2)

Lexical vs. dynamic scoping

In the substitution model we have seen thus far, variable names are subsituted immediately throughout their scope when a function is applied or a let is evaluated. This means that whenever we see a variable, how that variable is bound is immediately clear: the variable is bound at the closest enclosing binding occurrence that can be seen in the program text. This rule is called lexical scoping.

To fully understand this, let us have a simple exemple:

let x=1 in 
 let f(y)=y+x in
  let x=3 in
   f(1) 
In lexical scoping, the first declaration of x (x=1) is used when evaluating f(1), because this is the one which was in scope when f was declared; thus the result is 2.

The most common alternative to lexical scoping is called dynamic scoping. In dynamic scoping, a variable is bound to the most recently bound version of the variable, and function values do not record how their variables such as x are bound. In the language Perl, the equivalent of the example code results in 4 because the second declaration of x (x=3) is used when evaluating f(1). Dynamic scoping can be confusing because the meaning of a function depends on the context where it is used. Most modern languages use lexical scoping.

Let us apply this to the tricky example from earlier.The key question is what the variable n means within the functions f, f1, f2. In lexical scoping, even though these variables are all bound to the function f, they are bound to versions of the function f that occurred in three different scopes, where the variable n was bound to 1, 2, and 3 respectively. For example, on the first entry to evil, the value 3 is substituted for the variable n within the function f (which ultimately becomes f2 on the third application on evil). In dynamic scoping on the other hand, it would print 33 rather than 36, because the most recently bound value for the variable n is 1. Here is the complete evaluation, where we do not replace the n because of the dynamic scoping nature of our evaluation:

evil(fun(x)->1000, fun(x)->1000, 3)

→ let f(x) = 10 + n in
  if 3 = 1 then f(0) + (fun(x)->1000)(0) + (fun(x)->1000)(0)
  else evil(f, (fun(x)->1000), 3-1)

→ evil(fun(x)->10+n, fun(x)->1000, 2)

→ let f(x) = 10 + n in
  if 2 = 1 then f(0) + (fun(x)->10+n)(0) + (fun(x)->1000)(0)
  else evil(f, (fun(x)->10+n), 2-1)

→ evil(fun(x)->10+n, fun(x)->10+n, 1)

→ let f(x) = 10 + n in
  if 1 = 1 then f(0) + (fun(x)->10+n)(0) + (fun(x)->10+n)(0)
  else evil(f, (fun(x)->10+2), 1-1)

→ (fun(x)->10+n)(0) + (fun(x)->10+n)(0) + (fun(x)->10+n)(0)

→ 33 (because n is bound to 1 at that point)