Practical Common Lispの Ch 8 の最後で、Peter Seibel がonce-only
マクロを提示します。その目的は、ユーザー定義マクロでの変数評価に関する多くの微妙な問題を軽減することです。この時点で、他の投稿のようにこのマクロがどのように機能するかを理解しようとしているのではなく、適切に使用する方法だけを理解しようとしていることに注意してください。
(defmacro once-only ((&rest names) &body body)
(let ((gensyms (loop for n in names collect (gensym))))
`(let (,@(loop for g in gensyms collect `(,g (gensym))))
`(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))
,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
,@body)))))
以下は、いくつかの変数評価の問題を提示しようとする (正しくない) 不自然なマクロのサンプルです。整数の範囲をデルタで反復処理し、範囲を返すことを目的としています。
(defmacro do-range ((var start stop delta) &body body)
"Sample macro with faulty variable evaluations."
`(do ((,var ,start (+ ,var ,delta))
(limit ,stop))
((> ,var limit) (- ,stop ,start))
,@body))
たとえば、(do-range (i 1 15 3) (format t "~A " i))
印刷1 4 7 10 13
してから返す必要があります14
。
問題には、1) の 2 番目のオカレンスがlimit
自由変数として発生するため、潜在的にキャプチャされる、2) バインドされた変数 の最初のオカレンスがキャプチャされる可能性があるlimit
、マクロ パラメーターに現れる他の変数と一緒に式で発生するため、 3) 順不同の評価。パラメーター リストの前に表示されますが、のdelta
前に評価されます。4)とが複数回評価されるため、複数の変数評価。私が理解しているように、これらの問題を修正する必要があります。stop
stop
delta
stop
start
once-only
(defmacro do-range ((var start stop delta) &body body)
(once-only (start stop delta limit)
`(do ((,var ,start (+ ,var ,delta))
(limit ,stop))
((> ,var limit) (- ,stop ,start))
,@body)))
ただし、バインドされていない変数であることに(macroexpand '(do-range (i 1 15 3) (format t "~A " i)))
不満があります。limit
代わりに に切り替えるとwith-gensyms
、上記の問題 1 と 2 のみを処理する必要があり、展開は問題なく進行します。
これはonce-only
マクロの問題ですか?そしてonce-only
、上で概説したすべての問題 (およびおそらく他の問題) を本当に解決しますか?