george moromisato wrote:In the next version, the interpreter will support closures.
Cool! 8)
Further testing showed me I was wrong about dynamic extent:
Code: Select all
(setq a True) -> True
(block (a) a) -> Nil
(block (a) ((lambda Nil a))) -> True
The lambda expression simply matches free variables to global bindings. When no global binding exists (like my previous examples), it raises error on application.
Betelgeuse, a closure is an anonymous procedure that (1) captures the environment in which it's created and (2) uses an extension of this environment to evaluate its body. It can be modeled this way
procedure = (parameters body environment)
parameters = (variable ...)
where an environment is a stack of frames based off the global environment, ie
environment = global-environment | (frame . environment)
and a frame is a collection of bindings
frame = (binding ...)
and a binding associates a variable to a value
binding = (variable value)
For
procedure = ((variable-1 ... variable-n) body captured-environment), expression
(procedure value-1 ... value-n) evaluates to the evaluation of
body in environment
(parameter-bindings . captured-environment) where frame
parameter-bindings is
((variable-1 value-1) ... (variable-n value-n)).
Variables in a scope match its environment's top bindings (to evaluate expressions). To illustrate environments
Code: Select all
(setq a 'global-a)
(setq b 'global-b)
;mark 0
(block (b c)
(setq b 'b)
(setq c 'c)
;mark 1
(block (b)
(setq a 'new-a)
(setq b 'new-b)
(setq c 'new-c)
;mark 2
)
;mark 3
)
;mark 4
Mark 0 and 4 are in the same scope and use the global environment to evaluate expressions there (though the binding for
a has changed).
Mark 0
Environment: global-environment
Top bindings: (a 'global-a) (b 'global-b)
Mark 4
Environment: global-environment
Top bindings: (a 'new-a) (b 'global-b)
Mark 1 and 3 (same scope) use an extension of the global environment as their environment. The enclosing block form extends the global environment with local-bindings for
b and
c.
Mark 1
Environment: (frame0 . global-environment)
Top frame: frame0 = ((b 'b) (c 'c))
Top bindings: (a 'global-a) (b 'b) (c 'c)
Mark 3
Environment: (frame0 . global-environment)
Top frame: frame0 = ((b 'b) (c 'new-c))
Top bindings: (a 'new-a) (b 'b) (c 'new-c)
Mark 2 uses a further extension as its environment. The enclosing block extends the enclosing enviroment with a local binding for
b. The block's body changes bindings for
a and
c.
Mark 2
Enviroment: (frame1 frame0 . global-environment)
Top frame: frame1 = ((b 'new-b))
Top bindings: (a 'new-a) (b 'new-b) (c 'new-c)
While this can be convenient, I'm beginning to see careful use of some global variables can allow me to write the code I was after.