While all assignments can be expressed with SETF
, certain patterns involving assigning a new value based on the current value are sufficiently common to warrant their own operators. For instance, while you could increment a number with SETF
, like this:
(setf x (+ x 1))
or decrement it with this:
(setf x (- x 1))
it's a bit tedious, compared to the C-style ++x
and —x
. Instead, you can use the macros INCF
and DECF
, which increment and decrement a place by a certain amount that defaults to 1.
(incf x) === (setf x (+ x 1))
(decf x) === (setf x (- x 1))
(incf x 10) === (setf x (+ x 10))
INCF
and DECF
are examples of a kind of macro called SETF
that modify places by assigning a new value based on the current value of the place. The main benefit of modify macros is that they're more concise than the same modification written out using SETF
. Additionally, modify macros are defined in a way that makes them safe to use with places where the place expression must be evaluated only once. A silly example is this expression, which increments the value of an arbitrary element of an array:
(incf (aref *array* (random (length *array*))))
A naive translation of that into a SETF
expression might look like this:
(setf (aref *array* (random (length *array*)))
(1+ (aref *array* (random (length *array*)))))
However, that doesn't work because the two calls to RANDOM
won't necessarily return the same value—this expression will likely grab the value of one element of the array, increment it, and then store it back as the new value of a different element. The INCF
expression, however, does the right thing because it knows how to take apart this expression:
(aref *array* (random (length *array*)))
to pull out the parts that could possibly have side effects to make sure they're evaluated only once. In this case, it would probably expand into something more or less equivalent to this:
(let ((tmp (random (length *array*))))
(setf (aref *array* tmp) (1+ (aref *array* tmp))))
In general, modify macros are guaranteed to evaluate both their arguments and the subforms of the place form exactly once each, in left-to-right order.
The macro PUSH
, which you used in the mini-database to add elements to the *db*
variable, is another modify macro. You'll take a closer look at how it and its counterparts POP
and PUSHNEW
work in Chapter 12 when I talk about how lists are represented in Lisp.
Finally, two slightly esoteric but useful modify macros are ROTATEF
and SHIFTF
. ROTATEF
rotates values between places. For instance, if you have two variables, a
and b
, this call:
(rotatef a b)
swaps the values of the two variables and returns NIL
. Since a
and b
are variables and you don't have to worry about side effects, the previous ROTATEF
expression is equivalent to this:
(let ((tmp a)) (setf a b b tmp) nil)
With other kinds of places, the equivalent expression using SETF
would be quite a bit more complex.
SHIFTF
is similar except instead of rotating values it shifts them to the left—the last argument provides a value that's moved to the second-to-last argument while the rest of the values are moved one to the left. The original value of the first argument is simply returned. Thus, the following:
(shiftf a b 10)
is equivalent—again, since you don't have to worry about side effects—to this:
(let ((tmp a)) (setf a b b 10) tmp)
Both ROTATEF
and SHIFTF
can be used with any number of arguments and, like all modify macros, are guaranteed to evaluate them exactly once, in left to right order.
With the basics of Common Lisp's functions and variables under your belt, now you're ready to move onto the feature that continues to differentiate Lisp from other languages: macros.
7. Macros: Standard Control Constructs
While many of the ideas that originated in Lisp, from the conditional expression to garbage collection, have been incorporated into other languages, the one language feature that continues to set Common Lisp apart is its macro system. Unfortunately, the word
All programmers should be used to the idea that the definition of a language can include a standard library of functionality that's implemented in terms of the 'core' language—functionality that could have been implemented by any programmer on top of the language if it hadn't been defined as part of the standard library. C's standard library, for instance, can be implemented almost entirely in portable C. Similarly, most of the ever-growing set of classes and interfaces that ship with Java's standard Java Development Kit (JDK) are written in 'pure' Java.
One advantage of defining languages in terms of a core plus a standard library is it makes them easier to understand and implement. But the real benefit is in terms of expressiveness—since much of what you think of as 'the language' is really just a library—the language is easy to extend. If C doesn't have a function to do some thing or another that you need, you can write that function, and now you have a slightly richer version of C. Similarly, in a language such as Java or Smalltalk where almost all the interesting parts of the 'language' are defined in terms of classes, by defining new classes you extend the language, making it more suited for writing programs to do whatever it is you're trying to do.
While Common Lisp supports both these methods of extending the language, macros give Common Lisp yet another way. As I discussed briefly in Chapter 4, each macro defines its own syntax, determining how the s- expressions it's passed are turned into Lisp forms. With macros as part of the core language it's possible to build new syntax—control constructs such as WHEN
, DOLIST
, and LOOP
as well as definitional forms such as DEFUN
and DEFPARAMETER
—as part of the 'standard library' rather than having to hardwire them into the core. This has implications for how the language itself is implemented, but as a Lisp programmer you'll care more that it gives you another way to extend the language, making it a better language for expressing solutions to your particular programming problems.