So let's start section with some basic
adminstrative information. Students can rate us at http://www.engineering.cornell.edu/taeval/survey.cfm
So we need to talk about the environment
model. The enviroment model is a formal
method for reasoning about programs. We
want to use the envrinment model for several reasons. The evironment model:
1) mirrors real interpreter desgin
2) formalizes fun cleanly
3) simplifies reasoning about refs
We will try to de-emphasize the imperative
nature of the environment model. This means we need to try to stay away from
presenting environment diagrams as something that we construct
imperatively. Traditionally we have
said,
"val
x=1+1"
means we evaluate 1+1 then draw a box and
put x: 3 in it. This semester we will
have a more functional emphasis. The
notation that we will use will hopefully convey this to the students. Unfortunately, the imperative way of
building environment diagrams is part of what makes them simple to work with,
hence useful. I guess the moral here is
to separate the construction and the idea behind it.
Jeff Vinocur's notes on the environment
model provide a great overview in the imperative style of drawing environments:
http://www.cs.cornell.edu/courses/cs312/2002sp/handouts/environment-model.html
To
look at things more functionally, I would like to introduce a few definitions:
eval: expression * environment -> value
bind: enviroment * variable * value ->
environment
These guys are metafunctions (not ML!) and don't have time to offer full definitions. However their behavior follows naturally from the SML's language semantics.
I
will use [] to represent a frame and [[]] to represent the top-level
environment's double frame. So
[[]]<--[x:3]<--[y:2]<--[x:1]
is the environment where y is bound to 2
and x bound to 1 shadows x bound to 3.
I'm going to use {} and <> as
parentheses. This is intended to make parentheses matching visually less
painful, and not to convey any extra meaning.
Very
simple examples:
1)
evaluate: let val x = 40+2 in x end
- eval(let val x = 40+2 in x end, [[]])
- eval(x, bind{ [[]], x, eval(40+2, [[]])
} )
(*
same as evaluating the in clause in
* the environment created by
binding
* x
to the value found by evaluating
*
40+2 in the environment [] *)
- eval(x, bind{ [[]], x, 42} )
- eval(x, [[]]<--[x: 42])
- 42 (* we evaluate x by looking it up in
the current environment*)
2)
evaluate let val x = (
let val x = 13 in 300 + x end )
in
x - 1
end
-
eval( let val x = (
let val x = 13 in 300 + x end )
in
x - 1
end
, [[]])
- eval( x-1, bind{ [[]], x, eval <let
val x = 13 in 300 + x end, [[]]> })
- eval( x-1, bind{ [[]], x, eval <300 +
x, bind{ [[]], x, eval(13, [[]])}> })
- eval( x-1, bind{ [[]], x, eval <300 +
x, bind{ [[]], x, 13}>})
- eval( x-1, bind{ [[]], x, eval <300 +
x, [[]]<--[x: 13]>})
- eval( x-1, bind{ [[]], x, eval <300 +
13, [[]]<--[x: 12]>})
- eval( x-1, bind{ [[]], x, 313})
- eval( x-1, [[]]<--[x: 313])
- eval(313-1, [[]]<--[x: 313])
- 312
Looking above, we can only say, "the horror, the horror". To keep the paper work under control we need to scale the formalism back. However, the above was useful. In particular, it demonstrates that environments, like lists, are really not mutable, even if though it's sometimes easy to forget.
A
few more examples, omitting the steps:
3)
let
val x = 4
val f = fn a => a+x
val x = 2
in
f x
end
is
equivalent to:
let
val x = 4
in let val f = fn a => a+x
in let val x = 2
in
f x
end
end
end
Which
builds environment:
[[]]<--[x: 4]<--[f
:+]<--[x: 2]
^ | *
| |
+-------oo
p: a
b: a+x
Evaluating
f(x) in this context (the * denotes the top of the environment)
yields
f 2 = x+2 = 4+2 = 6.
Why
did we use the binding of x: 4?