val times = fn a => (fn b => a*b);This declaration introduces
times.
I will write the value as a closure:
times = [fn a => (fn b => a*b) | global]The bracketed notation has two parts: the code for the function (it is actually compiled) and the nonlocal referencing environment in which it is to be run (in this case global).
val twice = times 2;When
times is called, it gets a new activation record. I will write that
activation record like this:
alpha: <times | global | a=2>The first part names the compiled code that is running. The second part is the nonlocal referencing environment in which that code runs. The third part is the binding of the formal parameters. I label the entire activation record with a greek name (
alpha) so I can
refer to it later.
The body of times needs to return a
function. It does so, returning this closure to be bound to twice:
twice = [fn b => a*b | alpha]The returned closure has a nonlocal referencing environment
alpha, which is
the activation record of the function doing
the returning.
So alpha must not be deallocated when times returns.
We will assume a garbage collector will eventually deallocate unused activation
records; we will never explicitly deallocate them.
val compose = fn (f,g) => (fn x => f(g(x)));The resulting closure:
compose = [fn (f,g) => (fn x => f(g(x))) | global]
val fourtimes = compose(twice, twice);This declaration invokes
compose,
generating this activation record:
beta: <compose | global | f=twice, g=twice>
Compose then returns a value to be bound to fourtimes:
fourtimes = [fn x => f(g(x)) | beta]Both
f and g are compiled as references to 1-level nonlocal
values.
We now invoke fourtimes:
fourtimes 5;This invocation builds a new activation record:
gamma: <fourtimes | beta | x=5>The body of
fourtimes first needs to evaluate g(x).
The compiled code for fourtimes knows that
g is one level nonlocal; it is found in beta to be twice.
So we invoke g=twice in this new activation record:
delta: <twice | alpha | b=5>The body of
twice is a*b, where b is local (value 5),
and a is 1-level nonlocal, therefore in alpha (value 2). So
the value 2*5=10 is returned.
We are back in gamma, and the body of fourtimes now applies f
to the 10 that it just calculated. It finds f one level nonlocal,
in beta; f is twice. Calling f=twice builds this
activation record:
epsilon: <twice | alpha | b=10>Once more,
twice evaluates a*b where a is the nonlocal value
2 (in alpha) and b is the local value 10 (in
epsilon). This call returns 20 back to gamma, which returns
the 20 back to its caller, the main program.
val curry = fn f => (fn a => (fn b => f(a,b)));This declaration generates a closure:
curry = [fn f => (fn a => (fn b => f(a,b))) | global]
val curryPlus = curry plus;Function
curry is called in activation record gamma:
gamma: <curry | global | f=plus>This invocation returns the following closure, bound to
curryPlus:
curryPlus = [fn a => (fn b => f(a,b)) | gamma]
val successor = curryPlus 1;This declaration invokes
curryPlus, generating this activation record:
iota: <curryPlus | gamma | a=1>This call returns the following closure, bound to
successor:
successor = [fn b => f(a,b) | iota]In this closure,
a is one level nonlocal, b is local, and f
is two levels nonlocal.
We can now invoke successor:
successor 3;We build a new activation record:
kappa: <successor | iota | b=3>Local value:
b=3. One level nonlocal (iota): a=1.
Two levels nonlocal (gamma): f=plus.
The body of successor needs to invoke f(a,b), which is resolved as
plus(1,3), which yields 4.