Date post: | 22-Dec-2015 |
Category: |
Documents |
View: | 212 times |
Download: | 0 times |
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?
CS212 approach:
Construct a formal, mathematical model.
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:
(numbers) num (operators) op ::= + | - | * | /(expressions) e ::= num | op | (e1 … en )
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 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 for certain kinds of combinations.
If no rule applies, then the program is ill-formed. (DrScheme reports an error.)
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 (e1 e2 e3) => v show:
(a) e1 => op (e1 evaluates to an operator)
(b) e2 => v1 (e2 evaluates to a value)
(c) e3 => v2 (e3 evaluates to a value)
(d) v1 op v2 = v
(applying the operator to the values yields
the value v.)
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 ::= + | - | * | / | < | > | =bool ::= #t | #f e ::= num | op | (e1 … en ) | (if e1 e2 e3)
| boolv ::= 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 => #t, < => <
2. (e1 e2 e3) => v if:
(a) e1 => op
(b) e2 => v1
(c) e3 => v2
(d) v1 op v2 = v
Examples: (< 3 4) => #t
Evaluation Rule 3a: (if #f)To prove (if e1 e2 e3) => v show:
(a) e1 => #f
(b) e3 => v
Example: (if (< 3 1) -1 0) => 0 (by rule 3a) (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 3b (if #t)
To prove (if e1 e2 e3) => v show:
(a) e1 => v1 and v1 is not #f
(b) e2 => v
Example:(if (> 3 1) -1 0) => -1 (by rule 3a) (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 IfUnlike 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
(3a)– otherwise, evaluate second argument (3b)
Scheme-2e ::= num | op | (e1 … en ) | (if e1 e2 e3) |
bool | fn | xv ::= num | op | bool | fnfn ::= (lambda (x1 … 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,3a,3b) still apply• It’s an error to run into an unbound variable.
Eval. Rule 4: (most important)
To prove (e1 e2 e3 … en) => v show:
(a) e1 => (lambda (x2 x3… xn) e)
(b) e2 => v2, e3 => v3, …, en => vn
(c) e[v2/x2, v3/x3 ,… , vn/xn] = e’
(i.e., substitute v2,…,vn for x2,…,xn in e)
(d) e’ => v
• We’ll formally define substitution later.
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 3a) (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)
(a) ((lambda (x) (lambda (y) y)) 3) =>
(lambda (y) y) (by rule 4 & proof below)
(b) 5 => 5 (by rule 1)
(c) y[5/y] = 5 (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 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) y)) 3)
New: ((lambda (x) (lambda (x) x)) 3)
Following rule 4:
(a) (lambda (x) (lambda (x) x)) =>
(lambda (x) (lambda (x) x))
(b) 3 => 3
(c ) (lambda (x) x)[3/x] = ???
Body of outer function
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 | (e1 … en ) | (if e1 e2 e3) |
bool | x | (lambda (x1…xn) e)
We write e[v1/x1,…,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:
s1. num[v/x] = num ex: 3[#t/y]=3
s2. op[v/x] = op ex: +[#t/y]=+
s3. bool[v/x] = bool ex: #f[#t/x]=#f
Usually, just push the substitution in:s4. (e1 … en )[v/x] = (e1 [v/x] … en [v/x])
s5. (if e1 e2 e3)[v/x] =
(if e1 [v/x] e2 [v/x] e3 [v/x] )
Examples for rules s4-s5(+ 3 2)[#t/y] = (by s4)
(+[#t/y] 3[#t/y] 2[#t/y]) = (+ 3 2)
because
+[#t/y] = + (by s2)
3[#t/y]= 3 (by s1)
2[#t/y]= 2 (by s1)
(if #f 3 2)[#t/y] = (by s5)
(if #f[#t/y] 3[#t/y] 2[#t/y]) =
(if #f 3 2)
Substitution: The Real Actions6. y [v/x] = v if y and x are the same = y otherwise
s7. (lambda (x1…xn) e) [v/x] =
a. (lambda (x1…xn) e) if x is one of x1…xn .
b. (lambda (x1…xn) e[v/x]) if x is not one of
x1…xn .
Substitution Rule 7 is very important!!!
Example For Rule s6:(+ y x)[3/y] = (by s4)
(+[3/y] y[3/y] x[3/y]) =
(+ 3 x)
because
+[3/y] = + (by s2)
y[3/y]= 3 (by s6 -- notice y = y)
x[3/y]= x (by s6 -- notice xy)
Example for rule s7:(lambda (x y) (+ z y))[3/z] = (s7b) (lambda (x y) (+ z y)[3/z] ) = (s5) (lambda (x y) (+[3/z] z[3/z] y[3/z] )) = (s2) (lambda (x y) (+ z[3/z] y[3/z] )) = (s6a) (lambda (x y) (+ 3 y[3/z] )) = (s6b) (lambda (x y) (+ 3 y)) = (s2)
In the first line, rule s7b applies because the variable we’re replacing (z) does not occur as a parameter to the function.
Another Example for rule s7(lambda (x y) (+ z y))[3/y] = (s7a) (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] = (s7b) (lambda (x y) (+ z y)[3/w] ) = (s5) (lambda (x y) (+[3/w] z[3/w] y[3/w] )) = (s2) (lambda (x y) (+ z[3/w] y[3/w] )) = (s6b) (lambda (x y) (+ z y[3/w] )) = (s6b) (lambda (x y) (+ z y)) = (s2)
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 s7a)
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, 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-3Expressions and values are as before...
(programs) p ::= d1 … dn e
(defines) 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.
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) (+ x 1)))
(inc x)
We evaluate as follows:– start with an empty environment Env0 = {}– evaluate (+ 3 4) in Env0, yielding 7 and bind x
to 7 resulting in Env1 = {x:=7}– bind inc to the lambda-value resulting in Env2 =
{x:=7, inc:=(lambda (x) (+ x 1))}– evaluate (inc x) in Env2 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 we run into a free
variable while evaluating e, we look it up in Env.
2. Env1 |- d => Env2
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 21. Env |- v => v (no real change)
2. Env |- ( e1 e2 e3 ) => v if:
(a) Env |- e1 => op
(b) Env |- e2 => v1
(c) Env |- e3 => v2
(d) Env |- v1 op v2 = v
Evaluation Rule 3:
3a. Env |- (if e1 e2 e3) => v if:
(a) Env |- e1 => #f
(b) Env |- e3 => v
3b. Env |- (if e1 e2 e3) => v if:
(a) Env |- e1 => v’ and v’ is not #f
(b) Env |- e3 => v
Evaluation Rule 4:
Env |- (e1 e2 e3 … en) => v if:
(a) Env |- e1 => (lambda (x2 x3… xn) e)
(b) Env |- e2 => v2 ,…, Env |- en => vn
(c) e[v2/x2 ,… , vn/xn] = e’
(d) Env |- e’ => v
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) => 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 d1 … dn e => v show:
(a) {} |- d1 => Env1
…
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.
(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:
{} |- (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:=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 2nd environment is {z:=1,w:=3}.
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 => (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 (- 3 1)))) =>6(3a)
(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)
(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 3a)
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 (3b)
And on...
Env |- (if (< 1 2) 1 …) => 1 (3b)
(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.