マクロを書く場合、変数に注意する (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
グレアムさんが言いたいのは、マクロを書くときは、変数に渡されるのが値とは
限らない、式が渡される場合もあるから気をつけてね、ということだろうな。
参考
- 『On Lisp』 Paul Graham 著 / 野田 開 訳 / H19.3.23 第1版第1刷 オーム社
カテゴリー: Lisp, memo
タグ: common-lisp, for, gensym, macro, マクロ
カウント: 89