健全なマクロ展開 - reversed syntactic closures (その2)

Hanson は、 定義時構文環境とシンボルを閉じ込めた構文クロージャをシンボルの別名とし、 シンボルと同格に扱えるように Bawden の syntactic closures を改良しました。 別名と意味を結びつける束縛対を構文環境に置くことができるようになり、 (その1) の展開例では別名と置換規則が結びついていました。 別名はマクロ・アプリケーションのキーワードになることもでき、 それを応用するのが capture-syntactic-environment 手続きです。

capture-syntactic-environment 手続きの実引数は変換子の継続です。 例えば、 (その1) の cond マクロは変換手続きに入ってすぐに capture-syntactic-environment 手続きを摘要します。 継続は変換手続きの残り丸ごと全部です。 capture-syntactic-environment 手続きは即座に使い捨て展開対象式を返します。 そして、 「後にこの使い捨て展開対象式が展開される段階で、 展開時構文環境を実引数とし、 継続を摘要します」と Hanson の仕様に説明があります。 この説明の意味を理解するために、 ここではさらに簡易にした cond 変換子で、 どういう仕組みなのか展開器のふるまいを追いかけていきます。

(define-syntax cond
 (sc-macro-transformer
  (lambda (form expanding-env)
   (capture-syntactic-environment (lambda (defined-env)
    (make-syntactic-closure expanding-env '()
     (let ((alias_if (make-syntactic-closure defined-env '() 'if))
           (alias_begin (make-syntactic-closure defined-env '() 'begin))
           (alise_cond (make-syntactic-closure defined-env '() 'cond)))
      (match form
       ((_ (pred form1 ..1) clauses ...)
        (if (identifier=? expanding-env pred defined-env 'else)
         `(,alias_begin ,@form1)
         `(,alias_if ,pred
           (,alias_begin ,@form1)
           (,alias_cond ,@clauses))))
       (else ''unspecified)))))))))

使い捨て展開対象式はマクロ・アプリケーションであり、 このマクロ・アプリケーションのキーワードが構文クロージャ alias_foo になっています。 この構文クロージャは、 構文環境 E3 とシンボル foo を閉じ込めてあって、 シンボル foo の別名になっています (このシンボルは何でも良く、 GNU/MIT Scheme はシンボル keyword を使います)。 別名は識別子なので、 マクロ・アプリケーションのキーワードとして利用可能なわけです。 構文環境 E3 には、 キーワード foo のマクロとして、 変換手続きを登録してあります。 この変換手続き tranpoline は、 マクロ・アプリケーション (alias_foo) 展開時に摘要されます。 そして、 これが上の cond 変換子の継続を摘要し、 この段階で cond アプリケーションのマクロ変換をおこないます。

(define (capture-syntactic-environment kont)
 ; マクロ・アプリケーション (alias_foo) を作ります。
 ; alias_foo は構文クロージャ #<SC E3 () foo>、 すなわち foo の別名です。
 ; 構文環境 E3 は (((foo . (macro tranpoline . ()))) . ()) です。
 (let* ((tranpoline
         (lambda (form expanding-env defined-env)
          ; 継続を摘要します。
          ; 実引数は (alias_foo) の展開時構文環境です。
          (kont expanding-env)))
        (null-syntactic-env '())
        (macro-foo (make-sc-macro tranpoline null-syntactic-env))
        (syntactic-env `(((foo ,@macro-foo)) ,@null-syntactic-env))
        (alias_foo (make-syntactic-closure syntactic-env '() 'foo)))
  `(,alias_foo)))

(define (make-sc-macro proc defined-env)
 (cons* 'macro proc defined-env))

展開状況を追ってみます。 cond マクロの定義時構文環境を E1 とします。 cond マクロ・アプリケーションの展開時構文環境を E2 としましょう。

【(cond ((p1 f1) (else f2)) E2】
E2
E1 (((cond macro cond-proc . E1) ...) ...)

すると、 capture-syntactic-environment が返した使い捨てマクロ・アプリケーションを、 sc-macro-expander が cond 変換子の定義時構文環境 E1 に包んだ展開器へ戻します。 マクロ・アプリケーションのキーワードは構文クロージャで、 構文環境 E3 とシンボル foo を閉じ込めてあります。 そして、 構文環境 E3 には foo をキーワードとして、 使い捨てマクロ変換子を一つだけ定義してあります。

;  sc-macro-expander が追加する構文クロージャ
;  │        capture-syntactic-environment が生成した
;  ↓        ↓   使い捨てマクロ・アプリケーション
【#<SC E1 () (#<SC E3 () foo>)> E2】
E3 (((foo macro tranpoline . ())) . ())
E2
E1 (((cond macro cond-proc . E1) ...) ...)

構文クロージャを展開して、 展開時構文環境が cond マクロの定義時構文環境 E1 に変わります。

【(#<SC E3 () foo>) E1】
E3 (((foo macro tranpoline . ())) . ())
E2
E1 (((cond macro cond-proc . E1) ...) ...)

使い捨てマクロ・アプリケーションの展開は、 まずキーワードになっている foo の別名である構文クロージャの意味を展開時構文環境 E1 から探すことから始まります。 できたての構文クロージャと eq? になる束縛対が E1 にあるはずがなく、 E1 からの探索は失敗します。 続いて、 展開器は構文クロージャに閉じ込めた構文環境 E3 から、 構文クロージャに閉じ込めたシンボル foo を探します。 シンボル foo の意味は E3 下ではマクロなので、 この変換子で、 使い捨てマクロ・アプリケーションを展開します。 そのときの、 変換手続き tranpoline の実引数の展開時構文環境は E1、 定義時構文環境は () です。

(tranpoline (#<SC E3 () foo>) E1 ())

変換手続き tranpoline は、 構文環境 E1 を実引数とし、 cond 変換手続きの継続を摘要します。 この構文環境 E1 は、 cond マクロの定義時構文環境 E1 を展開器が持ち回ってきたものであり、 cond 変換手続きの継続が cond マクロの定義時構文環境 E1 を手に入れることができました。

cond 変換手続きの継続は展開対象式 (cond ((p1 f1) (else f2)) を変換して、この展開対象式の展開時構文環境 E2 を閉じ込めた構文クロージャを返します。

【#<SC E2 ()
    (#<SC E1 () if> p1
     (#<SC E1 () begin> f1)
     (#<SC E1 () cond> (else f2)))> E1】

そして、 if 特殊形式の展開が構文環境 E2 でおこなわれます。

【(#<SC E1 () if> p1
   (#<SC E1 () begin> f1)
   (#<SC E1 () cond> (else f2))) E2】

(if【p1 E2】
 (begin【f1 E2】)
 【(#<SC E1 () cond> (else f2)) E2】)

このように capture-syntactic-environment は使い捨てマクロ・アプリーションを返し、 それを展開するときまで、 変換子の継続の摘要を先延ばしします。 継続には先延ばしされた時点での展開時構文環境を渡してきます。 強力ですけど、 先延ばしした展開時に、 どの構文環境が展開時構文環境になっているのか展開状況を理解できていないと使いこなせず、 cond 変換手続きの例のようにマクロそれ自身の定義時構文環境をとりだすのに使うのが精一杯です。