Chapter 8 - Lifetime and Visibility

In this chapter we'll see how lifetime and visibility affect the values of Lisp variables during execution. This is pretty much like local and global variables in other languages, but Lisp's special variables change things. This chapter also sets the stage for understanding that lifetime and visibility aren't just for variables.

Everything in Lisp has both lifetime and visibility

Every object in Lisp has both lifetime and visibility. We'll see why this is important in the following sections.

Lifetime: Creation, existence, then destruction

An object's lifetime is the period between its creation and destruction. Some objects have fleeting lifetimes, limited to the form in which they appear. Other objects are created as soon as the program begins running, and are not destroyed until the program finishes. And others enter and leave existence according to still other rules.

Visibility: To see and to be seen by

An object is either visible or not visible at a particular point in your program. Sometimes visibility is controlled by the execution path of the program. But for most objects in Common Lisp, visibility is determined by the textual arrangement of your program; this is good, because you can reason about visibility just by reading a program, without having to first reason about the program's control flow.

The technical names: Extent and Scope

When you read language specifications for Common Lisp, you'll see the technical terms extent and scope used in place of lifetime and visibility. I wanted to introduce these concepts first using the non-technical terms because I believe them to be more evocative. But you should get used to reading about extent and scope. Remember:

lifetime is to extent
as
visibility is to scope

Really easy cases: top-level defining forms

Top-level defining forms are easy. The objects so defined have indefinite extent and scope. This is a fancy way of saying that objects defined by top level forms "always" exist and are visible (or at least potentially accessible, as I'll explain shortly) everywhere in the program.

Practically, what this means is that every object defined by a top level form exists for as long as the program runs. The object comes into existence when the top level form is evaluated. If the form was compiled into a file, then the object created by the form comes into existence when the compiled file is loaded.

An object having indefinite scope is visible everywhere in your program. It doesn't matter whether the object was created in a different source file or at a different time -- even if it was created after you define a function that references the top level object (although some Lisp compilers will issue a warning when you do this, the code will always behave properly if you evaluate the object's defining form before evaluating the function that references the object).

If you're familiar with the concept of lexical scope as it applies to programming languages, you're probably confused by the notion of indefinite scope. If I introduce an object whose name shadows the name of an object in an outer scope, then that outer object is "not visible" in the inner scope. And you're right, up to a point.

Lisp makes a very clear and explicit distinction between an object and its name. We say that an object is bound to a name, or that a (named) binding is established for an object. And it is very true that a binding in an inner lexical scope may shadow a binding in an outer scope. However, the scope of the outer object extends into the inner scope, even though it is inaccessible via its shadowed binding. This is an important distinction, because an object may have more than one binding, and the object must remain accessible via any binding which has not been lexically shadowed.

Scope and extent of parameters and LET variables

Objects bound by function parameters and LET forms have lexical scope. Their bindings are visible beginning at a certain textual location in the defining form and continuing through the textual end of the defining form. Any reference to a textually identical name from outside of the defining form must refer to a different binding. A nested defining form may declare a binding that shadows an enclosing binding to a textually identical name.

This is a slightly more rigorous restatement of concepts introduced in Chapter 3, Lesson 6. If you need to refresh your memory, this would be a good time to go back and review the examples in that short passage.

Slightly trickier: special variables

Special variables (also known by the more technically correct term dynamic variables) have dynamic scope. This means that a binding is created for a special variable as a result of executing some form in your program. The scope of the dynamic binding extends into any form called (directly or indirectly) by the form which established the dynamic binding.

The extent of a special variable lasts indefinitely, until the form that created the dynamic binding is no longer a site of active program execution -- in other words, until the defining form (and all of the forms called by it) finishes executing. If the dynamic binding is created by a top level form, the extent is the same as described previously for top level defining forms.

? (defparameter *my-special-variable* 17)
*MY-SPECIAL-VARIABLE*
? (defun show-my-special ()
    (declare (special *my-special-variable*))
    (print *my-special-variable*)
    nil)
SHOW-MY-SPECIAL
? (defun do-something-else ()
    (show-my-special))
DO-SOMETHING-ELSE
? (defun dynamically-shadow-my-special ()
    (let ((*my-special-variable* 8))
      (do-something-else))
    (show-my-special))
DYNAMICALLY-SHADOW-MY-SPECIAL
? (dynamically-shadow-my-special)

8
17
NIL

When reading the above, pay special attention to DO-SOMETHING-ELSE -- this calls SHOW-MY-SPECIAL. SHOW-MY-SPECIAL would normally see the lexical value of *MY-SPECIAL-VARIABLE* -- 17 -- except for the declaration which says that *MY-SPECIAL-VARIABLE* is a special variable.

DYNAMICALLY-SHADOW-MY-SPECIAL binds *MY-SPECIAL-VARIABLE* to the value 8, then calls DO-SOMETHING-ELSE, which in turn calls SHOW-MY-SPECIAL. At this point, the LET binding of *MY-SPECIAL-VARIABLE* is not lexically apparent to the code in SHOW-MY-SPECIAL. Yet, because the binding is declared special at the point of reference, and because the binding LET form is still active when DO-SOMETHING-ELSE calls SHOW-MY-SPECIAL, the dynamic binding of 8 (rather than the lexical binding of 17) is printed.

Later during execution, the second call to SHOW-MY-SPECIAL happens outside of the LET form, and the top level value of *MY-SPECIAL-VARIABLE* -- 17 -- is printed.

Strictly speaking, the (DECLARE (SPECIAL ... form is not necessary in SHOW-MY-SPECIAL -- the DEFPARAMETER form has the side effect of proclaiming its variable to be special. However, the added declaration adds redundant documentation at the point of use of the special variable. Furthermore, some Lisp compilers will issue a warning (typically: "Undeclared free variable assumed special") that is easily silenced by adding the declaration.

Contents | Cover
Chapter 7 | Chapter 9
Copyright © 1995-1999, David B. Lamkins
All Rights Reserved Worldwide

This book may not be reproduced without the written consent of its author. Online distribution is restricted to the author's site.