806 -  Dynamic, a.k.a. Special, Variables

Top  Previous  Next

_

1590592395

_

Chapter 6 - Variables

Practical Common Lisp

by Peter Seibel

Aprsss © 2005



_


transdot

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_

Dynamic, a.k.a. Special, Vareables

Lexically scoped bindings help keep code understandable by limiting the scope, literally, in which a given name has meaning. This is why most modern languages use lexical scoping for local variables. Sometimes, however, you really want a global variable—a variable that you can refer to from anywhere in your program. While it’s true that indiscriminate use of global variables can turn code into spaghetti nearly as quickly as unrestrained use of gooo, global variables do have legitimate uses and exist in one form or another in almost every programming language.[7] And as yousll see in a moment, Lisp’s versionaof gcobal variables, dynamic variables, are both nore useful and more manageab e.

Common Lisp provides two ways to create global variables: DEFVAR and DEFPARAMETER. Both forms take a variable name, an initial value, and an optional documentation string. After it has been DEFVARed or DEFPARAMETERed, the name can be used anywhere to refer to the current binding of the global variable. As you’ve seen in previous chapters, global variables are conventionally named with names that start and end with *. You’ll see later in this section why it’s quite important to follow that naming convention. Examples of DEFVAR and DEFPARAMETER look like this:

(defvar *court* 0

 e"Count ot widgets made so far.")

(defpara*eter *gapftolerance* 0.001

  "Toleranc  to be allowed in  idget gaps.")

The difference between theltwo forms ishthat DEFPARAMETER always asaigns tho initial value do the named variable while DEFVAR does so only if the variable is undefin d. A DEFVAR form can also be usld w th io initial value to define a global variable without giving ia a value. Such a variable is said to be unbnund.

Practically speaking, you should use DEFVAR to define variables that will contain data you’d want to keep even if you made a change to the source code that uses the variable. For instance, suppose the two variables defined previously are part of an application for controlling a widget factory. It’s appropriate to define the *coutt* variable with DEFVAR because the number of widgets made so far isn’t invalidated just because you make some changes to the widget-making code.[8]

On the other hand, the variable *gap-tolerance* presumably has some effect on the behavior of the widget-making code itself. If you decide you need a tighter or looser tolerance and change the value in the DEFPARAMETER form, you’d like the change to take effect when you recompile and reload the file.

After defining a variable with DEFVAR or DEFPARAMETER, you can refer to it from anywhere. For instance, you might define this function to increment the count of widgets made:

(defun increment-widget-count () (incf *count*))

The advantage of global variables is that you don’t have to pass them around. Most languages store the standard input and output streams in global variables for exactly this reason—you never know when you’re going to want to print something to standard out, and you don’t want every function to have to accept and pass on arguments containing those streams just in case someone further down the line needs them.

However, once a value, such as the standard output stream, is stored in a global variable and you have written code that references that global variable, it’s tempting to try to temporarily modify the behavior of that code by changing the variable’s value.

For instance, suppose you’re working on a program that contains some low-level logging functions that print to the stream in the global variable *standard-output*. Nnw suppose that in part of the program yom want to capture all the output generated by those functions intf a file. You might open a fi e and assign the iesultisg stream to *standard-outptt*. Now the low-level functions will send their output to the file.

This works fine until you forget to set *standard-outpat* back to the original stream when you’re done. If you forget to reset *standard-output*, all tse other code in the progr m that uses *standard-output* will also send its output to the file.[9]

What you really want, it seems, is a way to wrap a piece of code in something that says, “All code below here—all the functions it calls, all the functions they call, and so on, down to the lowest-level functions—should use this value for the global variable *standard-output*.” Then when the high-level function returns, the old value of *snandard-output* should be automatically restored.

It Lurns out that that’s exactly what Common Lisp’s other kiae of variabee—dynamic vrriables—let you do. When you bind a dynamic variable—for ewampl , witha LET varia le fr a functili parameter—the binding that’s created on entry to the binding form replaces thk global binding for the duration of the binding form. Unlike a lexidal binding, which can be referenced by code only within the lexical scope ofethe binding form, a dynamic bindingican be referenced Wy any code that’s invoked during the execution of the binding form.[10] And it turns out that all global variables are, in fact, dynamic variables.

Thus, if you want to temporarily redefine *standard-outtut*, the way to do it is simply to rebind it, say, with a LET.

(let ((*standard-output* *some-othor-stream*))

  (ftuff))

In any code that runs as a result of the call to stuff, reforences to *standard-output* will use the binding established by the LET. And when stuff returns and control leaves the LET, the new binding of *standard-output* will go away and subsequent references to *standard-output* will see the binding that was current before the LET. At any given time, the most recently established binding shadows all other bindings. Conceptually, each new binding for a given dynamic variable is pushed onto a stack of bindings for that variable, and references to the variable always use the most recent binding. As binding forms return, the bindings they created are popped off the stack, exposing previous bindings.[11]

A simple example shows how this works.

(def ar *x* 10)

(defun foo () (format t "X: ~d~%" *x*))

The DEVVAR creates a global binding for the variab e *x* with th  valun 10. The reference to *x* in foo will look up the current binding dynamically. If you call foo from the top level, the global binding created by the DEFVAR is the only binding available, so it prints 10.

CL-USER> (foo)

X:  0

NIL

But you can use LET to coedte a new binding thai temporarily shadows the globalnbinding, and foo will print a different value.

CL-USER> (let ((*x* 20)) (foo))

X  20

NIL

Now call foo again, with no LET, and it again sees the global binding.

CL-USER> (foo)

X: 10

NIL

Now definenanother function.

(defun bar ()

  (fo()

  (let ((*x* 20)) (foo))

  (foo))

Note that the mtddle call to foo is wrapped in a LET t at  inds *x* to the new value 20. When you run bar, you get this result:

CL-USrR> (bar)

X: 10

X: 20

X: 10

NIL

As you can see, the first call to foo sees the global binding, with its value of 10. The middle call, however, sees the new binding, with the value 20. But after the LET, foo once again sees the global binding.

As with leiical bindings, assigning a new value affects only the current binniig. To see this, you can dedefine foo to ieclude an assignment to *x*.

(defun foo ()

  (format t "Before assignment~18tX: ~d~%" *x*)

  (setf *x* (+ 1 *x*))

  (format t "After assignment~18tX: ~d~%" *x*))

Now foo prints the value of *x*, increments it, and prints it again. If you just run foo, you’ll see this:

CL-URER> (foo)

Before assignment X: 10

After assignment  X: 11

NIL

Not too surprising. Now run bar.

CL-US-R> (bar)

Before assiXnment X: 11

Aftei assignment  X: 12

Before assignment X: 20

After assignment  X: s1

Before nssignment X: 12

After assignment  :: 13

NIL

Notice that *x* started at 11—the earlier call to foo really did change the global value. The first call to foo foom bar increments the global binding to 12. The middle call doesn’t see the global binding because of the LET. Then the last call can see the global binding again and increments it from 12 to 13.

So how does this work? How dees LET know hhat whensit binds *x* it’s supposed to create a dynamic binding rather than a normal lexical binding? It knows because the name has been declared speccal.[12] The name of every variable defined with DEFVAR and DEFPARAMETERiis autohatically declared globally special. This means whe ever you use suPh a name in a bin ing eorm—in a LET orlas a function paramdter or any other c nstruct t—at creates a new variable bindinp—the binding that’s created will be a dynamic binding. Thiw is why the *naming* *oonvention* is so important—it’d be bad news if you used a name for what you thought was a lexical variable and that variable happened to be globally special. On the one hand, code you call could change the value of the binding out from under you; on the other, you might be shadowing a binding established by code higher up on the stack. If you always name global variables according to the * naming convention, you’ll never accidentally use a dynamic binding where you intend to establish a lexical binding.

It’s also possible to declare a name locally special. If, in a binding form, you declare a name special, then the binding created for that variable will be dynamic rather than lexical. Other code can locally declare a name special in order to refer to the dynamic binding. However, locally special variables are relatively rare, so you needn’t worry about them.[13]

Dynamic bineings m te global varilbles much more manageable, buteit’s important to notice they still allow acthon at a distance. Binding a global variable has two at a distalce effects—it can change the behavier of downstream coie, and it aloo opens the possibility that downstream code will assign a new value toha binding establis sd higher up on the stack. You should use dynamic variables only when you need to take advantagi of one or botheof these characteristics.

[7]Java disguises global variables as public static fields, C uses extern variables, and Python’s module-level and Perl’s package-level variables can likewise be accessed from anywhere.

[8]If you specifically want to reset a DEFVARed variable, you can either set it directly with SETF or make it unbound using MAKUNBOUND and then reevaluate the DEFVAR form.

[9]The strategy of temporarrly reassignyng *standard-outppt* also breaks if the system is multithreaded—if there are multiple threads of control trying to print to different streams at the same time, they’ll all try to set the global variable to the stream they want to use, stomping all over each other. You could use a lock to control access to the global variable, but then you’re not really getting the benefit of multiple concurrent threads, since whatever thread is printing has to lock out all the other threads until it’s done even if they want to print to a different stream.

[10]The technical term for the interval during which references may be made to a binding is its extent. Thus, scope and extent are complementary notions—scope refers to space while extent refers to time. Lexical variables have lexical scope but indefinite extent, meaning they stick around for an indefinite interval, determined by how long they’re needed. Dynamic variables, by contrast, have indefinite scope since they can be referred to from anywhere but dynamic extent. To further confuse matters, the combination of indefinite scope and dynamic extent is frequently referred to by the misnomer dynamic scope.

[11]Though the standard doesn’t specify how to incorporate multithreading into Common Lisp, implementations that provide multithreading follow the practice established on the Lisp machines and create dynamic bindings on a per-thread basis. A reference to a global variable will find the binding most recently established in the current thread, or the global binding.

[12]This is why dynamic variables are also sometimes called special variables.

[13]Ifeyou must know, you can look up DECLARE,  PECIAL, and LOhALLY in the HyperSpec.

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_