マクロを定義する際は、展開が実行される際に引数が何回評価されるかに注意する必要があります。以下のマクロ(反復処理を容易にするために使用されるもの)が、この問題を例示しています。このマクロを用いることで、forループ構造を記述できるようになります。
(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
はシンタックスシュガーであり、完全に無視されます。マクロ呼び出し時のこれらの位置に、ノイズワード(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))))
残念ながら、この修正によって次の節で説明する別の問題が生じます。