slides

advertisement
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:
num
op ::= + | - | * | /
(expressions) e ::= num | op | (e1 … 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 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 illformed. (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) |
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 => #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 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 (3a)
– otherwise, evaluate second argument (3b)
Scheme-2
e ::= num | op | (e1 … en ) | (if e1 e2 e3) |
bool | fn | x
v ::= num | op | bool | fn
fn ::= (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
(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 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)
Body of outer function
Following rule 4:
(a) (lambda (x) (lambda (x) x)) =>
(lambda (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] = (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] = +
3[#t/y]= 3
2[#t/y]= 2
(by s2)
(by s1)
(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 Action
s6. 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] = +
y[3/y]= 3
x[3/y]= x
(by s2)
(by s6 -- notice y = y)
(by s6 -- notice xy)
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-3
Expressions and values are as before...
(programs)
(defines)
p ::= d1 … 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.
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 2
1. 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.
Download