new mkcd := lambda a { res := lambda x { if (a < x) { res := true; } { a := a - x; res := false; } }; }; new A := mkcd(10); new B := mkcd(12); write A(11); write B(11);
> (define L (let ((a 0)) (list (lambda (x) (+ a x)) (lambda (x) (* a x)) (lambda (x) (set! a (+ a x)))))) > ((car L) 5) 5 > ((cadr L) 5) 0 > ((caddr L) 5) > ((car L) 5) 10 > ((cadr L) 5) 25Do you see what's happening here? The three functions in the list L all share the variable
a
. So
long after the let
is done executing, we have
three distinct functions all referencing the same object via
a
. Thus there's no way to make a
"local" to any one of the functions, because when one of them
changes a
, the change must be reflected in all of
them.
Here's a simple example program to walk through. We're going to use SPL but imagine it as statically/lexically scoped, and we'll go through how an implementation using frames might work.
new x := 3; new y := 5; new f := lambda x { res := x*y; }; write f(5); write f(2*x);I've annotated the abstract syntax tree with some hypothetical addresses for various nodes to faciliate the discussion.
In class, of course, we
actually drew the frames and closures and talked about the
calls to eval. Now, by the way, eval
takes two
arguements: a pointer to a node in an Abstract Syntax Tree,
and the referencing environment to be used, which
is simply a pointer to a Frame.
In this example the problem that brought us to this point ---
lexical scoping with first-class functions --- doesn't crop
up. We don't have any functions-returning-functions here.
So ...
Consider this slightly more complicated example, in which
function mkf
returns a function.
new x := 3; new z := 5; new mkf := lambda y { res := lambda x { res := x*y; }; }; new f := mkf(z); write f(2*x);I've annotated the abstract syntax tree with some hypothetical addresses for various nodes to faciliate the discussion.