Книга: Practical Common Lisp
Other Ways to Modify Places
Other Ways to Modify Places
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))
and
(decf x) === (setf x (- x 1))
(incf x 10) === (setf x (+ x 10))
INCFDECF
are examples of a kind of macro called modify macros. Modify macros are macros built on top of 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.
- Cyber-places
- Appendix E. Other resources and links
- Other debugging tools
- 10.2 Modifying the .emacs File Directly
- 4 A few ways to use threads
- KDE — The Other Environment
- Other Mail Clients
- Other Uses for DHCP
- Other FTP Servers
- Other vsftpd Server Configuration Files
- Modifying the Program
- Other Operators