2010年2月2日火曜日

Lisp Quote Backquote (4)

日々ちょこちょこSteeleのbackquote impl.をながめているのですが、なんだかすっと頭に入ってこないのです。こういうときは、大抵何か根本的な理解が欠落しています。丁寧にコードを追ってみました。そしたら、マクロの基本的な振舞いの理解が曖昧だったことがわかりました。私的には大収穫なのですが、こんな基本的なことが曖昧というのはとても恥ずかしい。でも、誰かの何かに役立つかもしれないので書いちゃいます。

わかっていなかったのは、マクロの中で呼ぶ関数の振舞いです。

まず、諸処定義。

CL-USER> (setf a 1 b 2)
2
CL-USER> a
1
CL-USER> b
2
CL-USER> (defmacro a-mac (x y)
(a-fun x y))
A-MAC
CL-USER> (defun a-fun (x y)
(+ x y))
A-FUN
CL-USER>

この関数を呼び出したとき、もちろん引数はeagerに評価されます。

CL-USER> (a-fun a b)
3
CL-USER>

あ、この例では、eagerもlazyもへったくれもないですが、まぁCommon Lispの関数呼出しの評価モデルはeagerです。

では、(a-mac a b) ではどうだろう、ということなんですね。a-mac自体はマクロなので、引数を評価しないので、それは(a-fun a b)になる。そしてこのように変換された後に評価されるから、(a-fun a b)は上の関数呼び出しと同じで3になるじゃん、と思ったんですね。これが間違い。エラーです。

CL-USER> (a-mac a b)
; Evaluation aborted.
CL-USER>

エラーはこんな感じ。

`A' is not of the expected type `NUMBER'
[Condition of type TYPE-ERROR]

ちょっと考えてみればアタリマエなんですが、a-macの中のa-fun呼出しはマクロ展開関数の一部ですから、そのときの動作は、通常の関数呼出しの振舞いの定義に従うのではなく、あくまでマクロ展開関数の振舞いの定義に従うのですね。で、マクロ展開関数の中では、そもそもマクロの引数として渡された lisp form は評価されずにそのまま取り扱われるのです。関数がどんなに入れ子になっていてもマクロ展開関数の中ならば、ざっくり言うと、引数は評価されません。そのまま取り扱います。最終的にマクロ展開関数が返す lisp form が初めて評価されるのです(実行時環境に対して)。

ちなみに、上で間違いといった解釈に多少合致したマクロは次のものです。

CL-USER> (defmacro b-mac (x y)
(list 'a-fun x y))
B-MAC
CL-USER> (b-mac a b)
3
CL-USER>

ここでは、マクロ展開関数が(a-fun a b)というlisp formを返します。そのlisp formを評価するときは通常の関数呼出しですから、eagerに(a-fun 1 2)となり、3となります。

Common Lispの関数の引数は常にeagerに評価されると思っていたのが間違い。マクロ展開関数の中で呼出されるときは評価しないんですね!
うひー、恥ずかしい。。。でもよかった。

こつこつ。

0 件のコメント: