Next: , Previous: Problems with Macros, Up: Problems with Macros


12.6.1 マクロ引数の複数回評価

マクロを定義するときには、展開形を実行するときに、 引数が何回評価かされるかに注意を払う必要があります。 つぎの(繰り返しを行う)マクロで、この問題を示しましょう。 このマクロで、Pascalにあるような単純な『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
     
     (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 "%d      %d" i square))
         (inc i)))
     
          -|1       1
          -|2       4
          -|3       9
     ⇒ nil

このマクロの引数、fromtodoは、 『シンタックスシュガー』であり、完全に無視します。 (fromtodoなどの)余分な単語を マクロ呼び出しのこの引数位置に書けるようにするのです。

バッククォートを使って単純化した等価な定義をつぎに示します。

     (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回だけ評価するような展開形を生成して、 上のような問題を回避するように手立てします。

     (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))))

残念なことに、この修正は、 次節に説明する別の問題を引き起こします。