ということがちょっとひっかかっています。というのは、前回の記事を書くときに、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 macro function
- the macroexpand hook は、the macro functionを呼び出す。その引数は、次の2つ。
- the entire macro form
- an environment object (lexical)
- the entire macro form
- 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済みである)」
ということですね。
うむ。重ねて恥しい。しかし、すっきりした。
こつこつ。
0 件のコメント:
コメントを投稿