Scheme/syntax-rules
syntax-rulesは、R5RSで導入された衛生的マクロ(hygienic macro)である。衛生的なsyntax-rulesを使うことで、文脈に依存せず正しく動作するマクロを書くことができる。また、現実の問題として、R5RSで規格化されたマクロはsyntax-rulesしかないので、実装に依存しないマクロを書くためにsyntax-rulesを使う必要があるかもしれない。
純粋な衛生的なマクロは、伝統的なS式の変換によるマクロと比較して表現力が劣るが、それでも数多くのマクロを表現することができる。たとえば、伝統的マクロとして実装されているS式のパターンマッチマクロ match (Andrew Wrightのmatch) も、syntax-rulesのみを使った別実装がされている(Alex Shinn[1]による。)
衛生的マクロとは
[編集]パターン変数
[編集]キーワード
[編集]省略 (...)
[編集]再帰的マクロ
[編集]let-syntax
[編集]let-syntaxはその内部で使用されるマクロを定義する構文である。let-syntaxを使うことで、グローバル名前空間を汚染しない、その場限りのマクロを定義することができる。また、マクロの引数を利用して、新たなマクロを定義する場合にも使われる。
継続渡し
[編集]マクロの展開や特殊形式の認識は外側の式から順に行われるため、マクロ定義に、他のマクロの返り値(他のマクロによって展開されたプログラム)を直接埋め込むことはできない。
たとえば、以下のコードは(lambda (x y) (+ x y))に展開することを意図したものだが、(lambda (get-vars (x 'number) (y 'number)) (+ x y)) で展開が終了してしまい正しく動かない。
(define-syntax get-vars
(syntax-rules ()
((_ (var type) ...) (var ...))))
(define-syntax my-macro
(syntax-rules ()
((_ body args ...) (lambda (get-vars args ...) body))))
(my-macro (+ x y) (x 'number) (y 'number))
マクロの展開を次々と連鎖させるためには、マクロの展開形も(マクロ 引数 ...)という形になっていなければならない。 そこで、マクロを継続渡しスタイルで定義する。まず注意すべき点は、マクロの展開型として許されるのは構文だけであり、継続としてマクロ自身を直接扱うことはできないことである。したがって、マクロの引数とbodyを継続として扱い、その情報をもとにcpsマクロの内部で継続マクロを組み立てる必要がある。
以下が、cpsで書かれたマクロである。一見ややこしく見えるが、マクロの組み立てと適用の部分が煩雑なだけであり、本質は機械的なCPS変換である。
(define-syntax get-vars-cps
(syntax-rules (syntax-lambda)
((_ (syntax-lambda cont-args cont-body) (var type) ...)
(let-syntax
((cont-syntax
(syntax-rules ()
((_ cont-args) cont-body))))
(cont-syntax (var ...))))))
(define-syntax my-macro-cps
(syntax-rules (syntax-lambda)
((_ (syntax-lambda cont-args cont-body) body args ...)
(get-vars-cps
(syntax-lambda it
(let-syntax
((cont-syntax
(syntax-rules ()
((_ cont-args) cont-body))))
(cont-syntax ((lambda it body)))))
args ...))))
(define-syntax my-macro-2
(syntax-rules ()
((_ body args ...) (my-macro-cps (syntax-lambda (it) it) body args ...))))
これによって、展開自体は適切に行われる。
チューリング完全性
[編集]マクロの入力と、マクロの出力(マクロ展開形)に適当なマッピング(対応付け)を施すと、チューリング機械をエミュレートできる。これは、入力と出力の仔細にこだわらなければ、ありとあらゆるプログラム変換をマクロによってできるということを意味する。入力されるプログラムによってはマクロの展開が停止しない場合があるような変換も表現することができる。
この能力はマクロの再帰的呼び出しによってもたらされている。
制限
[編集]一方で、衛生的マクロであることから、(構文的)環境を変える変換を行うことができない。チューリングマシンの万能性からして制限があることを奇妙に思われるかも知れないが、これはチューリングマシンでテープアルファベット以外の文字を出力できないのと同じ道理である。
let-syntaxによる構文要素(型)の判定
[編集]let-syntaxによるマクロのその場定義を利用して、構文要素の型を判定することもできる。Oleg[2]のアイディアによる。
(define-syntax type-of
(syntax-rules ()
((_ ()) 'null)
((_ (expr ...)) 'pair)
((_ #(expr ...)) 'vector)
((_ symbol-or-literal)
(let-syntax
((test
(syntax-rules ()
((_ symbol-or-literal) 'symbol)
((_ _) 'literal) ; 数値・真偽値・文字・文字列のどれか
)))
(test match-me)))))
型の不明な構文 symbol-or-literal を新しく定義するマクロ test のパターン中に埋め込み、そのマクロの挙動をみてもとの構文を判断するという仕組みである。構文がシンボルなら、パターン中の構文は変数として作用するため、match-meのような任意のシンボルにマッチするという性質を使っている。
R5RSではこのように技巧的な手段が必要であるが、R6RSの衛生的マクロでは自然に定義できる。
- ^ synthcode (accessdate=2012-01-24)
- ^ http://okmij.org/ftp/Scheme/macros.html (accessdate=2012-01-24)