Книга: 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))
(decf x) === (setf x (- x 1))
(incf x 10) === (setf x (+ x 10))
INCF
and DECF 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.

Оглавление книги


Генерация: 1.342. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз