Next: Surprising Local Vars, Previous: Wrong Time, Up: Problems with Macros [Contents][Index]
マクロを定義する場合、展開形が実行されるときに引数が何回評価されるか注意を払わなければなりません。以下の(繰り返し処理を用意にする)マクロで、この問題を示してみましょう。このマクロで“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
は、“構文糖(syntactic
sugar)”であり、完全に無視されます。このアイデアは、マクロ呼び出し中で(from
, to
, and
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))))
残念なことに、この訂正により、以下のセクションで説明する、別の問題が発生します。