2010年2月3日水曜日

Lisp Quote Backquote (5)

「マクロ展開関数の中での関数呼出しにおいて、引数は評価されない」

ということがちょっとひっかかっています。というのは、前回の記事を書くときに、ANSI Common Lisp 仕様のマクロ展開の部分はざっと確認したのですが、なんとなくそこの記述が曖昧な気がしたのですね。それを確認しようと思います。こういうのを重箱の隅をつつくような、というのかなぁ。まあ、仕事ではなく、好きでやっているので、やりたいようにやっちゃおう。

さて、ANSI Common Lisp の該当部分、

3.1.2.1.2.2 Macro Forms

に書かれていることの骨子は次のとおりです。


  • a form の第一要素たるsymbolについて、macro-functionがtrueならば、そのformを a macro form とみなす。
  • macro-functionがtrueであるとき、macro-functionが返すのはa macro functionである。

    CL-USER> (macro-function 'a-mac)
    #<Interpreted Function A-MAC @ #x1000c669d2>
    CL-USER> (macro-function 'a-fun)
    NIL
    CL-USER>

  • まず、the macroexpand hookを呼び出す。その引数は、次の3つ。

    • the macro function
    • the entire macro form
    • an environment object (lexical)

  • the macroexpand hook は、the macro functionを呼び出す。その引数は、次の2つ。

    • the entire macro form
    • an environment object (lexical)

  • the macro funcitionの値は、a formとして扱う。
  • the macroexpand hookは、the formをパススルーする。
  • the form が the original formの代わりに評価される。


ふむ。曖昧、というか、少くともここには、macro functionsの引数評価方法が通常の関数呼出しとは違うとは、字句としては書かれていない
そこで、続いて、macroexpand hookを調べてみよう。

3.8.15 *macroexpand-hook*

ふむ。ここには、新しい情報は無いな。。。

上で書いた骨子は、「評価モデル」の話です。評価モデルは、意味(意味論, semantics)の一部を説明するためのものであり、それがすべてではありません。そこで、Compilationでは何をどう述べているのか確認してみましょう。

3.2.2.2 Minimal Compilation

この2つめの項目で言及されているのは、compile time にマクロの展開は全部やっちゃいますよ、ということです。このことはよく知っているつもりでしたが、もしかして、このことに集約されてしまうのかな?

コンパイルにおけるcompile timeの定義というか振舞いは次の部分に記述されています。

3.2.1 Compiler Terminology

うーん。ここでは、各種環境の定義とその利用についてが書かれているだけですね。

とすると、評価モデルの説明のところにて、字句としては書かれていませんが、何かimplicationがあるのでしょうか。。。




あ゛、、、、




評価モデルのところ(3.1.2.1.2.2 Macro Forms)のこの文、

`The expansion function is invoked by calling the macroexpand hook with the expansion function as its first argument, the entire macro form as its second argument, and an environment object (corresponding to the current lexical environment) as its third argument.'

で第二引数として`the entire macro form'を渡すと言っているところに含意があるんだ。だって、そこで評価しちゃったらマクロの展開は無限ループだ。。。だから、quoteして渡されるんですね。ここで勝負が着いているのだな。

勝負が着いているというのは、前回の記事に書いた「関数呼出しを入れ子にしてもマクロ展開関数内では評価されない」というのは見た目としては通常の関数呼出しでも同じあり、しかも「評価されない」のではなく、「評価された結果としてそう振舞う」ということなんですね。この点も前回の記事は間違っています。確認してみましょう。

CL-USER> (defun fun-1 (x)
(fun-2 x))
FUN-1
CL-USER> (defun fun-2 (x)
(list x))
FUN-2
CL-USER> (fun-1 'a)
(A)

これ、(fun-1 'a)をまず読み込みます。a cons form で operator が function なので、先に引数が評価されます。それは a symbol object を返します。その値にて関数本体を実行します。fun-1の関数本体は(fun-2 x)です。さて、ここで、(fun-2 <a symbol object>)となると考えてしまうのが間違えなんですね。引数は束縛なので、xという名前がa symbol objectに束縛されているというレキシカル環境ができます。そこで、(fun-2 x)が評価されるので、eagerに評価された結果はその束縛によりa symbol objectなわけですね。これも基礎中の基礎、だな。。。

まとめると、

「関数の振舞いは、マクロ展開関数の中での呼出しも、通常の呼出しも、同じである。ただし、マクロ展開フックがマクロ展開関数を呼び出すときに、引数に置くフォームをquoteする(もしくはquote済みである)」

ということですね。

うむ。重ねて恥しい。しかし、すっきりした。

こつこつ。

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に評価されると思っていたのが間違い。マクロ展開関数の中で呼出されるときは評価しないんですね!
うひー、恥ずかしい。。。でもよかった。

こつこつ。