背景とりとめのない
あなたが持っているのは非常に遅いバインディングマクロです。これは実行可能なアプローチですが、同じコードを繰り返し実行するとマクロが繰り返し展開されるため、非効率的です。
良い面として、これはインタラクティブな開発に適しています。プログラマーがマクロを変更し、それを使用するコード(以前に定義された関数など)を再度呼び出すと、新しいマクロが即座に有効になります。これは直感的な「私が言っていることをする」行動です。
以前にマクロを展開するマクロシステムでは、プログラマーは、マクロが変更されたときにマクロに依存するすべての関数を再定義する必要があります。そうしないと、既存の定義は引き続き古いマクロ展開に基づいており、新しいバージョンのマクロには気づきません。 。
合理的なアプローチは、解釈されたコードにはこの遅延バインディングマクロシステムを使用することですが、コンパイルされたコードには「通常の」(より適切な単語がないため)マクロシステムを使用することです。
マクロを展開するために、別の環境は必要ありません。ローカルマクロは変数と同じ名前空間にある必要があるため、そうではありません。たとえば、Common Lispでこれを行う(let (x) (symbol-macrolet ((x 'foo)) ...))
と、内側の記号マクロが外側の字句変数をシャドウイングします。マクロエクスパンダは、変数のバインド形式を認識している必要があります。およびその逆!let
変数の内部がある場合x
、それは外部をシャドウイングしsymbol-macrolet
ます。x
マクロエキスパンダーは、体内で発生するすべての発生を盲目的に置き換えることはできません。つまり、Lispマクロ拡張は、マクロと他の種類のバインディングが共存する完全な字句環境を認識している必要があります。もちろん、マクロ展開中に、同じ方法で環境をインスタンス化することはありません。もちろん、もしあれば(let ((x (function)) ..)
、(function)
は呼び出されx
ず、値も指定されません。ただし、マクロエクスパンダはx
、この環境にが存在することを認識しているため、の発生はx
マクロではありません。
したがって、1つの環境と言うとき、私たちが実際に意味するのは、統合された環境には2つの異なる表現またはインスタンス化があるということです。拡張時間の表現と評価時間の表現です。実行時バインディングマクロは、これまでのように、これら2回を1つにマージすることで実装を簡素化しますが、そのようにする必要はありません。
Lispマクロはパラメータを受け入れることができることにも注意して&environment
ください。macroexpand
これは、マクロがユーザーによって提供されたコードの一部を呼び出す必要がある場合に必要です。マクロを介してマクロエキスパンダーに戻るこのような再帰は、適切な環境を通過する必要があります。これにより、ユーザーのコードは、字句的に囲まれたマクロにアクセスし、適切に展開されます。
具体例
次のコードがあるとします。
(symbol-macrolet ((x (+ 2 2)))
(print x)
(let ((x 42)
(y 19))
(print x)
(symbol-macrolet ((y (+ 3 3)))
(print y))))
これがプリント4
に与える影響42
、、6
。Common LispのCLISP実装を使用し、CLISPの実装固有の関数であるを使用してこれを拡張してみましょうsystem::expand-form
。macroexpand
ローカルマクロに再帰されないため、通常の標準を使用することはできません。
(system::expand-form
'(symbol-macrolet ((x (+ 2 2)))
(print x)
(let ((x 42)
(y 19))
(print x)
(symbol-macrolet ((y (+ 3 3)))
(print y)))))
-->
(LOCALLY ;; this code was reformatted by hand to fit your screen
(PRINT (+ 2 2))
(LET ((X 42) (Y 19))
(PRINT X)
(LOCALLY (PRINT (+ 3 3))))) ;
(まず、これらのlocally
フォームについて。なぜそこにあるのですか?symbol-macrolet
これはおそらく宣言のためです。symbol-macrolet
フォームの本体に宣言がある場合は、その本体にスコープする必要があります。 、およびそれlocally
を実行します。の展開でsymbol-macrolet
このラッピングが残らない場合、locally
宣言のスコープは正しくありません。)
このマクロ展開から、タスクが何であるかを確認できます。マクロエクスパンダは、コードをウォークして、マクロシステムに関係するバインディングコンストラクトだけでなく、すべてのバインディングコンストラクト(実際にはすべての特殊な形式)を認識する必要があります。
のインスタンスの1つがその(print x)
ままになっていることに注意してください:のスコープ内にあるもの(let ((x ..)) ...)
。もう1つは(print (+ 2 2))
、のシンボルマクロに従って、になりましたx
。
これから学ぶことができるもう1つのことは、マクロ展開は展開を置き換えてsymbol-macrolet
フォームを削除するだけであるということです。したがって、残っている環境は元の環境から、拡張プロセスでスクラブされたすべてのマクロマテリアルを除いたものになります。マクロ展開は、1つの大きな「大統一」環境ですべての字句バインディングを尊重(print (+ 2 2))
しますが、その後、丁寧に気化して、のようなコードと他の痕跡だけを残し(locally ...)
、非マクロバインディング構造だけが元の環境。
したがって、拡張されたコードが評価されると、縮小された環境の実行時のパーソナリティだけが機能します。let
バインディングはインスタンス化され、初期値などが詰め込まれます。拡張中は、そのいずれも発生しませんでした。非マクロバインディングは、そのスコープを主張し、実行時の将来の存在を示唆するだけです。