Consider a program that calls a function foo. How does the caller communicate with the callee? In ML, and in the languages we have looked at to date, communication only happens via the arguments. This is an essential part of modularity.
Consider the following code:
let val x:int = 3 fun foo(u: int, v: int): int = u + x val x:int = 5 in foo(4, 42) end
In this code, foo adds 3 to its first argument. The result is 7. Recall the static scoping rule (substitution model, and evaluator): to find the binding occurrence for a variable reference, just look up, textually (sometimes called lexical scoping).
How can the caller of foo change foo's behavior? Only by changing its arguments. Corollary: if foo has no arguments, the called can't ever change its behavior.
Suppose that you want to change foo's behavior without changing its arguments? Example: print-depth, or similar debugging information.
In a statically scoped language, you can't do this. In fact, the language is designed not to allow this!
Here is the relevant part of the evaluator (this is not precisely how the code actually looks):
evaluateApply (e1: exp, e2: exp, encrt: env): value * typ = let val (fc, fct) = evaluate(e1, encrt) in case fc of SpecForm_v(name) => specialForm(name, e2, encrt) | Predef_v (name) => predefined (name, evaluate (e2, encrt)) | Fn_v(fal, clenv, body, name) => let val en = clenv in evaluate(body, addbindings(fal,evaluate(e2,encrt), en)) end
Note that the environment in which we were called, encrt, is not the environment we use to evaluate the function body. Once we are done evaluating e2, encrt is dropped on the floor.
Instead, we use the environment clenv from the closure (result of evaluating e1). This is worth going over in detail!
Consider evaluating (fn(x) => x + 1) (2+3) in some env E1.
Consider evaluating (let val z:int = 1 in fn(x) => x + z end) (2+3) in some env E1.
What if we want to change our language, so that
let val x:int = 3 fun foo(u: int, v: int): int = u + x val x:int = 5 in foo(4, 42) endevaluates to 9 instead of 7? I.e., to look up the value of a variable, you find the binding in the environment in which you are called, rather than lexically? This is called dynamic scoping, and historically was widely used in the 1960's.
How do we change this?
evaluateApply (e1: exp, e2: exp, encrt: env): value * typ = let val (fc, fct) = evaluate(e1, encrt) in case fc of SpecForm_v(name) => specialForm(name, e2, encrt) | Predef_v (name) => predefined (name, evaluate (e2, encrt)) | Fn_v(fal, clenv, body, name) => let val en = encrt (* only change *) in evaluate(body, addbindings(fal,evaluate(e2,encrt), en)) end
Important lesson: small changes in the evaluator lead to large changes in the language.
Prelim #2 preview: here is a small change to the evaluator; what does it now do? What is the new value of the following expression?
Note that with dynamic scoping there is no real point to closures, since their environment clenv is discarded.
In fact, we can make our evaluator more general:
evaluateApply (e1: exp, e2: exp, encrt: env): value * typ = ( let val (fc, fct) = evaluate(e1, encrt) in case fc of SpecForm_v(name) => specialForm(name, e2, encrt) | Predef_v (name) => predefined (name, evaluate (e2, encrt)) | Fn_v(fal, clenv, body, name) => let val en = case SCOPING of STATIC => clenv | DYNAMIC => encrt in evaluate(body, addbindings(fal,evaluate(e2,encrt), en)) end
OK, what else can we do? Consider how we go about defining functions. When we say something like
fun sqr(z:int):int = z*z
our intent is that everywhere we write sqr(WHATEVER) we might as well have written WHATEVER*WHATEVER.
This concept is known as referential transparency, and is the key to designing large programs. Functional languages come pretty close to doing this.
However, they don't quite do it. For example consider the following example:
let val x:int = 3 fun foo(u: int, v: int): int = u + x in foo(4, raise Fail "banana") endTo solve this issues we will introduce "near" ML languages that use a lazy evaluation instead of an eager evaluation (refer to next lecture).
CS312 © 2002 Cornell University Computer Science |