Chapter 3 - Essential Lisp in Twelve Lessons

Lesson 6 - Binding versus Assignment

Binding creates a new place to hold a value

Lisp often "creates a binding" for a variable by allocating a piece of storage to hold the variable's value and putting the value into the newly allocated memory. Binding is a very general mechanism for implementing lexical scope for variables, but it has other uses depending upon the lifetime of the binding. We'll revisit this in Chapter 8 when we study lifetime and visibility.

Yes, Lisp allocates storage for new bindings. While this sounds like it could be horribly inefficient, we've said nothing yet about where Lisp allocated the storage. For example, Lisp binds function parameters to actual values, but allocates the storage on the stack just like any other programming language. Lisp creates bindings in the heap if it can't determine that the binding has a lifetime which ends when the binding form finishes executing.

Bindings have names

Lisp gives each binding a name. Otherwise, how would your program refer to the binding? Simple, eh? Hold on...

A binding can have different values at the same time

It is quite common for multiple bindings to share the same name. For example:

(let ((a 1))
   (let ((a 2))
      (let ((a 3))
         ...)))

Here, A has three distinct bindings by the time the body (marked by ...) executes in the innermost LET.

This is not to say that the above example is representative of typical Lisp code, however.

One binding is innermost

;; Here, A has no binding.
(let ((a 1))
   ;; Here, the innermost binding of A has the value 1.
   (let ((a 2))
      ;; Here, the innermost binding of A has the value 2.
      (let ((a 3))
         ;; Here, the innermost binding of A has the value 3.
         ...)))

As you can see, the notion of innermost binding depends on the relative position of your program's code to the form that established a particular binding. If you look at how binding forms are nested (easy to do if you indent your code as shown above) then the program has access to bindings created around, or enclosing, your program code.

One more thing you should know is that an outer binding is still visible through inner binding forms, as long as the inner binding form does not bind the same symbol:

;; Here, A and B have no binding.
(let ((a 1)
      (b 9))
   ;; Here, the innermost binding of A has the value 1,
   ;; and the binding of B has the value 9.
   (let ((a 2))
      ;; Here, the innermost binding of A has the value 2.
      ;; The binding of B still has the value 9.
      (let ((a 3))
         ;; Here, the innermost binding of A has the value 3.
         ;; B still has the value 9 from the outermost LET form.
         ...)))

The program can only access bindings it creates

When a binding form binds a new value to an existing symbol, the previous value becomes shadowed. The value of the outer binding is hidden (but not forgotten) while your program code executes inside the inner binding form. But as soon as your program leaves the inner binding form, the value of the outer binding is restored. For example:

(let ((z 1))
   ;; Here, the innermost binding of Z has the value 1.
   (let ((z 2))
      ;; Here, the innermost binding of Z has the value 2.
      ...)
   ;; Now we're outside the inner binding form,
   ;; and we again see the binding with the value 1.
   ...)

Assignment gives an old place a new value

The SETQ form changes the value of an existing binding:

(let ((z 1))
   ;; Here, the innermost binding of Z has the value 1.
   (setq z 9)
   ;; Now the value of Z is 9.
   (let ((z 2))
      ;; Here, the innermost binding of Z has the value 2.
      ...)
   ;; Now we're outside the inner binding form,
   ;; and we again see the outer binding of Z with the value 9.
   ...)

The SETQ form above changed the value of the outer binding of Z for the remainder of the outer LET form. This is often the wrong thing to do. The problem is that you now have to look in two places to discover the value of Z -- first at the binding forms, then in the program code for assignments such as SETQ. While the binding forms are indented by convention (many Lisp editors do this as you type), the assignment form, as part of the body code of the program, gets no special indentation; this makes it harder to spot when you read the program.

We can quite easily avoid the assignment in the previous example by introducing a new binding:

(let ((z 1))
   ;; Here, the innermost binding of Z has the value 1.
   (let ((z 9))
      ;; Now the value of Z is 9.
      (let ((z 2))
         ;; Here, the innermost binding of Z has the value 2.
         ...)
      ;; Now we're outside the innermost binding form,
      ;; and we again see the middle binding of Z with the value 9.
      ...)
   ;; Here, we see the outermost binding of Z with the value 1.
   ...)

Now all of the bindings of Z are apparent from the relative indentation of the LET forms. While reading the program, all we have to do to find the right binding for Z at any point in our program code (the ... in the example) is to scan vertically looking for a LET form at an outer level of indentation.

When a SETQ form refers to a variable that is not bound by an enclosing LET form, it assigns a value to the global or special value of the symbol. A global value is accessible anywhere it's not shadowed, and stays available for as long as the Lisp system runs. We'll look at special variables in Chapter 8.

(setq a 987)
;; Here, A has the global value 987.
(let ((a 1))
   ;; Here, the binding of A to the value 1 shadows the global value.
   ...)
;; Now the global value of A is again visible.
...

Contents | Cover
Chapter 2 | Chapter 4 | Chapter 3, Lesson 5 | Chapter 3, Lesson 7 | Chapter 3, Introduction
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.