wiki

Closure

A function value together with the environment it was defined in: the values of the free variables its body refers to. Closures are what make functions first-class in a lexically scoped language, and closure conversion is the compiler pass that turns them into ordinary data: a code pointer and a record of captured values.

In the variables and are free. The function means something only together with their values at the point where it was created, and a closure is that pair: code and environment.

Counters, capture by value and by reference, and closure conversion by hand.

(* make_counter returns a function that closes over a fresh reference. *)
let make_counter () =
let n = ref 0 in
fun () -> incr n; !n
(* A for loop binds a new i each iteration, and each closure keeps the
value it saw. A shared reference is one location, and every closure
sees its final contents. *)
let by_value = List.init 3 (fun i -> fun () -> i)
let by_reference =
let r = ref 0 in
List.init 3 (fun i -> r := i; fun () -> !r)
(* Closure conversion by hand: the free variables become an explicit
environment passed to code that has none of its own. *)
type env = { k : int; m : int }
let code env x = (env.k * x) + env.m
let make_affine k m = (code, { k; m })
let apply (f, env) x = f env x

Running it.

c1 (), c1 (), c2 () -> 1, 2, 1
by_value closures -> 0, 1, 2
by_reference closures -> 2, 2, 2
apply (make_affine 3 4) 5 -> 19

Each call to make_counter creates a new reference and a new closure over it, so c1 and c2 count independently. The loop's closures each capture the i of their own iteration. The last three capture one reference between them, and all read its final contents. Closures capture variables, and OCaml variables are immutable, so the surprise in other languages, loop closures all seeing the last index, happens here only through an explicit ref.

Closure conversion makes the environment explicit. Each function with free variables becomes a closed piece of code taking the environment as an extra argument, paired with a record of the captured values:

and application unpacks the pair, as apply does above. After it, no function refers to a variable it does not bind, and functions can be compiled to plain code. The environment is heap-allocated, which is why closures cost an allocation and why capturing a large structure keeps it alive.

Landin introduced closures to give an evaluation model for Lisp and ALGOL-like languages with first-class functions.[1] Lambda lifting is the alternative to closure conversion: instead of an environment record, the free variables become extra parameters, which works only when every call site can supply them.

see also

further reading

  1. [1]P. J. Landin, “The mechanical evaluation of expressions”, The Computer Journal 6 (1964).
  2. [2]A. W. Appel, Compiling with Continuations, Cambridge University Press (1992).
  3. [3]T. Johnsson, “Lambda lifting: transforming programs to recursive equations”, FPCA (1985).