Consider the following Scheme code as an example:
(define (foo a) (lambda (b) (vector foo a b)))
Within the lambda expression, foo
is a top-level variable,
a
is a lexically captured variable, and b
is a local
variable.
Another way to refer to a
and b
is to say that a
is
a “free” variable, since it is not defined within the lambda, and
b
is a “bound” variable. These are the terms used in the
lambda calculus, a mathematical notation for describing functions.
The lambda calculus is useful because it is a language in which to
reason precisely about functions and variables. It is especially good
at describing scope relations, and it is for that reason that we mention
it here.
Guile allocates all variables on the stack. When a lexically enclosed procedure with free variables—a closure—is created, it copies those variables into its free variable vector. References to free variables are then redirected through the free variable vector.
If a variable is ever set!
, however, it will need to be
heap-allocated instead of stack-allocated, so that different closures
that capture the same variable can see the same value. Also, this
allows continuations to capture a reference to the variable, instead
of to its value at one point in time. For these reasons, set!
variables are allocated in “boxes”—actually, in variable cells.
See Variables, for more information. References to set!
variables are indirected through the boxes.
Thus perhaps counterintuitively, what would seem “closer to the
metal”, viz set!
, actually forces an extra memory allocation and
indirection. Sometimes Guile’s optimizer can remove this allocation,
but not always.
Going back to our example, b
may be allocated on the stack, as
it is never mutated.
a
may also be allocated on the stack, as it too is never
mutated. Within the enclosed lambda, its value will be copied into
(and referenced from) the free variables vector.
foo
is a top-level variable, because foo
is not
lexically bound in this example.