Chapter 3 - Essential Lisp in Twelve Lessons

Lesson 3 - Some Examples of Special Forms and Macros

Now we'll look at several special forms and macros. Over the next four lessons, we'll build up a repertoire that will let you write simple functions using the most elementary Lisp data type, the list. Later chapters will cover more complex program structures and data types.

SETQ

Earlier, I told you that Lisp evaluates a symbol form by retrieving its variable value. SETQ gives you a way to set that value:

? (setq my-name "David")
-> "David"

? my-name
-> "David"

? (setq a-variable 57)
-> 57

? a-variable
-> 57

? (setq a-variable :a-keyword)
-> :A-KEYWORD

SETQ's first argument is a symbol. This is not evaluated. The second argument is assigned as the variable's value. SETQ returns the value of its last argument.

SETQ doesn't evaluate its first argument because you want to assign a value to the symbol itself. If SETQ evaluated its first argument, the value of that argument would have to be a symbol. The SET form does this:
? (setq var-1 'var-2)
-> VAR-2

? var-1
-> VAR-2

? var-2
->| Error: Unbound variable

? (set var-1 99)
-> 99

? var-1
-> VAR-2

? VAR-2
-> 99

Did you notice the ' in the first form? This keeps the following form, var-2, from being evaluated. Later in this lesson, when we look at QUOTE, I'll explain this in greater detail.

In the example, we first make the value of VAR-1 be the symbol VAR-2. Checking the value of VAR-2, we find that it has none. Next, we use SET (not SETQ) to assign the value 99 to the symbol, VAR-2, which is the value of VAR-1.

The SETQ form can actually take any even number of arguments, which should be alternating symbols and values:

? (setq month "June" day 8 year 1954)
-> 1954

? month
-> "June"

? day
->| 8

? year
-> 1954

SETQ performs the assignments from left to right, and returns the rightmost value.

LET

The LET form looks a little more complicated that what we've seen so far. The LET form uses nested lists, but because it's a special form, only certain elements get evaluated.

? (let ((a 3)
        (b 4)
        (c 5))
    (* (+ a b) c))
-> 35

? a
->| Error: Unbound variable

? b
->| Error: Unbound variable

? c
->| Error: Unbound variable

The above LET form defines values for the symbols A, B, and C, then uses these as variables in an arithmetic calculation. The calculation's result is also the result of the LET form. Note that none of the variables defined in the LET have a value after Lisp has finished evaluating the form.

In general, LET looks like this:

(let (bindings) forms)

where bindings is any number of two-element lists -- each list containing a symbol and a value -- and forms is any number of Lisp forms. The forms are evaluated, in order, using the values established by the bindings. LET returns the value(s) returned by the last form.

Indentation doesn't affect the operation of LET, but proper indentation does improve readability. Consider these equivalent forms:

(let ((p 52.8)
      (q 35.9)
      (r (f 12.07)))
  (g 18.3)
  (f p) 
  (f q) 
  (g r t))

(let ((p 52.8) (q 35.9) (r (f 12.07))) (g 18.3) (f p) (f q) (g r t))

In the first case, indentation makes clear which are the bindings and which are the forms. Even if the reader doesn't know about the different roles played by the two parts of the LET form, the indentation suggests a difference.

In the second case, you'll have to count parentheses to know where the bindings end and the forms begin. Even worse, the absence of indentation destroys visual cues about the different roles played by the two parts of the LET form.

If you define a variable using SETQ and then name the same variable in a LET form, the value defined by LET supersedes the other value during evaluation of the LET:

? (setq a 89)
-> 89

? a
-> 89

? (let ((a 3))
    (+ a 2))
-> 5

? a
-> 89

Unlike SETQ, which assigns values in left-to-right order, LET binds variables all at the same time:

? (setq w 77)
-> 77

? (let ((w 8)
        (x w))
    (+ w x))
-> 85

LET bound W to 8 and X to W. Because these bindings happened at the same time, W still had its value of 77.

Lisp has a variation of LET, called LET*, that does perform bindings in order:
? (setq u 37)
-> 37

? (let* ((v 4)
         (u v))
    (+ u v))
-> 8

COND

The COND macro lets you evaluate Lisp forms conditionally. Like LET, COND uses parentheses to delimit different parts of the form. Consider these examples:

? (let ((a 1)
        (b 2)
        (c 1)
        (d 1))
    (cond ((eql a b) 1)
          ((eql a c) "First form" 2)
          ((eql a d) 3)))
-> 2

In the above COND form we defined three clauses. Each clause is a list beginning with a test form and followed by as many body forms as desired. The body forms are simply code that you want to execute if the test succeeds. The clauses are selected in order -- as soon as one test succeeds, the corresponding body forms are evaluated and the value of the last body form becomes the value of the COND form.

COND is more general than the special form, IF, which only allows one test and one form each for the then and else parts.

Let's look at what happened in the example. EQL returns T if its two arguments are identical, or the same number (there's a subtle difference that we'll cover in Chapter 17). Only two of the three tests executed. The first, (EQL A B), returned NIL. Therefore, the rest of that clause (containing the number 1 as its only form) was skipped. The second clause tested (EQL A C), which was true. Because this test returned a non-NIL value, the remainder of the clause (the two atomic forms, "First form" and 2) was evaluated, and the value of the last form was returned as the value of the COND, which was then returned as the value of the enclosing LET. The third clause was never tested, since an earlier clause had already been chosen -- clauses are tested in order.

Conventional use of COND uses T as the test form in the final clause. This guarantees that the body forms of the final clause get evaluated if the tests fail in all of the other clauses. You can use the last clause to return a default value or perform some appropriate operation. Here's an example:

? (let ((a 32))
    (cond ((eql a 13) "An unlucky number")
          ((eql a 99) "A lucky number")
          (t "Nothing special about this number")))
-> "Nothing special about this number"

QUOTE

Sometimes we'd like to suppress Lisp's normal evaluation rules. One such case is when we'd like a symbol to stand for itself, rather than its value, when it appears as an argument of a function call:

? (setq a 97)
-> 97

? a
-> 97

? (setq b 23)
-> 23

? (setq a b)
-> 23

? a
-> 23

? (setq a (quote b))
-> B

? a
-> B

The difference is that B's value is used in (SETQ A B), whereas B stands for itself in (SETQ A (QUOTE B)).

The QUOTE form is so commonly used that Lisp provides a shorthand notation:

(QUOTE form) eqv 'form

The eqv symbol means that the two Lisp forms are equivalent. Lisp arranges the equivalence of ' and QUOTE through a reader macro. We'll take a brief look at how you can define your own reader macros in Lesson 12.


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