マクロを定義する場合、展開形が実行されるときに引数が何回評価されるか注意を払わなければなりません。以下の(繰り返し処理を用意にする)マクロで、この問題を示してみましょう。このマクロでfor-loop構文を記述できます。
(defmacro for (var from init to final do &rest body)
"Execute a simple \"for\" loop.
For example, (for i from 1 to 10 do (print i))."
(list 'let (list (list var init))
(cons 'while
(cons (list '<= var final)
(append body (list (list 'inc var)))))))
(for i from 1 to 3 do (setq square (* i i)) (princ (format "\n%d %d" i square))) →
(let ((i 1))
(while (<= i 3)
(setq square (* i i))
(princ (format "\n%d %d" i square))
(inc i)))
-|1 1
-|2 4
-|3 9
⇒ nil
マクロ内の引数from、to、doは構文糖(syntactic
sugar)であり完全に無視されます。このアイデアはマクロ呼び出し中で(from、to、doのような)余計な単語をこれらの位置に記述できるようにするというものです。
以下はバッククォートの使用により、より単純化された等価の定義です:
(defmacro for (var from init to final do &rest body)
"Execute a simple \"for\" loop.
For example, (for i from 1 to 10 do (print i))."
`(let ((,var ,init))
(while (<= ,var ,final)
,@body
(inc ,var))))
この定義のフォームは両方(バッククォートのあるものとないもの)とも、各繰り返しにおいて毎回finalが評価されるという欠点をもちます。finalが定数のときは問題がありません。しかしこれがより複雑な、たとえば(long-complex-calculation
x)のようなフォームならば、実行速度は顕著に低下し得ます。finalが副作用をもつなら、複数回実行するとおそらく誤りになります。
うまく設計されたマクロ定義は、繰り返し評価することがそのマクロの意図された目的でない限り、引数を正確に1回評価を行う展開形を生成することで、この問題を避けるためのステップを費やします。以下はforマクロの正しい展開形です:
(let ((i 1)
(max 3))
(while (<= i max)
(setq square (* i i))
(princ (format "%d %d" i square))
(inc i)))
以下はこの展開形を生成するためのマクロ定義です:
(defmacro for (var from init to final do &rest body)
"Execute a simple for loop: (for i from 1 to 10 do (print i))."
`(let ((,var ,init)
(max ,final))
(while (<= ,var max)
,@body
(inc ,var))))
残念なことにこの訂正により以下のセクションで説明する、別の問題が発生します。