lisp-transformer
R6RSには、伝統的なマクロを作るためのlisp-transformerが紹介されている。
- http://github.com/okuoku/yunilibs/blob/master/lib/yuni/rnrs/ref/lisp-transformer.ss
- http://practical-scheme.net/wiliki/wiliki.cgi?R6RS%3A%E7%BF%BB%E8%A8%B3%3AStandard%20Libraries%3A12.6%20Syntax-object%20and%20datum%20conversions
lisp-transformer自体はR6RS標準には含まれていない。単に、syntax-caseの例として紹介されているに過ぎない。
通常の人間にとっては、define-syntaxの次にはsyntax-rulesを書くというのがイディオムとして定着しているので、lisp-transformerの使い方はなかなか解りづらい。
Wikipediaを見ると、他に様々なtransformerが使えることが解る。R6RSではこのうちsyntax-caseとsyntax-rulesを定義しているし、nmoshではer-macro-transformer(ライブラリ(explicit-renaming)にて提供)が使える。
同様に、lisp-transformerも、define-syntaxの直後に書くことで使える。
lisp-transformerを使って、yuniSchemeのdefine-table構文を実装してみた。(ただし、これは同じスロット名を持つtableを宣言すると間違ったことが起こる)
(define-syntax define-table (lisp-transformer (lambda (f) ...
fには、(define-table ...)となるようなリストがわたるので、それをパースして適当なプログラムを返す手続きを書けば良いことになる。
nmosh特有の注意として、ちゃんとnmoshのgensymを使わないと、意図しないシンボルの衝突が発生することがある。nmoshはライブラリをキャッシュするがgensymのカウンタまでは保存しない。よって、gensymを含んだライブラリをロードする度にカウンタはリセットされるものと考える必要がある。nmosh備え付けのgensymはその問題が起きないように配慮している。
また、lisp-transformer内で使用する手続きはexpandレベルで定義されている必要がある。平たく言えば、lisp-transformerの内部で宣言するか、外部のライブラリに宣言したうえで(import (for (some library) expand))のようにしてインポートしなければならない。
実はnmoshはsyntax objectを直接オブジェクトとして処理できるので、lisp-tranformerを使わずに直接手続きを書くという裏技もある。。このような性質はR6RSでは要求されていないので、psyntax moshとnmoshの違いの一つとなっている。
例えば次のスクリプトを考える :
(import (rnrs) (for (mosh pp) expand)) (define-syntax dump (lambda (f) (pp f)(newline))) (dump x y z)
$ nmosh t.scm (#[identifier] ←リストが出力される #[identifier] ← xを表す構文オブジェクト #[identifier] ← y #[identifier]) ← z $ mosh t.scm #[unknown] ← リストでないことに注意 Condition components: (変換器が未定義値を返したのでエラーが起こる)