the right mark the scope of each binding.

(defun foo (x)

(format t 'Parameter: ~a~%' x) ; |<——— x is argument

(let ((x 2)) ; |

(format t 'Outer LET: ~a~%' x) ; | |<—— x is 2

(let ((x 3)) ; | |

(format t 'Inner LET: ~a~%' x)) ; | | |<— x is 3

(format t 'Outer LET: ~a~%' x)) ; | |

(format t 'Parameter: ~a~%' x)) ; |

Each reference to x will refer to the binding with the smallest enclosing scope. Once control leaves the scope of one binding form, the binding from the immediately enclosing scope is unshadowed and x refers to it instead. Thus, calling foo results in this output:

CL-USER> (foo 1)

Parameter: 1

Outer LET: 2

Inner LET: 3

Outer LET: 2

Parameter: 1

NIL

In future chapters I'll discuss other constructs that also serve as binding forms—any construct that introduces a new variable name that's usable only within the construct is a binding form.

For instance, in Chapter 7 you'll meet the DOTIMES loop, a basic counting loop. It introduces a variable that holds the value of a counter that's incremented each time through the loop. The following loop, for example, which prints the numbers from 0 to 9, binds the variable x:

(dotimes (x 10) (format t '~d ' x))

Another binding form is a variant of LET, LET*. The difference is that in a LET, the variable names can be used only in the body of the LET—the part of the LET after the variables list—but in a LET*, the initial value forms for each variable can refer to variables introduced earlier in the variables list. Thus, you can write the following:

(let* ((x 10)

(y (+ x 10)))

(list x y))

but not this:

(let ((x 10)

(y (+ x 10)))

(list x y))

However, you could achieve the same result with nested LETs.

(let ((x 10))

(let ((y (+ x 10)))

(list x y)))

Lexical Variables and Closures

By default all binding forms in Common Lisp introduce lexically scoped variables. Lexically scoped variables can be referred to only by code that's textually within the binding form. Lexical scoping should be familiar to anyone who has programmed in Java, C, Perl, or Python since they all provide lexically scoped 'local' variables. For that matter, Algol programmers should also feel right at home, as Algol first introduced lexical scoping in the 1960s.

However, Common Lisp's lexical variables are lexical variables with a twist, at least compared to the original Algol model. The twist is provided by the combination of lexical scoping with nested functions. By the rules of lexical scoping, only code textually within the binding form can refer to a lexical variable. But what happens when an anonymous function contains a reference to a lexical variable from an enclosing scope? For instance, in this expression:

(let ((count 0)) #'(lambda () (setf count (1+ count))))

the reference to count inside the LAMBDA form should be legal according to the rules of lexical scoping. Yet the anonymous function containing the reference will be returned as the value of the LET form and can be invoked, via FUNCALL, by code that's not in the scope of the LET. So what happens? As it turns out, when count is a lexical variable, it just works. The binding of count created when the flow of control entered the LET form will stick around for as long as needed, in this case for as long as someone holds onto a reference to the function object returned by the LET form. The anonymous function is called a closure because it 'closes over' the binding created by the LET.

The key thing to understand about closures is that it's the binding, not the value of the variable, that's captured. Thus, a closure can not only access the value of the variables it closes over but can also assign new values that will persist between calls to the closure. For instance, you can capture the closure created by the previous expression in a global variable like this:

(defparameter *fn* (let ((count 0)) #'(lambda () (setf count (1+ count)))))

Then each time you invoke it, the value of count will increase by one.

CL-USER> (funcall *fn*)

1

CL-USER> (funcall *fn*)

2

CL-USER> (funcall *fn*)

3

A single closure can close over many variable bindings simply by referring to them. Or multiple closures can capture the same binding. For instance, the following expression returns a list of three closures, one that increments the value of the closed over count binding, one that decrements it, and one that returns the current value:

(let ((count 0))

(list

#'(lambda () (incf count))

#'(lambda () (decf count))

#'(lambda () count)))

Dynamic, a.k.a. Special, Variables

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 goto, global variables do have legitimate uses and exist in one form or another in almost every

Вы читаете Practical Common Lisp
Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату