R6RS syntax-case 読解メモ
TechR6RSの読解メモです。
syntax-case
(rnrs syntax-case (6))
の機能- 低レベルマクロ
- 高レベルっぽいスタイル
- automatic syntax checking
- input destructuring (パターンマッチ)
- output restructuring (quasisyntaxとか)
- maintenance of lexical scoping and referential transparency (hygiene)
- controlled identifier capture (
aif
のit
みたいに、意図的に名前を衝突させるやつ)
12.1 Hygiene
- hygiene: 式Nを式Mに埋め込んだとき、N内の自由変数がM内のbindingと意図せず衝突しないこと
- マクロ展開時に生成された識別子が、同じtranscription stepで生成された変数にのみbindされること
This leaves open what happens to
an introduced identifier that appears outside the scope of
a binding introduced by the same call.
Such an identifier refers to
the lexical binding in effect where it appears
(within a syntax <template>; see section 12.4)
inside the transformer body or one of the helpers it calls.
This is essentially the referential transparency property described by Clinger and Rees [3].
Thus, the hygiene condition can be restated as follows:
A binding for an identifier
introduced into the output of a transformer call from the expander
must capture only references to the identifier
introduced into the output of the same transformer call.
A reference to an identifier
introduced into the output of a transformer
refers to the closest enclosing binding for the introduced identifier
or, if it appears outside of any enclosing binding for the introduced identifier,
the closest enclosing lexical binding
where the identifier appears (within a syntax <template>)
inside the transformer body or one of the helpers it calls.
- 明示的にcaptureさせたい場合は
datum->syntax
を使う
markとsubstを使って実装できるよという話(ここですることじゃなくない??助かるけども…)
- mark
- expanderがtransformerを起動するたびに付けるもの
- 同じ名前の識別子を区別するため
- subst: portions of each binding formに適用される(??)
- 識別子とexpand-time valueを結びつけるもの
- expanderがマクロ使用箇所を見つけたときは以下を行う
- 入力にantimarkを付ける
- マクロのtransformerを実行
- 出力に新しいmarkを付ける
- markとantimarkはキャンセルするので、このステップで増えたやつだけマークしたことになる
- expanderがbinding formを見つけたときは以下を行う
- 新しい名前の数だけsubstを作る
- 各substは、識別子と、その束縛に関するメタ情報を持つ
- binding formは、新しい名前を導入するやつのこと。lambdaとかlet-syntaxとか
- markとsubstをあわせたものをwrapという
- S式にwrapを付けたものをwrapped syntax objectという
- 特に、1つのシンボルにwrapを付けたものをidentifierという (typicallyって書いてるから、シンボル以外をidentifierにする実装も考えられるのかな?)
- identifierとexpand-time valueを結びつけるためにsubstが作られたとき、substにはその時点でidentifierに付いていたmarkの情報も記録される
- expanderがidentifierからbindingを探すときは、まずidentifierに適用するsubstを探すことになる
- 探すsubstは、wrap内の名前とmarksが一致する最も外側のsubst
- 名前は、例えばシンボルならeq?で比較する
- marksは、wrap内のそのsubstより前のmarksがマッチ対象となる。substより後ろのmarksは関係ない。
12.2 Syntax objects
- syntax object: S式にメタ情報を付加したもの
- メタ情報はhygiene用の情報のほか、行番号とかを入れてもよい
- formalには以下のいずれか
- non-wrapped:
- a pair of syntax objects
- a vector of syntax objects
- pair/vector/symbol以外の値 (bool, number, string, char)
- wrapped:
- syntax objectにwrapを付けたもの
- つまり生のsymbolはsyntax objectではない
- symbolだけでは他の名前と区別できないので、メタ情報(wrap)を付けてsyntax objectにする
12.3 Transformers
define-syntax
,let-syntax
,letrec-syntax
を使うと、syntatic keyword(マクロ名)から transformer(マクロ変換器)への束縛ができる- transformerには以下の2種類がある
- transformation procedure(普通のマクロ)
- 1引数の手続き。syntax objectを受け取り、syntax objectを返す
- keyword(マクロ名)を
set!
の第一引数にした場合は例外 - variable transformer(変数参照がマクロ呼出しになるやつ)
- keyword(マクロ名)を
set!
の第一引数にしてよい(後述)
(make-variable-transformer proc) procedure
proc
を受け取り、variable transformerを返すproc
は1引数の手続きで、syntax objectを受け取りsyntax objectを返す- (keyword(マクロ名)はdefine-syntax等と組み合わせて指定する)
- keyword(マクロ名)が
set!
の第一引数であった場合、(set! <keyword> <value-expr>)
を表すsyntax objectがproc
に渡され、proc
の返り値がset!
の呼び出しを置き換える
12.4 Parsing input and producing output
syntax-case
の説明。全体は、以下の形をしている
(syntax-case <expression> (<literal> ...)syntax
<syntax-case clause> ...)
_auxiliary syntax
...auxiliary syntax
literal
は識別子(ただし...
と_
は許されない)。clause
は以下のいずれか。
(<pattern> <output expression>)
(<pattern> <fender> <output expression>)
fender
とoutput expression
は単一の式。pattern
は以下のいずれか
(<pattern> ...)
(<pattern> <pattern> ... . <pattern>)
(<pattern> ... <pattern> <ellipsis> <pattern> ...)
(<pattern> ... <pattern> <ellipsis> <pattern> ... . <pattern>)
#(<pattern> ...)
#(<pattern> ... <pattern> <ellipsis> <pattern> ...)
ellipsis
は...
という識別子。pattern
内では、_
と...
とliteral
に書いたものが識別子と見なされ、それ以外の識別子はパターン変数(pattern variable)と見なされる。_
と...
の意味は、syntax-rulesと同じ。- ある
pattern
に同じパターン変数が複数回あってはいけない。(_
はパターン変数でないので複数回あってもよい。) _
は任意のsubformにマッチするが、パターン変数ではないので、マッチ結果を参照することはできない。- literal identifierがinput subformにマッチするのは、input subformもidentifierであり、かつ
- both its occurrence in the input expression and its occurrence in the list of literals have the same lexical binding か、
- 両者が同じ名前かつどちらもlexical bindingを持っていないとき。
- subpatter+
...
は、0個以上の入力にマッチする。
より形式的な定義 (入力FがパターンPにマッチする条件)
- Pが
_
かパターン変数のとき。 - Pがliteral identifierで、Fと
free-identifier=?
で等しいとき。 - Pが
(P1 〜 Pn)
という形で、Fが同じ長さのリストで、各要素がP1〜Pnにマッチするとき。 - Pが
(P1 〜 Pn . Px)
という形で、Fがn要素以上の(maybe improper)listで、 最初のn要素がP1〜Pnにマッチし、n番目のcdrがPxにマッチするとき。 - Pが
(P1 〜 Pk Pe ... Pm+1 〜 Pn)
という形で、Fがn要素のリストで、 最初のk要素がP1〜Pkにマッチし、続く(m-k)要素がPeにマッチし、残りの(n-m)要素がPm+1〜Pnにマッチするとき。 - Pが
(P1 〜 Pk Pe ... Pm+1 〜 Pn . Px)
という形で、Fがn要素の(maybe improper)リストで、 最初のk要素がP1〜Pkにマッチし、続く(m-k)要素がPeにマッチし、続く(n-m)要素がPm+1〜Pnにマッチし、 最後(n番目)の要素のcdrがPxにマッチするとき。 - Pが
#(P1 〜 Pn)
という形で、Fがn要素のベクタで、各要素がP1〜Pnにマッチするとき。 - Pが
#(P1 〜 Pk Pe ... Pm+1 〜 Pn)
という形で、Fがn要素以上のベクタで、 最初のk要素がP1〜Pkにマッチし、続く(m-k)要素がPeにマッチし、残りの(n-m)要素がPm+1〜Pnにマッチするとき。 - Pがpattern datum(=リスト・ベクタ・シンボル以外のdatum)で、Fと
equal?
で等しいとき。
意味論
expression
を評価する。- 最初の
clause
のpattern
についてマッチを試みる。 fender
なしclauseにマッチした場合、output expression
を評価し、その結果をsyntax-case
の返り値とする。fender
ありclauseにマッチした場合、fender
を評価し、偽ならマッチ失敗と同様に扱う。- マッチしなかった場合、次のclauseを試す。
- どのclauseにもマッチしなかった場合はsyntax violation。
補足
pattern
内のパターン変数は、入力の対応する部分にboundされる。この束縛はoutput expression
と、fender
(あれば)内のsyntax
の中で有効 (syntax
の外ではパターン変数は参照できない)。- パターン変数の変数・予約語と同じ名前空間を持つ。
syntax-case
がtail contextにある場合、output expression
もtail position
(syntax <template>) および #'<template>
概要
quote
に似てるが、以下の違いがあるtemplate
中にパターン変数を書くとその値が挿入される- contextual information(実装でいうwrap)が保存される
- S式ではなくsyntax objectを返す
テンプレート
template
は以下のいずれか- パターン変数
- (パターン変数・ellipsis以外の)識別子
- pattern datum
(<subtemplate> ...)
(<subtemplate> ... . <template>)
#(<subtemplate> ...)
- 各
subtemplate
には、template(+0個以上のellipses)が書ける。 (... <template>)
syntax
の返り値は、template
をコピーしたものだが、パターン変数(1.)はその値(boundされているinputのsubform)に置換する- 識別子(2.)とpattern datum(3.)はそのまま出力にコピーされる。
- (4.)の
subtemplate
+...
は、subtemplate
の0回以上の出現に展開される。 - n(≧1)個の
...
が付いたsubpattern
内のパターン変数は、n個以上の...
が付いたsubtemplate
にしか書けない。 - これらのパターン変数は、boundされているinput subformに置き換えられる(distributed as specified?)
subtemplate
内のパターン変数にsubpattern
より多い...
を付けた場合は、必要な数だけinput formが複製されるsubtemplate
にn(≧1)個の...
が付いているとき、ちょうどn個の...
が付いたsubpattern
からのパターン変数を1つ以上使わなければならない(そうでないとinput subformをoutput内に何回繰り返せばよいかわからないので)- これらの条件が満たされない場合はsyntax violation
- (7.)の
(... <template>)
は<template>
と同じだが、その中の...
が特別な意味を持たない。例えば...
を 出力に含めたいときは(... ...)
と書く。
出力
- 出力するSOがwrappedかunwrappedかは、以下のように決まる
(<t1> . <t2>)
でt1かt2にパターン変数がある場合、unwrappedなpair(<t> <ellipsis>)
でtにパターン変数がある場合、unwrappedなlist#(<t1> ... <tn>)
でt1〜tnのいずれかにパターン変数がある場合、unwrappedなvector- 上記以外はwrappedなsyntax object
- パターン変数の出現箇所をinput subformに置き換えるときは、そのsubformがwrappedならwrapped、そうでなければunwrappedにする
12.5 Identifier predicates
(identifier? obj) procedure
- objがidentifierのとき真(普通にシンボルをidentifierに使う場合は、objがシンボルをwrapしたsyntax objectであるとき真)
- マクロ内のエラーチェックに使う
- 第一引数が識別子でなければエラー、みたいな
rec
の例ではsyntax-caseのfenderを使って、第一引数が識別子でなければマッチしないようにしている
(bound-identifier=? id1 id2) procedure
- 引数の重複チェックとかに使う
(free-identifier=? id1 id2) procedure
- マクロが予約語をもつ(caseのelseとか)ときとかに使う
12.6 Syntax-object and datum conversions
(syntax->datum syntax-object) procedure
- syntax objectからメタ情報を剥いでS式にする
- (実装上は、単に一番外側のwrapを剥がすだけではない。wrapped -> array -> wrapped -> vector ... みたいなケースがあるので、内側のwrapも再帰的に剥がさないといけない)
(datum->syntax template-id datum) procedure
- template-idはidentifier。datumはS式
- datumを、template-idと同じメタ情報をもつsyntax objectにする
aif
のit
など、名前を意図的に注入する際に使う(これを使わないと新しいit
ができてしまうが、これを使うとit
が「そこにあった」ことにできる)- includeの例も、ファイルから読み込んだすべての式が「そこにあった」ことになるので、flib.ssのgがglib.ssのgを参照できる(C言語の#includeみたいな動作)。
- 同様に、
define-macro
っぽいものも作れる(lisp-transformer
の例)。p
を実行後、返り値の式全体が「そこにあった」ことにする
12.7 Generating lists of temporaries
(generate-temporaries l) procedure
- lはリストか、リストをwrapしたsyntax object
- リストの長さと同じだけの一時変数を返す
- 一時変数の個数が事前にわからない(入力の長さに依存する)場合に使う
12.8 Derived forms and procedures
(with-syntax ((<pattern> <expression>) ...)
) syntax
- letの構文版みたいなもの(ここだけで使えるマクロを定義する)
<pattern>
にはsyntax-caseのパターンが書けるのでletよりも高機能- with-syntaxはsyntax-caseで簡単に実装できる
(quasisyntax <template>) syntax unsyntax auxiliary syntax unsyntax-splicing auxiliary syntax
- quasiquoteの構文版みたいなもの
- unsyntax: 与えられた値を埋め込む
- unsyntax-splicing: 与えられたリスト(TODO: ベクタは?)の中身を、リストまたはベクタに展開して埋め込む
- quasisyntaxはネストできる
一部の場合に限り、1引数でない(=0か2以上の引数をとる)unsyntaxおよびunsyntax-splicingが許される
syntax-rulesは、syntax-caseで簡単に実装できる
identifier-syntax(下記参照)もsyntax-caseで実装できる
12.9 Syntax violations
(syntax-violation who message form) procedure (syntax-violation who message form subform) procedure
- 例外(構文エラー)を投げる
- who: マクロ名(string, symbolまたは#f)
- message: エラー内容(string)
- form: エラーが発生したform。syntax objectかdatum value(S式のこと? TODO: ioオブジェクトとかはdatum valueでない?)
- subform: formのうちのエラー発生箇所。syntax objectかdatum value
- whoが#fのときは推論する
- formがidentifierのときはそのsymbol
- formがリストをwrapしたsyntax objectで最初の要素がidentifierのときはそのsymbol
- それ以外の場合はcondition objectにwhoは提供しない
- condition objectの作り方
- &who: whoがあるときだけ
- &message: messageを入れる
- &form: formとsubform(なければ#f)を入れる
本体の方
上記はr6rs-libだが、r6rs本体にもいくつかマクロ関係の記述がある
11.19 Macro transformers
(syntax-rules (<literal> ...) <syntax rule> ...) syntax (expand) _ auxiliary syntax (expand) ... auxiliary syntax (expand)
- syntax-rulesは本体に含まれている(R5RSとの互換性のためかな)
(identifier-syntax <template>) syntax (expand) (identifier-syntax syntax (expand)
(<id1> <template1>) ((set! <id2> <pattern>) <template2>)) set! auxiliary syntax ( expand)
- make-variable-transformerの簡易版(r6rs-libなしでもvariableマクロを作れるように、ということか?)
- getとsetをフックするみたいなことができる
R7RSのsyntax-rules
R7RS(small)のsyntax-rulesはR6RSと違い、第一引数にシンボルを書くことでellipsisとして使う記号を変更できるという仕様があるので注意。
例:(Guileのリファレンスより引用)
(define-syntax define-quotation-macros
(syntax-rules ()
((_ (macro-name head-symbol) ...)
(begin (define-syntax macro-name
(syntax-rules ::: ()
((_ x :::)
(quote (head-symbol x :::)))))
...))))
(define-quotation-macros (quote-a a) (quote-b b) (quote-c c))
(quote-a 1 2 3) ⇒ (a 1 2 3)