My開発メモ

マクロを書く場合、変数に注意する (Common Lisp)

“On Lisp”の内容を理解するのが、なかなか大変。
ここでは、p138 のコード、『第10章 マクロのその他の落し穴 — 10.1 評価の回数』
のところをメモしておく。

(1) forマクロを書いてみる

Lispでは繰り返しでは再帰を使うが、手続き型のような for文を書くこともできる。

lisp で forマクロを書く場合、以下のようにするとどうなるか?

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

このマクロを定義したのち、以下のようにこのマクロを使ってみる。

(for (i 1 10)
  (princ i))

12345678910
NIL

これは期待どおりに動いている。

(2) 予想外の使われかた

マクロを定義する時、マクロに使用した変数に値が渡されるとは限らない。

たとえば、以下のように 式が渡される場合もあり得る。

(let x 2
  (for (i 1 (incf x))
    (princ i)))

(incf x) というのは、xの値に1を足したxを返してくれる。手続き型言語における ++x のようなものである。

これで forマクロを呼び出すと、forマクロの中の “stop”変数が (incf x) を取り込む。
そして、それが、(> ,var ,stop) で評価される。

この時、変数varは マクロ式の中で、(1+ ,var) により増加している。
ところが、,stop変数もこの時評価されて 増加しているのである。

だから、いつまでたっても 変数var は 変数stop よりも大きくならない。
したがって、このforマクロは、無限ループとなる。

(3) gensym による解決策

この場合、以下のように forマクロを書けばよい。

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

(let ((gstop (gensym))) というのは、gstopという変数を定義しているのだが、
(gensym) は、プログラム上のどのシンボルとも eq でないシンボルを返す。
そのことで、gstop は唯一独自のシンボルとなる。

そして、そのシンボルに stop の評価を代入する。(,gstop ,stop)
このことで、(incf x) の値が固定される。

結果、((> ,var ,gstop)) は var が 4 となった時点で終了となる。

(let x 2
  (for (i 1 (incf x))
    (princ i)))

123
NIL

グレアムさんが言いたいのは、マクロを書くときは、変数に渡されるのが値とは
限らない、式が渡される場合もあるから気をつけてね、ということだろうな。

参考

カテゴリー: Lisp, memo

タグ: common-lisp, for, gensym, macro, マクロ

カウント: 88