以下のフォームはtransformer-specで作られるマクロ変換器と nameの束縛を作ります。外側のスコープにnameの束縛があれば、 それはシャドウされます。
トップレベル束縛の場合、nameに他のモジュールからインポートされたり 継承されている束縛があれば、それをシャドウすることになります (モジュール参照)。 (註:モジュール内でのトップレベル束縛がインポートした束縛をシャドウするのは Gaucheの拡張です。R7RSではインポートした束縛の再定義はしてはいけないことに なっているので、ポータブルなコードでは避けて下さい)。
同じスコープで同じ名前を複数回束縛した場合の動作は未定義です。
transformer-specはsyntax-rules
フォーム、
er-macro-transformer
フォーム、あるいは他のマクロキーワードか
構文キーワードです。これについては後述します。
[R7RS base] トップレベルで使われた場合、このフォームはトップレベルのnameを transformer-specで定義されるマクロ変換器に束縛します。
lambda
、let
等の本体の宣言部分に使われた場合 (内部define-syntax
)、
その本体内のスコープでnameを束縛します。
概念的には、同一階層にある内部define-syntax
はまとめられて
letrec-syntax
のように振る舞います。ただし、define-syntax
が出てきたところで
新たなスコープが作られるわけではありません。
例えば内部define
と内部define-syntax
を一つのスコープ内で混ぜこせに
並べることができます。重要なのは、内部define-syntax
で定義されるローカルマクロは、
その定義前にマクロ展開に必要とされてはならない、ということです。
[R7RS base]
ローカルマクロを定義します。各nameが
対応するtransformer-specで定義されるマクロ変換器へと束縛された
環境を作りbodyを評価します。
let-syntax
では、transformer-specはlet-syntax
を
囲むスコープ内でtransformer-specを評価するのに対し、
letrec-syntax
ではnameの束縛がなされた環境で
transformer-specを評価します。つまりletrec-syntax
は
相互再帰的なマクロを定義できます。
transformer-specは、マクロ展開器へと評価される特別な式です。 マクロ変換器はコンパイル時に実行されるため、他の式とは異なった段階で評価されます。 そのためにいくらか制限があります。
現在のところ、以下に挙げる式しか許されていません。
syntax-rules
フォーム。これは「高レベル」マクロと呼ばれ、
パターンマッチングのみによってマクロを定義します。
これはSchemeとは異なる一種の宣言的言語で、
マクロの段階や衛生の問題をボンネットの下に隠してしまいます。
ある種のマクロはsyntax-rules
でより簡単に書けます。
詳しくはSyntax-rulesマクロ変換器を参照してください。
er-macro-transfomer
フォーム。
これはexplicit renaming(ER)マクロを定義します。
ERマクロでは、必要な衛生を保ちながら、任意のSchemeコードを使って変換を書けます。
伝統的なLispのマクロは、ERマクロでリネームを使わない特別な場合と考えられます。
詳しくはExplicit-renamingマクロ変換器を参照してください。
make-id-transformer
フォーム。
これは識別子マクロを定義します。
通常のマクロと異なり、識別子マクロはリストの最初の位置に置かれないでも展開されます。
ソース上では通常の変数のように見えます。
詳しくは識別子マクロ変換器を参照してください。
(define-syntax si if) (define écrivez write) (si (< 2 3) (écrivez "oui"))
• Syntax-rulesマクロ変換器: | ||
• Explicit-renamingマクロ変換器: | ||
• 識別子マクロ変換器: |
[R7RS base] パターンマッチングによるマクロ変換器を作ります。
各clauseは次の形式です。
(pattern template)
patternはマクロ呼び出しにマッチすべきパターンを記述します。 パターンはS式で、マクロ呼び出しの式と同じ構造を持っている場合にマッチします。 但し、パターン中のシンボルはパターン変数と呼ばれ、 マクロ呼び出し式の対応する任意の部分木とマッチし、 templateの中でマッチした部分木を参照するのに使えます。
例えば、パターンが(_ "foo" (a b))
であったとすると、それは
(x "foo" (1 2))
や(x "foo" (1 (2 3)))
といったマクロ呼び出しとマッチしますが、
(x "bar" (1 2))
、(x "foo" (1))
、(x "foo" (1 2) 3)
とは
マッチしません。
さらに、後で説明するように、繰り返しのある構造やリテラルシンボルとマッチするような記述も可能です。
clauseは順番に、そのパターンにマクロ呼び出しとマッチするかが検査されます。 マッチするパターンが見つかれば、対応するtemplateでマクロ呼び出しの式が 置き換えられます。template中のパターン変数は、 マクロ呼び出し式のその変数にマッチした部分木で置き換えられます。
これはなぜ衛生的マクロかで例に出したwhen
マクロを
syntax-rules
で書いたものです:
(define-syntax when (syntax-rules () [(_ test body ...) (if test (begin body ...))]))
パターンが(_ test body ...)
で、
テンプレートが(if test (begin body ...))
です。
...
(エリプシス) は、記述を省略しているわけではなく、
ピリオド3つからなる名前を持つシンボルです。
これは直前のパターン(body
)がゼロ個以上繰り替えされるということを示します。
when
マクロが
(when (zero? x) (print "huh?") (print "we got zero!"))
という形で呼び出されたとしましょう。
マクロ展開器はまず、この入力がパターンとマッチするかどうかを調べます。
(zero? x)
とマッチ。
(print "huh?")
および(print "we got zero!")
とマッチ
bodyとのマッチングはちょっとややこしいです。 パターン変数bodyは配列のようなものだと考えても良いでしょう。 配列の各要素がマッチする入力の部分木を保持します。 その値は、テンプレート中の似たような繰り返し部分構造の中で使うことができます。 ここまでで入力がパターンにマッチしたので、テンプレートの方を見てみましょう。
if
とbegin
はパターン中に現れていないので
パターン変数ではありません。従って、識別子として出力に挿入されます。
ここで、識別子if
やbegin
はこのマクロのスコープから見えるグローバルな
if
やbegin
を常に参照できるように、衛生的に扱われます。
マクロが使われた場所でif
やbegin
がシャドウされていたとしても影響を受けません。
(zero? x)
へと
置き換えられます。
(print "huh?")
と、次の
(print "we got zero!")
とがここに展開されます。
(if (zero? x) (begin (print "huh?") (print "we got zero!")))
が得られます (このうち、if
とbegin
は
マクロ定義環境から見える識別子を指すようになっています)。
エリプシスを使った展開はかなり強力です。 テンプレート中で、エリプシスは複数の値を持つパターン変数の直後にある必要はありません。 そういった変数を中に含む部分構造をエリプシスで繰り返すことも可能です。 次の例を見てください。
(define-syntax show (syntax-rules () [(_ expr ...) (begin (begin (write 'expr) (display "=") (write expr) (newline)) ...)]))
このマクロを次のように呼ぶと:
(show (+ 1 2) (/ 3 4))
以下のとおりに展開されます (シンボルの衛生性は保たれているとします)。
(begin (begin (write '(+ 1 2)) (display "=") (write (+ 1 2)) (newline)) (begin (write '(/ 3 4)) (display "=") (write (/ 3 4)) (newline)))
これを実行すれば、以下の出力が得られるでしょう。
(+ 1 2)=3 (/ 3 4)=3/4
また、パターン中の部分構造を繰り返しマッチするのにも使えます。
次の例はlet
をlambda
に展開する、簡略化した例です:
(define-syntax my-let (syntax-rules () [(_ ((var init) ...) body ...) ((lambda (var ...) body ...) init ...)]))
このマクロを(my-let ((a expr1) (b expr2)) foo)
のように呼び出すと、
varはa
およびb
に、
initはexpr1
およびexpr2
にそれぞれマッチします。
varとinitはテンプレート中でばらばらに使うことができます。
パターン変数の繰り返しを示すエリプシスの入れ子の数を、
そのパターン変数のレベルと呼ぶことにします。
サブテンプレートは、その中に含まれるパターン変数のレベルの最大値と同じだけの
エリプシスの入れ子に中になければなりません。
次の例では、パターン変数a
のレベルは1 (最後のエリプシスによって繰り返される)、
b
は2 (後ろの2つのエリプシスで繰り返される)、
c
は3 (全てのエリプシスで繰り返される) です。
(define-syntax ellipsis-test (syntax-rules () [(_ (a (b c ...) ...) ...) '((a ...) (((a b) ...) ...) ((((a b c) ...) ...) ...))]))
したがって、サブテンプレート a
は1重、
サブテンプレート (a b)
は2重、
(a b c)
は3重のエリプシスで繰り返されることになります。
(ellipsis-test (1 (2 3 4) (5 6)) (7 (8 9 10 11))) ⇒ ((1 7) (((1 2) (1 5)) ((7 8))) ((((1 2 3) (1 2 4)) ((1 5 6))) (((7 8 9) (7 8 10) (7 8 11)))))
また、サブテンプレートの後ろには複数のエリプシスを直接置くことができ、 繰り返しの「葉」の部分がそこにスプライスされます。
(define-syntax my-append (syntax-rules () [(_ (a ...) ...) '(a ... ...)])) (my-append (1 2 3) (4) (5 6)) ⇒ (1 2 3 4 5 6) (define-syntax my-append2 (syntax-rules () [(_ ((a ...) ...) ...) '(a ... ... ...)])) (my-append2 ((1 2) (3 4)) ((5) (6 7 8))) ⇒ (1 2 3 4 5 6 7 8)
註:サブテンプレートの直後に複数のエリプシスを置くこと、
及びパターン変数をそのレベルよりもエリプシスのネストが深いテンプレート中に置けることは、
R7RSに対する拡張で、SRFI-149で定義されています。上記の例では、
ellipsis-test
、my-append
、my-append2
が
R7RSの範囲外になります。
パターン中に現れる識別子はパターン変数として扱われますが、
特定の識別子そのものにマッチさせたい場合もあります。例えば組み込みのcond
や
case
は、else
という識別子を特別に認識します。
literal …がその目的に使えます。次の例を見てください。
(define-syntax if+ (syntax-rules (then else) [(_ test then expr1 else expr2) (if test expr1 expr2)]))
リテラルとして列挙された識別子はパターン変数にはならず、入力の識別子とそのままマッチします。 もし入力の該当する位置に同じ識別子が置かれていなければ、マッチは失敗します。
(if+ (even? x) then (/ x 2) else (/ (+ x 1) 2))
expands into (if (even? x) (/ x 2) (/ (+ x 1) 2))
(if+ (even? x) foo (/ x 2) bar (/ (+ x 1) 2))
⇒ ERROR: malformed if+
これまで、シンボルと呼ばずに識別子という言葉を使っていました。大まかに言うと、 識別子とはシンボルに周囲の構文的環境をくっつけたもので、 衛生的マクロによるリネームによっても同一性を失わないようになっています。
次の例は失敗します。if+
に渡されているelse
はlet
でローカルに
束縛されており、それはif+
が定義された時点でのグローバルなelse
とは
違うもので、したがってマッチしないからです。
(let ((else #f)) (if+ (even? x) then (/ x 2) else (/ (+ x 1) 2)) ⇒ ERROR: malformed if+
procedure-exprからマクロ変換器を作ります。
作られたマクロ変換器は、define-syntax
、let-syntax
、
letrec-syntax
により構文キーワードに束縛されなければなりません。
マクロ変換器の他の用途は定義されていません。
procedure-exprは3つの引数、form、rename、id=?を 取る手続きへと評価される式です。
form引数には、マクロ呼び出しのS式そのものが渡されます。
procedure-exprはマクロ展開の結果をS式として返します。
この点は、伝統的なマクロとよく似ています。実のところ、
renameとid=?を無視すれば、セマンティクスは伝統的な(非衛生な)マクロと
同じになります。次の例を見てください
(この例ではmatch
を使っています。マクロの入力を分解するのにも
手軽なツールです。)
(use util.match) ;; Unhygienic 'when-not' macro (define-syntax when-not (er-macro-transformer (^[form rename id=?] (match form [(_ test expr1 expr ...) `(if (not ,test) (begin ,expr1 ,@expr))] [_ (error "malformed when-not:" form)])))) (macroexpand '(when-not (foo) (print "a") 'boo)) ⇒ (if (not (foo)) (begin (print "a") 'boo))
衛生を気にする必要がない場合は、これでも十分です。
例えばマクロを自分で書いたコードの中だけで使い、
すべてのマクロ呼び出しを把握していて名前の衝突が起きないことを知っている場合です。
けれども、このwhen-not
マクロを広く使えるようにするなら、
マクロの使われる場所での名前の衝突からの防御が必要です。
たとえば、次のとおり呼び出されたとしてもちゃんと動くようにしたい場合です。
(let ((not values)) (when-not #t (print "This shouldn't be printed")))
procedure-exprに渡されるrename引数は、 シンボル(正確には、シンボルか識別子)を取り、それをマクロ定義時の環境を保持する ユニークな識別子へと実質的にリネームする手続きです。 リネームされた識別子はマクロ使用時の環境には影響を受けません。
大雑把なルールとして、マクロの出力に挿入する識別子はすべてrenameを通すことを
徹底すれば、衛生は保たれます。when-not
マクロの例では、
マクロの出力にif
、not
、begin
を挿入していますから、
衛生的なバージョンは次のとおり書けます。
(define-syntax when-not (er-macro-transformer (^[form rename id=?] (match form [(_ test expr1 expr ...) `(,(rename 'if) (,(rename 'not) ,test) (,(rename 'begin) ,expr1 ,@expr))] [_ (error "malformed when-not:" form)]))))
でもこれは面倒ですし読みづらいですね。そこでGaucheでは、
補助マクロquasirename
を用意しています。これはquasiquote
のように
動作しますが、フォーム中の識別子をリネームしてゆきます。詳しくは後述の
quasirename
のエントリを参照してください。quasirename
を使うと
衛生的なwhen-not
はこうなります:
(define-syntax when-not (er-macro-transformer (^[form rename id=?] (match form [(_ test expr1 expr ...) (quasirename rename `(if (not ,test) (begin ,expr1 ,@expr)))] [_ (error "malformed when-not:" form)]))))
シンボルをリネームせずに挿入すれば、意図的に衛生を破ることができます。
次のコードはアナフォリック(前方照応的)なwhen
を定義しています。
つまり、テスト式の結果が、expr1 exprs … からit
という
変数で参照できるということです。
it
の束縛はマクロ呼び出し箇所には無かったもので、
マクロ展開器により挿入されるので、これは非衛生マクロになります。
(define-syntax awhen (er-macro-transformer (^[form rename id=?] (match form [(_ test expr1 expr ...) `(,(rename 'let1) it ,test ; 'it' is not renamed (,(rename 'begin) ,expr1 ,@expr))]))))
quasirename
を使う場合、it
がリネームされないようにするには
,'it
と書きます。
(define-syntax awhen (er-macro-transformer (^[form rename id=?] (match form [(_ test expr1 expr ...) (quasirename rename `(let1 ,'it ,test (begin ,expr1 ,@expr)))]))))
使用例を見てみましょう。
(awhen (find odd? '(0 2 8 7 4))
(print "Found odd number:" it))
⇒ prints Found odd number:7
最後に、procedure-exprのid=?引数はふたつの引数を取り、
それらがともに識別子であって、しかも同じ束縛を参照するか束縛されていないか、という
場合に限り#t
を返します。
これはリテラル構文キーワード(cond
やcase
フォームのelse
等)
を比較するのに使えます。
下のif=>
マクロはif
と同じように動作しますが、
(if=> test => procedure)
のように呼ばれた場合、
(cond [test => procedure])
構文と同じように、
test
が真の値を返した際には結果を引数にしてprocedure
を呼び出します。
シンボル=>
は衛生的に比較されます。つまり、マクロ定義時と同じ束縛を
参照している場合にのみ有効となります。
(define-syntax if=> (er-macro-transformer (^[form rename id=?] (match form [(_ test a b) (if (id=? (rename '=>) a) (quasirename rename `(let ((t ,test)) (if t (,b t)))) (quasirename rename `(if ,test ,a ,b)))]))))
(rename '=>)
とすることで、マクロ定義時における=>
の束縛を
参照する識別子を手に入れ、id=?
でそれをマクロ引数から渡された式と
比較しています。
(if=> 3 => list) ⇒ (3) (if=> #f => list) ⇒ #<undef> ;; 第二引数が=>でなければ、if=>は通常のifと同じ: (if=> #t 1 2) ⇒ 1 ;; 下の例ではマクロ使用時の=>の束縛がマクロ定義時の束縛と違っているため、 ;; => はリテラルと認識されず、if=> は通常のifとして振る舞う。 (let ((=> 'oof)) (if=> 3 => list)) ⇒ oof
form中の「リテラル」な部分 (unquote
やunquote-splicing
の外側)
に現れるシンボルや識別子がrenameによってリネームされることを除いて、
準クオートのように動作します。
quasiquote-form引数は、準クオートされたフォームです。
最も外側の準クオート`
自体はquasirename
によって消費され、
出力には現れません。
この形にしたのは、ネストしたquasiquote
/quasirename
を
正しく扱うためです。
例えば次のフォームは:
(quasirename r `(a ,b c "d"))
次のとおり書くのと同じです:
(list (r 'a) b (r 'c) "d")
この手続きはマクロ専用というわけではありません。 renamerはシンボルか識別子を取る手続きであれば何でも構いません。
(quasirename (^[x] (symbol-append 'x: x)) `(+ a ,(+ 1 2) 5)) ⇒ (x:+ x:a 3 5)
ただ、ERマクロを書く際にとても便利なのは確かです。次の2つを比べてみてください。
(use util.match) ;; using quasirename (define-syntax swap (er-macro-transformer (^[f r c] (match f [(_ a b) (quasirename r `(let ((tmp ,a)) (set! ,a ,b) (set! ,b tmp)))])))) ;; not using quasirename (define-syntax swap (er-macro-transformer (^[f r c] (match f [(_ a b) `((r'let) (((r'tmp) ,a)) ((r'set!) ,a ,b) ((r'set!) ,b (r'tmp)))]))))
註: Gauche 0.9.7とそれ以前には、quasirename
は第二引数に
準クオートを必要としませんでした。つまり(quasirename r `form)
と書くかわりに(quasirename r form)
で良かったのです。
互換性のため、しばらくは準クオート無しのフォームも受け付けられます。
準クオートされたフォームを返すことを意図していた既存のコードについては、
準クオートを(quasirename r ``form)
のように重ねる必要があります。
移行を容易にするため、quasirename
中の準クオートの扱いを、
環境変数GAUCHE_QUASIRENAME_MODE
でカスタマイズできます。
次に挙げる値を設定することができます。
legacy
quasirename
は、0.9.7やそれ以前と全く同様に動きます。
0.9.7用のコードを変更なしに動かす必要がある場合に使ってください。
compatible
quasirename
はこのエントリで説明した通りに動きます。
formが準クオートされていない場合、準クオートされているものと見なします。
準クオートされたフォームを作り出すことを意図している稀なケース以外の
既存のコードはこれで動かせるでしょう。
warn
quasirename
はこのエントリで説明した通りに動きますが、
formが準クオートされていなければ警告を出します。
strict
quasirename
はformが準クオートされていることを要求し、
そうでなければエラーを投げます。将来はこの動作がデフォルトになる予定です。
transformer-specから識別子マクロ変換器を作ります。
transformer-specはdefine-syntax
等で使えるものと同じです。
通常のマクロは、M
をマクロが束縛された識別子とすると、(M arg …)
という
形式でマクロ変換器を呼び出します。一方識別子マクロは、マクロに束縛された識別子単独、
もしくは(set! M expr)
という形式でマクロ変換器を呼び出すものです。
言い換えれば、識別子マクロは、関数呼び出し形式ではなく変数参照形式で使われるものです。
次のコードを考えます。state-manager
は状態を持つクロージャで、
識別子マクロthe-state
はクロージャを隠して
あたかも変数にアクセスしているかのように見せます。
(define state-manager (let ([state #f]) (case-lambda [() state] [(val) (set! state val)]))) (define-syntax the-state (make-id-transformer (syntax-rules (set!) [(set! _ expr) (state-manager expr)] [_ (state-manager)]))) (state-manager 'off) the-state ⇒ off (set! the-state 'on) the-state ⇒ on (state-manager) ⇒ on
(上のsyntax-rules
の二番目の節のパターンは_
のみから
なりますが、これはGaucheの拡張で、任意のフォームにマッチします。
したがってset!
のマッチより後に置く必要があります)。
識別子マクロを使うとアクロバティックなことができますが、 すぐに読者を混乱させるコードになってしまいます。 一般的に、どうしても識別子マクロでなければ実現できないこと以外には使用を避けることをおすすめします。 ほとんどの場合、カッコを足して通常のマクロとして使うのが良いでしょう。
移植性が気になる場合は、util.identifier-syntax
モジュールの
identifier-syntax
が使えるかもしれません
(util.identifier-syntax
- R6RS識別子マクロ参照)。
これはR6RSで規定されているので、サポートしている処理系も多いでしょう。
Gaucheではmake-id-transformer
を使って
util.identifier-syntax
を実装しています。