yhara.jp

Recent Posts

R6RS syntax-case 読解メモ

Tech

R6RSの読解メモです。

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 (aifitみたいに、意図的に名前を衝突させるやつ)

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がマクロ使用箇所を見つけたときは以下を行う
    1. 入力にantimarkを付ける
    2. マクロのtransformerを実行
    3. 出力に新しい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>)

fenderoutput 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であり、かつ
    1. both its occurrence in the input expression and its occurrence in the list of literals have the same lexical binding か、
    2. 両者が同じ名前かつどちらも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?で等しいとき。

意味論

  1. expressionを評価する。
  2. 最初のclausepatternについてマッチを試みる。
  3. fenderなしclauseにマッチした場合、output expressionを評価し、その結果をsyntax-caseの返り値とする。
  4. fenderありclauseにマッチした場合、fenderを評価し、偽ならマッチ失敗と同様に扱う。
  5. マッチしなかった場合、次のclauseを試す。
  6. どの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は以下のいずれか
    1. パターン変数
    2. (パターン変数・ellipsis以外の)識別子
    3. pattern datum
    4. (<subtemplate> ...) 
    5. (<subtemplate> ... . <template>)
    6. #(<subtemplate> ...)
    7. subtemplateには、template(+0個以上のellipses)が書ける。
    8. (... <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にする
  • aifitなど、名前を意図的に注入する際に使う(これを使わないと新しい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)

More posts

Posts

(more...)

Articles

(more...)

Category

Ads

About

About the author