Currying in ML

Times

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).

Twice

val twice = times 2;
When times is called, it gets a new activation record. I will write that activation record like this:
α: <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 (α) 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 | α]
The returned closure has a nonlocal referencing environment α, which is the activation record of the function doing the returning. So α 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.

Compose

val compose = fn (f,g) => (fn x => f(g(x)));
The resulting closure:
compose = [fn (f,g) => (fn x => f(g(x))) | global]

Fourtimes

val fourtimes = compose(twice, twice);
This declaration invokes compose, generating this activation record:
β: <compose | global | f=twice, g=twice>
Compose then returns a value to be bound to fourtimes:
fourtimes = [fn x => f(g(x)) | β]
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:
γ: <fourtimes | β | 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 β to be twice. So we invoke g=twice in this new activation record:
δ: <twice | α | b=5>
The body of twice is a*b, where b is local (value 5), and a is 1-level nonlocal, therefore in α (value 2). So the value 2*5=10 is returned.

We are back in γ, and the body of fourtimes now applies f to the 10 that it just calculated. It finds f one level nonlocal, in β; f is twice. Calling f=twice builds this activation record:

ε: <twice | α | b=10>
Once more, twice evaluates a*b where a is the nonlocal value 2 (in α) and b is the local value 10 (in ε). This call returns 20 back to γ, which returns the 20 back to its caller, the main program.

Curry

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]

CurryPlus

val curryPlus = curry plus;
Function curry is called in activation record γ:
γ: <curry | global | f=plus>
This invocation returns the following closure, bound to curryPlus:
curryPlus = [fn a => (fn b => f(a,b)) | γ]

Successor

val successor = curryPlus 1;
This declaration invokes curryPlus, generating this activation record:
ι: <curryPlus | γ | a=1>
This call returns the following closure, bound to successor:
successor = [fn b => f(a,b) | ι]
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 | ι | b=3>
Local value: b=3. One level nonlocal (ι): a=1. Two levels nonlocal (γ): f=plus. The body of successor needs to invoke f(a,b), which is resolved as plus(1,3), which yields 4.