For Gauche 0.9.5


Next: , Previous: , Up: マクロ   [Contents][Index]

5.2 衛生的マクロ

マクロ束縛

以下のフォームはtransformer-specで作られるマクロ変換器と nameの束縛を作ります。外側のスコープにnameの束縛があれば、 それはシャドウされます。

トップレベル束縛の場合、nameに他のモジュールからインポートされたり 継承されている束縛があれば、それをシャドウすることになります (モジュール参照)。 (註:モジュール内でのトップレベル束縛がインポートした束縛をシャドウするのは Gaucheの拡張です。R7RSではインポートした束縛の再定義はしてはいけないことに なっているので、ポータブルなコードでは避けて下さい)。

同じスコープで同じ名前を複数回束縛した場合の動作は未定義です。

transformer-specsyntax-rulesフォーム、 er-macro-transformerフォーム、あるいは他のマクロキーワードか 構文キーワードです。これについては後述します。

Special Form: define-syntax name transformer-spec

[R7RS] トップレベルで使われた場合、このフォームはトップレベルのnametransformer-specで定義されるマクロ変換器に束縛します。

lambdalet等の本体の宣言部分に使われた場合 (内部define-syntax)、 その本体内のスコープでnameを束縛します。 内部defineがletrec*に変換されるのと同じように、 内部define-syntaxはletrec-syntaxへと変換されます。

Special Form: let-syntax ((name transformer-spec) …) body
Special Form: letrec-syntax ((name transformer-spec) …) body

[R7RS] ローカルマクロを定義します。各nameが 対応するtransformer-specで定義されるマクロ変換器へと束縛された 環境を作りbodyを評価します。 let-syntaでは、transformer-speclet-syntaxを 囲むスコープ内でtransformer-specを評価するのに対し、 letrec-syntaxではnameの束縛がなされた環境で transformer-specを評価します。つまりletrec-syntaxは 相互再帰的なマクロを定義できます。

Transformer specs

transformer-specは、マクロ展開器へと評価される特別な式です。 マクロ変換器はコンパイル時に実行されるため、他の式とは異なった段階で評価されます。 そのためにいくらか制限があります。

現在のところ、以下の上げる式しか許されていません。

  1. syntax-rulesフォーム。これは「高レベル」マクロと呼ばれ、 パターンマッチングのみによってマクロを定義します。 これはSchemeとは異なる一種の宣言的言語で、 マクロの段階や衛生の問題をボンネットの下に隠してしまいます。 ある種のマクロはsyntax-rulesでより簡単に書けます。 詳しくはSyntax-rules pattern langaugeを参照してください。
  2. er-macro-transfomerフォーム。 これはexplicit renaming(ER)マクロを定義します。 ERマクロでは、必要な衛生を保ちながら、任意のSchemeコードを使って変換を書けます。 伝統的なLispのマクロは、ERマクロでリネームを使わない特別な場合と考えられます。 詳しくはExplcit-renamingマクロ変換器を参照してください。
  3. マクロキーワードか構文キーワード。これはGauche独自の拡張で、 既存のマクロキーワードや構文キーワードの別名を定義するものです。
    (define-syntax si if)
    (define écrivez write)
    
    (si (< 2 3) (écrivez "oui"))
    

Next: , Previous: , Up: 衛生的マクロ   [Contents][Index]

5.2.1 Syntax-rules pattern langauge

Special Form: syntax-rules (literal …) clause1 clause2 …
Special Form: syntax-rules ellipsis (literal …) clause1 clause2 …

[R7RS] This specifies a macro transformer by pattern matching.


Previous: , Up: 衛生的マクロ   [Contents][Index]

5.2.2 Explcit-renamingマクロ変換器

Special Form: er-macro-transformer procedure-expr

procedure-exprからマクロ変換器を作ります。 作られたマクロ変換器は、define-syntaxlet-syntaxletrec-syntaxにより構文キーワードに束縛されなければなりません。 マクロ変換器の他の用途は定義されていません。

procedure-exprは3つの引数、formrenameid=?を 取る手続きへと評価される式です。

form引数には、マクロ呼び出しのS式そのものが渡されます。 procedure-exprはマクロ展開の結果をS式として返します。 この点は、伝統的なマクロとよく似ています。実のところ、 renameid=?を無視すれば、セマンティクスは伝統的な(非衛生な)マクロと 同じになります。次の例を見てください (この例では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マクロの例では、 マクロの出力にifnotbeginを挿入していますから、 衛生的なバージョンは次のとおり書けます。

(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-exprid=?引数はふたつの引数を取り、 それらがともに識別子であって、しかも同じ束縛を参照するか束縛されていないか、という 場合に限り#tを返します。 これはリテラル構文キーワード(condcaseフォームのelse等) を比較するのに使えます。

下のif=>マクロはifと同じように動作しますが、 (if=> 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
Macro: quasirename renamer form

form中の「リテラル」な部分 (unquoteunquote-splicingの外側) に現れるシンボルや識別子がrenameによってリネームされることを除いて、 準クオートのように動作します。

例えば次のフォームは:

(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)))]))))

Previous: , Up: 衛生的マクロ   [Contents][Index]