My開発メモ

マクロを書くときは、評価の順番に気をつけよう (Common-Lisp)

「On Lisp」の読書メモ。

(1) forマクロ — 評価の順番が不適切

以下のような forマクロがこの本の p138 に載っている。

(defmacro for ((var start stop) &body body)
  (let ((gstop (gensym)))
    `(do ((,gstop ,stop)
          (,var ,start (1+ ,var)))
         ((> ,var ,gstop))
       ,@body)))

このマクロについて、以下のようなコードを書いてみる。

(for (i 1 13)
  (princ i))

12345678910111213
NIL

これは期待した動きである。

しかし、以下のようにすると、どうか。

(let ((x 1))
  (for (i x (setq x 13))
    (princ i)))

forマクロに値ではなく変数を与えるのである。
結果は、以下。

13
NIL

なぜこうなるのか。

do以下の処理を見てみると、まず、以下の行が処理されるとき、
ここで、stopに渡された式 (setq x 13) が評価されて、
シンボルgstop に束縛される。

`(do ((,gstop ,stop)

つまり、この時点で x は 13 になってしまっている。

そのあと、次の式が評価されるのだが、

(,var ,start (1+ ,var)))

start には x が渡されているから、xが評価されて 13 になってしまっている。

(i 13 (1+ i))

という式になってしまっている。
この状態で次の終了条件が評価される。

((> ,var ,gstop))

これは、以下の式になっている。

(> i 13)

i が13よりも大きくなったら終了である。
つまり、for文の中のくりかえしは 1回しか実行されない。

その結果、

13
NIL

と出力されるのである。

(2) 適切な forマクロ

forマクロは、以下のように書かれなければならない。

(defmacro for ((var start stop) &body body)
  (let ((gstop (gensym)))
    `(do ((,var ,start (1+ ,var))
          (,gstop ,stop))            ; stopはここで評価されなければならない
         ((> ,var ,gstop))
       ,@body)))

今回の場合も、マクロに渡されるのは値だけではなく、変数の場合もあるということを忘れてはならない、ということかな。

参考

『On Lisp』 p.139
Paul Graham 著 / 野田 開 訳 / H19.3.23 第1版第1刷 オーム社

カテゴリー: Lisp, memo

タグ: common-lisp, forマクロ, for文, 評価の順番

カウント: 74