Common Lisp the Language, 2nd Edition
Next: Type Specifiers
Up: Common Lisp the Language
Previous: OverlapInclusion,
and
In describing various features of the Common Lisp language, the notions of scope and extent are frequently useful. These notions arise when some object or construct must be referred to from some distant part of a program. Scope refers to the spatial or textual region of the program within which references may occur. Extent refers to the interval of time during which references may occur.
As a simple example, consider this program:
(defun copy-cell (x) (cons (car x) (cdr x)))
The scope of the parameter named x
is the body of the
defun
form. There is no way to refer to this parameter from
any other place but within the body of the defun
.
Similarly, the extent of the parameter x
(for any
particular call to copy-cell
) is the interval from the time
the function is invoked to the time it is exited. (In the general case,
the extent of a parameter may last beyond the time of function exit, but
that cannot occur in this simple case.)
Within Common Lisp, a referenceable entity is established by
the execution of some language construct, and the scope and extent of
the entity are described relative to the construct and the time (during
execution of the construct) at which the entity is established. For the
purposes of this discussion, the term ``entity’’ refers not only to
Common Lisp data objects, such as symbols and conses, but also to
variable bindings (both ordinary and special), catchers, and
go
targets. It is important to distinguish between an
entity and a name for the entity. In a function definition such as
(defun foo (x y) (* x (+ y 1)))
there is a single name, x
, used to refer to the first
parameter of the procedure whenever it is invoked; however, a new
binding is established on every invocation. A binding is a
particular parameter instance. The value of a reference to the name
x
depends not only on the scope within which it occurs (the
one in the body of foo
in the example occurs in the scope
of the function definition’s parameters) but also on the particular
binding or instance involved. (In this case, it depends on the
invocation during which the reference is made). More complicated
examples appear at the end of this chapter.
There are a few kinds of scope and extent that are particularly useful in describing Common Lisp:
Lexical scope. Here references to the established entity can occur only within certain program portions that are lexically (that is, textually) contained within the establishing construct. Typically the construct will have a part designated the body, and the scope of all entities established will be (or include) the body.
Example: the names of parameters to a function normally are lexically scoped.
Indefinite scope. References may occur anywhere, in any program.
Dynamic extent. References may occur at any time in the interval between establishment of the entity and the explicit disestablishment of the entity. As a rule, the entity is disestablished when execution of the establishing construct completes or is otherwise terminated. Therefore entities with dynamic extent obey a stack-like discipline, paralleling the nested executions of their establishing constructs.
Example: the with-open-file
construct opens a connection
to a file and creates a stream object to represent the connection. The
stream object has indefinite extent, but the connection to the open file
has dynamic extent: when control exits the with-open-file
construct, either normally or abnormally, the stream is automatically
closed.
Example: the binding of a ``special’’ variable has dynamic extent.
Indefinite extent. The entity continues to exist as long as the possibility of reference remains. (An implementation is free to destroy the entity if it can prove that reference to it is no longer possible. Garbage collection strategies implicitly employ such proofs.)
Example: most Common Lisp data objects have indefinite extent.
Example: the bindings of lexically scoped parameters of a function have indefinite extent. (By contrast, in Algol the bindings of lexically scoped parameters of a procedure have dynamic extent.) The function definition
(defun compose (f g)
#'(lambda (x)
(funcall f (funcall g x))))
when given two arguments, immediately returns a function as its
value. The parameter bindings for f
and g
do
not disappear because the returned function, when called, could still
refer to those bindings. Therefore
(funcall (compose #'sqrt #'abs) -9.0)
produces the value 3.0
. (An analogous procedure would
not necessarily work correctly in typical Algol implementations or, for
that matter, in most Lisp dialects.)
In addition to the above terms, it is convenient to define dynamic scope to mean indefinite scope and dynamic extent. Thus we speak of ``special’’ variables as having dynamic scope, or being dynamically scoped, because they have indefinite scope and dynamic extent: a special variable can be referred to anywhere as long as its binding is currently in effect.
The term ``dynamic scope’’ is a misnomer. Nevertheless it is both
traditional and useful.
The above definitions do not take into account the possibility of shadowing. Remote reference of entities is accomplished by using names of one kind or another. If two entities have the same name, then the second may shadow the first, in which case an occurrence of the name will refer to the second and cannot refer to the first.
In the case of lexical scope, if two constructs that establish entities with the same name are textually nested, then references within the inner construct refer to the entity established by the inner one; the inner one shadows the outer one. Outside the inner construct but inside the outer one, references refer to the entity established by the outer construct. For example:
(defun test (x z)
(let ((z (* x 2))) (print z))
z)
The binding of the variable z
by the let
construct shadows the parameter binding for the function
test
. The reference to the variable z
in the
print
form refers to the let
binding. The
reference to z
at the end of the function refers to the
parameter named z
.
In the case of dynamic extent, if the time intervals of two entities overlap, then one interval will necessarily be nested within the other one. This is a property of the design of Common Lisp.
Implementation note: Behind the assertion that dynamic extents nest properly is the assumption that there is only a single program or process. Common Lisp does not address the problems of multiprogramming (timesharing) or multiprocessing (more than one active processor) within a single Lisp environment. The documentation for implementations that extend Common Lisp for multiprogramming or multiprocessing should be very clear on what modifications are induced by such extensions to the rules of extent and scope. Implementors should note that Common Lisp has been carefully designed to allow special variables to be implemented using either the ``deep binding’’ technique or the ``shallow binding’’ technique, but the two techniques have different semantic and performance implications for multiprogramming and multiprocessing.
A reference by name to an entity with dynamic extent will always refer to the entity of that name that has been most recently established that has not yet been disestablished. For example:
(defun fun1 (x)
(catch 'trap (+ 3 (fun2 x))))
(defun fun2 (y)
(catch 'trap (* 5 (fun3 y))))
(defun fun3 (z)
(throw 'trap z))
Consider the call (fun1 7)
. The result will be
10
. At the time the throw
is executed, there
are two outstanding catchers with the name trap
: one
established within procedure fun1
, and the other within
procedure fun2
. The latter is the more recent, and so the
value 7
is returned from the catch
form in
fun2
. Viewed from within fun3
, the
catch
in fun2
shadows the one in
fun1
. Had fun2
been defined as
(defun fun2 (y)
(catch 'snare (* 5 (fun3 y))))
then the two catchers would have different names, and therefore the
one in fun1
would not be shadowed. The result would then
have been 7
.
As a rule, this book simply speaks of the scope or extent of an entity; the possibility of shadowing is left implicit.
The important scope and extent rules in Common Lisp follow:
dynamic-extent
declaration also have lexical scope and indefinite extent, but objects
that are the values of such bindings may have dynamic extent. (The
declaration is the programmer’s guarantee that the program will behave
correctly even if certain of the data objects have only dynamic extent
rather than the usual indefinite extent.)symbol-macrolet
have lexical scope and indefinite
extent.special
have
dynamic scope (indefinite scope and dynamic extent).flet
and labels
have lexical scope and
indefinite extent.dynamic-extent
declaration also have lexical scope and
indefinite extent, but function objects that are the values of such
bindings may have dynamic extent.macrolet
have lexical scope and indefinite extent.catch
or
unwind-protect
special form has dynamic scope.block
construct has
lexical scope and dynamic extent. (Such exit points are also established
by do
, prog
, and other iteration
constructs.)go
targets established by a tagbody
,
named by the tags in the tagbody
, and referred to by
go
have lexical scope and dynamic extent. (Such
go
targets may also appear as tags in the bodies of
do
, prog
, and other iteration
constructs.)nil
and pi
have
indefinite scope and indefinite extent.The rules of lexical scoping imply that lambda-expressions appearing
in the function
construct will, in general, result in
``closures’’ over those non-special variables visible to the
lambda-expression. That is, the function represented by a
lambda-expression may refer to any lexically apparent non-special
variable and get the correct value, even if the construct that
established the binding has been exited in the course of execution. The
compose
example shown earlier in this chapter provides one
illustration of this. The rules also imply that special variable
bindings are not ``closed over’’ as they may be in certain other
dialects of Lisp.
Constructs that use lexical scope effectively generate a new name for each established entity on each execution. Therefore dynamic shadowing cannot occur (though lexical shadowing may). This is of particular importance when dynamic extent is involved. For example:
(defun contorted-example (f g x)
(if (= x 0)
(funcall f)
(block here
(+ 5 (contorted-example g
#'(lambda ()
(return-from here 4))
(- x 1))))))
Consider the call (contorted-example nil nil 2)
. This
produces the result 4
. During the course of execution,
there are three calls on contorted-example
, interleaved
with two establishments of blocks:
(contorted-example nil nil 2)
(block here ...)
(contorted-example nil #'(lambda () (return-from here 4)) 1)
(block here ...)
(contorted-example #'(lambda () (return-from here 4))
#'(lambda () (return-from here 4))
0)
(funcall f)
where f => #'(lambda () (return-from here 4))
(return-from here 4)
At the time the funcall
is executed there are two
block
exit points outstanding, each apparently named
here
. In the trace above, these exit points are
distinguished for expository purposes by subscripts. The
return-from
form executed as a result of the
funcall
operation refers to the outer outstanding
exit point (here
), not the inner one
(
here
). This is
a consequence of the rules of lexical scoping: it refers to that exit
point textually visible at the point of execution of the
function
construct (here abbreviated by the #'
syntax) that resulted in creation of the function object actually
invoked by the funcall
.
If, in this example, one were to change the form
(funcall f)
to (funcall g)
, then the value of
the call (contorted-example nil nil 2)
would be
9
. The value would change because the funcall
would cause the execution of (return-from here
4)
, thereby causing a
return from the inner exit point (here
). When that occurs, the value
4
is returned from the middle invocation of
contorted-example
, 5
is added to that to get
9
, and that value is returned from the outer block and the
outermost call to contorted-example
. The point is that the
choice of exit point returned from has nothing to do with its being
innermost or outermost; rather, it depends on the lexical scoping
information that is effectively packaged up with a lambda-expression
when the function
construct is executed.
This function contorted-example
works only because the
function named by f
is invoked during the extent of the
exit point. Block exit points are like non-special variable bindings in
having lexical scope, but they differ in having dynamic extent rather
than indefinite extent. Once the flow of execution has left the block
construct, the exit point is disestablished. For example:
(defun illegal-example ()
(let ((y (block here #'(lambda (z) (return-from here z)))))
(if (numberp y) y (funcall y 5))))
One might expect the call (illegal-example)
to produce
5
by the following incorrect reasoning: the
let
statement binds the variable y
to the
value of the block
construct; this value is a function
resulting from the lambda-expression. Because y
is not a
number, it is invoked on the value 5
. The
return-from
should then return this value from the exit
point named here
, thereby exiting from the block
again and giving y
the value 5
which,
being a number, is then returned as the value of the call to
illegal-example
.
The argument fails only because exit points are defined in Common
Lisp to have dynamic extent. The argument is correct up to the execution
of the return-from
. The execution of the
return-from
is an error, however, not because it
cannot refer to the exit point, but because it does correctly refer to
an exit point and that exit point has been disestablished.
Next: Type Specifiers
Up: Common Lisp the Language
Previous: OverlapInclusion,
and
AI.Repository@cs.cmu.edu