Introduction Even though the syntax of Scheme is

  • Slides: 49
Download presentation
Introduction Even though the syntax of Scheme is simple, it can be very difficult

Introduction Even though the syntax of Scheme is simple, it can be very difficult to determine the semantics of an expression. Hacker’s approach: Run it and see what happens. • What if it behaves differently on different machines or with different arguments? • What if you’re in charge of writing the first compiler? CS 212 approach: Construct a formal, mathematical model.

Overview Develop the model by starting with an extremely simple language and work our

Overview Develop the model by starting with an extremely simple language and work our way up. – arithmetic – if, booleans – lambda, variables – substitution Goal: be able to construct a formal proof that a given Scheme program evaluates to a specified value.

Scheme-0 Syntax: num op : : = + | - | * | /

Scheme-0 Syntax: num op : : = + | - | * | / (expressions) e : : = num | op | (e 1 … en ) (numbers) (operators) English: Expressions are either numbers, an operator (+, -, *, or /) or a combination which is a sequence of nested expressions surrounded by parentheses. Note: we use blue to denote meta-variables

Scheme-0 Values (values) v : : = num | op English: Values are either

Scheme-0 Values (values) v : : = num | op English: Values are either numbers or an operator. Values are a subset of expressions. Well-formed expressions evaluate to a value. We write e => v when expression e evaluates to value v.

Rules for Evaluation There are just two for Scheme-0: One for values, and one

Rules for Evaluation There are just two for Scheme-0: One for values, and one for certain kinds of combinations. If no rule applies, then the program is illformed. (Dr. Scheme reports an error. )

Evaluation Rule 1: Values v => v English: a value evaluates to itself. Examples:

Evaluation Rule 1: Values v => v English: a value evaluates to itself. Examples: 3 => 3 (by rule 1) + => + (by rule 1)

Evaluation Rule 2: Arithmetic To prove (e 1 e 2 e 3) => v

Evaluation Rule 2: Arithmetic To prove (e 1 e 2 e 3) => v show: (a) e 1 => op (e 1 evaluates to an operator) (b) e 2 => v 1 (e 2 evaluates to a value) (c) e 3 => v 2 (e 3 evaluates to a value) (d) v 1 op v 2 = v (applying the operator to the values yields the value v. )

Evaluation Rule 2 example: (+ 3 4) => 7 (by rule 2) (a) +

Evaluation Rule 2 example: (+ 3 4) => 7 (by rule 2) (a) + => + (by rule 1, since + is a value) (b) 3 => 3 (by rule 1, since 3 is a value) (c) 4 => 4 (by rule 1, since 4 is a value) (d) 3+4 = 7 (by math)

Scheme-1: op : : = + | - | * | / | <

Scheme-1: op : : = + | - | * | / | < | > | = bool : : = #t | #f e : : = num | op | (e 1 … en ) | (if e 1 e 2 e 3) | bool v : : = num | op | bool • We added booleans (as values) and if expressions. • #t is true, #f is false.

Eval. Rules 1 and 2 are the same: 1. v => v Examples: #t

Eval. Rules 1 and 2 are the same: 1. v => v Examples: #t => #t, < => < 2. (e 1 e 2 e 3) => v if: (a) e 1 => op (b) e 2 => v 1 (c) e 3 => v 2 (d) v 1 op v 2 = v Examples: (< 3 4) => #t

Evaluation Rule 3 a: (if #f) To prove (if e 1 e 2 e

Evaluation Rule 3 a: (if #f) To prove (if e 1 e 2 e 3) => v show: (a) e 1 => #f (b) e 3 => v Example: (if (< 3 1) -1 0) => 0 (by rule 3 a) (< 3 1) => #f (by rule 2) (a) < => < (by rule 1) (b) 3 => 3 (by rule 1) (c) 1 => 1 (by rule 1) (d) 3 < 1 = #f (by math) (b) 0 => 0 (by rule 1)

Evaluation Rule 3 b (if #t) To prove (if e 1 e 2 e

Evaluation Rule 3 b (if #t) To prove (if e 1 e 2 e 3) => v show: (a) e 1 => v 1 and v 1 is not #f (b) e 2 => v Example: (if (> 3 1) -1 0) => -1 (by rule 3 a) (> 3 1) => #t (by rule 2) (a) > => > (by rule 1) (b) 3 => 3 (by rule 1) (c) 1 => 1 (by rule 1) (d) 3 > 1 = #t (by math) (b) -1 => -1 (by rule 1)

Notes on If Unlike other combinations, if is lazy. For non-if combinations (rule 2):

Notes on If Unlike other combinations, if is lazy. For non-if combinations (rule 2): – evaluate arguments to values eagerly – apply operator to values For if combinations (rule 3): – evaluate first argument (only) to value – if it’s #f, then evaluate third argument (3 a) – otherwise, evaluate second argument (3 b)

Scheme-2 e : : = num | op | (e 1 … en )

Scheme-2 e : : = num | op | (e 1 … en ) | (if e 1 e 2 e 3) | bool | fn | x v : : = num | op | bool | fn fn : : = (lambda (x 1 … xn) e) • We add lambda expressions (functions) and variables. – We use x to represent an arbitrary variable • Note that functions are values (i. e. , lambdas evaluate to themselves. ) • Previous rules (1, 2, 3 a, 3 b) still apply • It’s an error to run into an unbound variable.

Eval. Rule 4: (most important) To prove (e 1 e 2 e 3 …

Eval. Rule 4: (most important) To prove (e 1 e 2 e 3 … en) => v show: (a) e 1 => (lambda (x 2 x 3… xn) e) (b) e 2 => v 2, e 3 => v 3, …, en => vn (c) e[v 2/x 2, v 3/x 3 , … , vn/xn] = e’ (i. e. , substitute v 2, …, vn for x 2, …, xn in e) (d) e’ => v • We’ll formally define substitution later.

Example: ((lambda (x) (if x 3 (* 4 2))) #f) => 8 (by 4)

Example: ((lambda (x) (if x 3 (* 4 2))) #f) => 8 (by 4) (a) (lambda (x) (if x 3 (* 4 2))) => (lambda (x) (if x 3 (* 4 2))) (by 1) (b) #f => #f (by 1) (c) (if x 3 (* 4 2))[#f/x] = (if #f 3 (* 4 2)) (by subst. ) (d) (if #f 3 (* 4 2)) => 8 (by 3 a) (a) #f => #f (by 1) (b) (* 4 2) => 8 (by 2, subgoals obvious)

Another Example: (((lambda (x) (lambda (y) y)) 3) 5) => 5 (by rule 4)

Another Example: (((lambda (x) (lambda (y) y)) 3) 5) => 5 (by rule 4) (a) ((lambda (x) (lambda (y) y)) 3) => (lambda (y) y) (by rule 4 & proof below) (b) 5 => 5 (c) y[5/y] = 5 (by rule 1) (by substitution) (d) 5 => 5 (by rule 1) So now all we have to show is part (a). . .

Example Continued ((lambda (x) (lambda (y) y)) 3) => (lambda (y) y) (by rule

Example Continued ((lambda (x) (lambda (y) y)) 3) => (lambda (y) y) (by rule 4) (a) (lambda (x) (lambda (y) y)) => (lambda (x) (lambda (y) y)) (by rule 1) (b) 3 => 3 (by rule 1) (c) (lambda (y) y)[3/x] = (lambda (y) y) (by subst. ) (d) (lambda (y) y) => (lambda (y) y) (by rule 1)

Hmmmm. . . Consider changing y to x systematically: Old: ((lambda (x) (lambda (y)

Hmmmm. . . Consider changing y to x systematically: Old: ((lambda (x) (lambda (y) y)) 3) New: ((lambda (x) x)) 3) Body of outer function Following rule 4: (a) (lambda (x) x)) => (lambda (x) x)) (b) 3 => 3 (c ) (lambda (x) x)[3/x] = ? ? ?

Some Wrong Answers: (lambda (x) x)[3/x] = (lambda (x) 3) (lambda (x) x)[3/x] =

Some Wrong Answers: (lambda (x) x)[3/x] = (lambda (x) 3) (lambda (x) x)[3/x] = (lambda (3) 3) Why are these wrong? – The first x is a binding occurrence (the name of a parameter) – The second x is a free occurrence that refers to a use of the nearest enclosing bound variable. – This is called lexical scope for variables.

Formalizing Substitution e : : = num | op | (e 1 … en

Formalizing Substitution e : : = num | op | (e 1 … en ) | (if e 1 e 2 e 3) | bool | x | (lambda (x 1…xn) e) We write e[v 1/x 1, …, vn/xn] as an abbreviation for performing the substitutions one at a time. So all we really need to define formally is e[v/x]. We do so by cases on e (7 cases):

Substitution Rules 1 -5 are easy No variable, no substitution: s 1. num[v/x] =

Substitution Rules 1 -5 are easy No variable, no substitution: s 1. num[v/x] = num ex: 3[#t/y]=3 s 2. op[v/x] = op ex: +[#t/y]=+ s 3. bool[v/x] = bool ex: #f[#t/x]=#f Usually, just push the substitution in: s 4. (e 1 … en )[v/x] = (e 1 [v/x] … en [v/x]) s 5. (if e 1 e 2 e 3)[v/x] = (if e 1 [v/x] e 2 [v/x] e 3 [v/x] )

Examples for rules s 4 -s 5 (+ 3 2)[#t/y] = (by s 4)

Examples for rules s 4 -s 5 (+ 3 2)[#t/y] = (by s 4) (+[#t/y] 3[#t/y] 2[#t/y]) = (+ 3 2) because +[#t/y] = + 3[#t/y]= 3 2[#t/y]= 2 (by s 2) (by s 1) (if #f 3 2)[#t/y] = (by s 5) (if #f[#t/y] 3[#t/y] 2[#t/y]) = (if #f 3 2)

Substitution: The Real Action s 6. y [v/x] = v if y and x

Substitution: The Real Action s 6. y [v/x] = v if y and x are the same = y otherwise s 7. (lambda (x 1…xn) e) [v/x] = a. (lambda (x 1…xn) e) if x is one of x 1…xn. b. (lambda (x 1…xn) e[v/x]) if x is not one of x 1…xn. Substitution Rule 7 is very important!!!

Example For Rule s 6: (+ y x)[3/y] = (by s 4) (+[3/y] y[3/y]

Example For Rule s 6: (+ y x)[3/y] = (by s 4) (+[3/y] y[3/y] x[3/y]) = (+ 3 x) because +[3/y] = + y[3/y]= 3 x[3/y]= x (by s 2) (by s 6 -- notice y = y) (by s 6 -- notice x y)

Example for rule s 7: (lambda (x y) (+ z y))[3/z] = (s 7

Example for rule s 7: (lambda (x y) (+ z y))[3/z] = (s 7 b) (lambda (x y) (+ z y)[3/z] ) = (s 5) (lambda (x y) (+[3/z] z[3/z] y[3/z] )) = (s 2) (lambda (x y) (+ z[3/z] y[3/z] )) = (s 6 a) (lambda (x y) (+ 3 y[3/z] )) = (s 6 b) (lambda (x y) (+ 3 y)) = (s 2) In the first line, rule s 7 b applies because the variable we’re replacing (z) does not occur as a parameter to the function.

Another Example for rule s 7 (lambda (x y) (+ z y))[3/y] = (s

Another Example for rule s 7 (lambda (x y) (+ z y))[3/y] = (s 7 a) (lambda (x y) (+ z y)) Why? Because the variable we’re substituting for (y) is one of the parameters, so we do not push the substitution in to the body of the function.

Yet another 7 example (lambda (x y) (+ z y))[3/w] = (s 7 b)

Yet another 7 example (lambda (x y) (+ z y))[3/w] = (s 7 b) (lambda (x y) (+ z y)[3/w] ) = (s 5) (lambda (x y) (+[3/w] z[3/w] y[3/w] )) = (s 2) (lambda (x y) (+ z[3/w] y[3/w] )) = (s 6 b) (lambda (x y) (+ z y)) = (s 2) This time, the variable we’re replacing (w) is not one of the parameters, but it doesn’t occur in the body of the function so it disappears!

Revisiting: (lambda (x) x)[3/x] = (lambda (x) x) (by s 7 a) Why? The

Revisiting: (lambda (x) x)[3/x] = (lambda (x) x) (by s 7 a) Why? The x that we’re substituting 3 for was shadowed by another definition. Most (modern) languages have similar scoping rules -- inner definitions of variables hide outer definitions.

Summary Formalized evaluation of Scheme-2 – Gave syntax of expressions • numbers, operators, combinations,

Summary Formalized evaluation of Scheme-2 – Gave syntax of expressions • numbers, operators, combinations, booleans, if, and lambda. – Gave syntax-directed rules for evaluating expressions to values. – Substitution comes into play for user-defined functions. – Lexical scope determines rules for when we substitute what. Still need to cover define. . .

Scheme-3 Expressions and values are as before. . . (programs) (defines) p : :

Scheme-3 Expressions and values are as before. . . (programs) (defines) p : : = d 1 … dn e d : : = (define x e) The top-level declarations allow us to define global variables (usually functions). But they require a slightly different model. . .

Top-Level Environments: An Environment (Env) is a way to keep track of top-level bindings.

Top-Level Environments: An Environment (Env) is a way to keep track of top-level bindings. It simply maps (some) variables to values. Example: {x: =3, y: =#t} English: if you see x while evaluating, replace it with 3 and if you see y, replace it with #t.

Intuition: Suppose our program is: (define x (+ 3 4)) (define inc (lambda (x)

Intuition: Suppose our program is: (define x (+ 3 4)) (define inc (lambda (x) (+ x 1))) (inc x) We evaluate as follows: – start with an empty environment Env 0 = {} – evaluate (+ 3 4) in Env 0, yielding 7 and bind x to 7 resulting in Env 1 = {x: =7} – bind inc to the lambda-value resulting in Env 2 = {x: =7, inc: =(lambda (x) (+ x 1))} – evaluate (inc x) in Env 2 replacing inc with (lambda (x) (+ x 1)) and x with 7 to get 8.

Three New Things: 1. Env |- e => v Same as before except if

Three New Things: 1. Env |- e => v Same as before except if we run into a free variable while evaluating e, we look it up in Env. 2. Env 1 |- d => Env 2 Evaluating a definition yields a new environment (with that definition) 3. P => v A program yields a value. Intuitively, start with an empty environment, evaluate definitions to get a new environment, and then evaluate the expression of the program.

Revisiting Evaluation Rules 1 and 2 1. Env |- v => v (no real

Revisiting Evaluation Rules 1 and 2 1. Env |- v => v (no real change) 2. Env |- ( e 1 e 2 e 3 ) => v if: (a) Env |- e 1 => op (b) Env |- e 2 => v 1 (c) Env |- e 3 => v 2 (d) Env |- v 1 op v 2 = v

Evaluation Rule 3: 3 a. Env |- (if e 1 e 2 e 3)

Evaluation Rule 3: 3 a. Env |- (if e 1 e 2 e 3) => v if: (a) Env |- e 1 => #f (b) Env |- e 3 => v 3 b. Env |- (if e 1 e 2 e 3) => v if: (a) Env |- e 1 => v’ and v’ is not #f (b) Env |- e 3 => v

Evaluation Rule 4: Env |- (e 1 e 2 e 3 … en) =>

Evaluation Rule 4: Env |- (e 1 e 2 e 3 … en) => v if: (a) Env |- e 1 => (lambda (x 2 x 3… xn) e) (b) Env |- e 2 => v 2 , …, Env |- en => vn (c) e[v 2/x 2 , … , vn/xn] = e’ (d) Env |- e’ => v

Eval. Rule 5: (new rule -- variables) Env |- x => v if x

Eval. Rule 5: (new rule -- variables) Env |- x => v if x is mapped to v by Env. That is, Env has a binding x: =v in it.

Eval. Rule 6: (new rule -- define) To prove Env |- (define x e)

Eval. Rule 6: (new rule -- define) To prove Env |- (define x e) => Env{x: =v} show Env |- e => v That is, first evaluate e in the current environment Env to yield a value v. Then bind v to the variable x to yield a new environment (for subsequent evaluation. )

Eval. Rule 7: (new rule -- program) To prove d 1 … dn e

Eval. Rule 7: (new rule -- program) To prove d 1 … dn e => v show: (a) {} |- d 1 => Env 1 … Envn-1 |- dn => Envn (b) Envn |- e => v That is, start with an empty environment, evaluate the definitions (in order), take the final environment and use it to evaluate e.

Putting it all together: Let’s prove that evaluating the program P below yields 6.

Putting it all together: Let’s prove that evaluating the program P below yields 6. (define z 1) (define w (* 3 z)) (define f (lambda (n) (if (< n 2) 1 (+ n (f (- n 1)))))) (f w)

First Define We start off in an empty environment ({}) and show: {} |-

First Define We start off in an empty environment ({}) and show: {} |- (define z 1)=> {z: = 1} (by rule 6) because {} |- 1 => 1 (by rule 1)

Second Define {z: =1} |- (define w (* z 3)) => {z: =1, w:

Second Define {z: =1} |- (define w (* z 3)) => {z: =1, w: =3} (6) because {z: =1} |- (* z 3) => 3 (2): (a) {z: =1} |- * => * (1) (b) {z: =1} |- z => 1 (5 -- note lookup) (c) {z: =1} |- 3 => 3 (1) (d) 1*3 = 3 (by math) So our 2 nd environment is {z: =1, w: =3}.

Third Define: This one is easy like the first one, because the expression is

Third Define: This one is easy like the first one, because the expression is already a value (a lambda): {z: =1, w: =3} |- (define f (lambda (n) …)) => Env where Env is {z: =1, w: =3, f: =(lambda (n) …)} by the fact that {z: =1, w: =3} |- (lambda (n) …) => (lambda (n) …) (rule 1). Finally, we must show that Env |- (f w) => 6.

Final Expression: Env |- (f w) => 6 (4) (a) Env |- f =>

Final Expression: Env |- (f w) => 6 (4) (a) Env |- f => (lambda (n) (if (< n 2) 2 (+ n (f(- n 1))))) (5 since f maps to (lambda (n) …) in Env) (b) Env |- w => 3 (5 since w maps to 3 in Env) (c) (if (< n 2) 1 (+ n (f (- n 1))))[3/n] = (if (< 3 2) 1 (+ 3 (f (- 3 1))))(subst)

Continuing. . . (d) Env |(if (< 3 2) 1 (+ 3 (f (-

Continuing. . . (d) Env |(if (< 3 2) 1 (+ 3 (f (- 3 1)))) =>6(3 a) (a) Env |- (< 3 2) => #f (2 & obvious) (b) Env |- (+ 3 (f (- 3 1))) => 6 (2) +, 3 obvious, need to show Env |- (f (- 3 1)) => 3

And on. . . Env |- (f (- 3 1)) => 3 (by 4)

And on. . . Env |- (f (- 3 1)) => 3 (by 4) (a) Env |- f => (lambda (n) (if …)) (by 5) (b) Env |- (- 3 1) => 2 (by 2 & obvious) (c) (if …)[2/n] = (if (< 2 2) 1 (+ 2 (f (- 2 1)))) (by subst) (d) Env |- (if (< 2 2) 1 (+ 2 (f (- 2 1)))) => 3 (by 3 a)

And on. . . (a) Env |- (< 2 2) => #f (2 &

And on. . . (a) Env |- (< 2 2) => #f (2 & obvious) (b) Env |- (+ 2 (f (- 2 1))) => 3 (2) +, 2 obvious, need to show Env |- (f (- 2 1)) => 1 (4) (a) Env |- f => (lambda (n) (if …)) (5) (b) Env |- (- 2 1) => 1 (2 & obvious) (c) (if …)[1/n] = (by subst. ) (if (< 1 2) 1 (+ 1 (f (- 1 1)))) (d) Env |- (if (< 1 2) 1 …) => 1 (3 b)

And on. . . Env |- (if (< 1 2) 1 …) => 1

And on. . . Env |- (if (< 1 2) 1 …) => 1 (3 b) (a) Env |- (< 1 2) => #t (2) (b) Env |- 1 => 1 (1) Therefore, adding up the last umpteen slides, the program evaluates to 6. Note: though many steps were skipped, they were obvious. When in doubt, do all of the steps.