(do-primes (p 0 10)

(incf ending-value p))

ending-value)

Again, MACROEXPAND-1 can show you the problem. The first call expands to this:

(do ((ending-value (next-prime 0) (next-prime (1+ ending-value)))

(ending-value 10))

((> ending-value ending-value))

(print ending-value))

Some Lisps may reject this code because ending-value is used twice as a variable name in the same DO loop. If not rejected outright, the code will loop forever since ending-value will never be greater than itself.

The second problem call expands to the following:

(let ((ending-value 0))

(do ((p (next-prime 0) (next-prime (1+ p)))

(ending-value 10))

((> p ending-value))

(incf ending-value p))

ending-value)

In this case the generated code is perfectly legal, but the behavior isn't at all what you want. Because the binding of ending-value established by the LET outside the loop is shadowed by the variable with the same name inside the DO, the form (incf ending-value p) increments the loop variable ending-value instead of the outer variable with the same name, creating another infinite loop.[99]

Clearly, what you need to patch this leak is a symbol that will never be used outside the code generated by the macro. You could try using a really unlikely name, but that's no guarantee. You could also protect yourself to some extent by using packages, as described in Chapter 21. But there's a better solution.

The function GENSYM returns a unique symbol each time it's called. This is a symbol that has never been read by the Lisp reader and never will be because it isn't interned in any package. Thus, instead of using a literal name like ending-value, you can generate a new symbol each time do-primes is expanded.

(defmacro do-primes ((var start end) &body body)

(let ((ending-value-name (gensym)))

`(do ((,var (next-prime ,start) (next-prime (1+ ,var)))

(,ending-value-name ,end))

((> ,var ,ending-value-name))

,@body)))

Note that the code that calls GENSYM isn't part of the expansion; it runs as part of the macro expander and thus creates a new symbol each time the macro is expanded. This may seem a bit strange at first—ending-value-name is a variable whose value is the name of another variable. But really it's no different from the parameter var whose value is the name of a variable—the difference is the value of var was created by the reader when the macro form was read, and the value of ending-value-name is generated programmatically when the macro code runs.

With this definition the two previously problematic forms expand into code that works the way you want. The first form:

(do-primes (ending-value 0 10)

(print ending-value))

expands into the following:

(do ((ending-value (next-prime 0) (next-prime (1+ ending-value)))

(#:g2141 10))

((> ending-value #:g2141))

(print ending-value))

Now the variable used to hold the ending value is the gensymed symbol, #:g2141. The name of the symbol, G2141, was generated by GENSYM but isn't significant; the thing that matters is the object identity of the symbol. Gensymed symbols are printed in the normal syntax for uninterned symbols, with a leading #:.

The other previously problematic form:

(let ((ending-value 0))

(do-primes (p 0 10)

(incf ending-value p))

ending-value)

looks like this if you replace the do-primes form with its expansion:

(let ((ending-value 0))

(do ((p (next-prime 0) (next-prime (1+ p)))

(#:g2140 10))

((> p #:g2140))

(incf ending-value p))

ending-value)

Again, there's no leak since the ending-value variable bound by the LET surrounding the do-primes loop is no longer shadowed by any variables introduced in the expanded code.

Not all literal names used in a macro expansion will necessarily cause a problem—as you get more experience with the various binding forms, you'll be able to determine whether a given name is being used in a position that could cause a leak in a macro abstraction. But there's no real downside to using a gensymed name just to be safe.

With that fix, you've plugged all the leaks in the implementation of do-primes. Once you've gotten a bit of macro-writing experience under your belt, you'll learn to write macros with these kinds of leaks preplugged. It's actually fairly simple if you follow these rules of thumb:

• Unless there's a particular reason to do otherwise, include any subforms in the expansion in positions that will be evaluated in the same order as the subforms appear in the macro call.

• Unless there's a particular reason to do otherwise, make sure subforms are evaluated only once by creating a variable in the expansion to hold the value of evaluating the argument form and then using that variable anywhere else the value is needed in the expansion.

• Use GENSYM at macro expansion time to create variable names used in the expansion.

Macro-Writing Macros

Of course, there's no reason you should be able to take advantage of macros only when writing functions. The job of macros is to abstract away common syntactic patterns, and certain patterns come up again and again in writing macros that can also benefit from being abstracted away.

In fact, you've already seen one such pattern—many macros will, like the last version of do- primes, start with a LET that introduces a few variables holding gensymed symbols to be used in the macro's expansion. Since this is such a common pattern, why not abstract it away with its own macro?

In this section you'll write a macro, with-gensyms, that does just that. In other words, you'll write a macro-writing macro: a macro that generates code that generates code. While complex macro-writing

Вы читаете Practical Common Lisp
Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату