健全なマクロ展開 - 構文オブジェクト (その7)

前回までは syntax-case の構文オブジェクトが局所マクロの再帰展開をどう扱うかを見てみました。 今度はλ構文と局所マクロが識別子をどのように扱うかを調べます。 そのために、 R4RS 以後、 健全性の例の一つとして利用されている次のマクロ展開をとりあげます。 この例では、 cond 構文のリテラル => と、 λ構文の変数 => が異なる意味を持つため、 cond マクロ変換手続きの 2 つの選択肢のうち、 上の選択肢ではなく下の選択肢を選択して展開することを期待しています。

(letrec-syntax ((cond (syntax-rules (=>)
                 ((_ (e1 => e3)) ((lambda (t) (if t (e3 t))) e1))
                 ((_ (e1 e2 e3 ...)) (if e1 (begin e2 e3 ...))))))
 ((lambda (=>) (cond (#t => 'ok))) #f))
; => ((lambda (=>.2) (if #t (begin =>.2 'ok))) #f)

ここでは、 syntax-rules マクロを低レベル・マクロに展開したものを使います。 ただし、 構文チェックを省略します。 最初はすべてのシンボルに環境フレーム E1 とマーク M0 が並ぶ構文環境がついています。

(letrec-syntax:E1:M0
 ((cond:E1:M0 (lambda:E1:M0 (x:E1:M0)
   (if:E1:M0 (free-identifier=?:E1:M0 (syntax-quote:E1:M0 =>:E1:M0) (syntax-cadadr:E1:M0 x:E1:M0))
    (syntax-quasiquote:E1:M0
     ((lambda:E1:M0 (t:E1:M0) (if:E1:M0 t:E1:M0 (,(syntax-caddadr:E1:M0 x:E1:M0) t:E1:M0))) 
     ,(syntax-caadr:E1:M0 x:E1:M0)))
    (syntax-quasiquote:E1:M0
     (if:E1:M0 ,(syntax-caadr:E1:M0 x:E1:M0) (begin:E1:M0 ,@(syntax-cdadr:E1:M0 x:E1:M0))))))))
 ((lambda:E1:M0 (=>:E1:M0) (cond:E1:M0 (#t =>:E1:M0 'ok))) #f))
E1 ((letrec-syntax:M0 special) (syntax-quote:M0 special) (lambda:M0 special)
    (if:M0 special) (begin:M0 special) ...)

letrec-syntax 構文の展開部が、 局所マクロの定義式と本体式に新しい構文環境フレーム E2 をくっつけます。 さらに、 この E2 に局所マクロの cond 構文の意味束縛を追加します。 この段階では、 意味束縛のマクロ手続きが仮のダミーの手続きになっています。

(letrec-syntax
 ((cond (lambda:E2:E1:M0 (x:E2:E1:M0)
   (if:E2:E1:M0 (free-identifier=?:E2:E1:M0 (syntax-quote:E2:E1:M0 =>:E2:E1:M0) (syntax-cadadr:E2:E1:M0 x:E2:E1:M0))
    (syntax-quasiquote:E2:E1:M0
     ((lambda:E2:E1:M0 (t:E2:E1:M0) (if:E2:E1:M0 t:E2:E1:M0 (,(syntax-caddadr:E2:E1:M0 x:E2:E1:M0) t:E2:E1:M0))) 
     ,(syntax-caadr:E2:E1:M0 x:E2:E1:M0)))
    (syntax-quasiquote:E2:E1:M0
     (if:E2:E1:M0 ,(syntax-caadr:E2:E1:M0 x:E2:E1:M0) (begin:E2:E1:M0 ,@(syntax-cdadr:E2:E1:M0 x:E2:E1:M0))))))))
 ((lambda:E2:E1:M0 (=>:E2:E1:M0) (cond:E2:E1:M0 (#t =>:E2:E1:M0 'ok))) #f))
E2 ((cond:M0 macro <dummy-proc>))
E1 ((letrec-syntax:M0 special) (syntax-quote:M0 special) (lambda:M0 special)
    (if:M0 special) (begin:M0 special) ...)

展開器が cond マクロの定義式をマクロ展開します。 letrec-syntax 構文の展開部がこれを評価して cond マクロ手続きを作り、 E2 のダミー手続きからすげ替えます。 このマクロ手続きがマクロ変換結果に挿入するシンボルには、 すべて構文環境 :E3:E2:E1:M0 がくっつきます。 ここで、 E3 は変換手続きのλ構文の展開が加えたものです。

(letrec-syntax
 ((cond (lambda (x.1)
   (if (free-identifier=? '=>:E3:E2:E1:M0 (syntax-cadadr x.1))
    `((,'lambda:E3:E2:E1:M0 (,'t:E3:E2:E1:M0)
       (,'if:E3:E2:E1:M0 ,'t:E3:E2:E1:M0 (,(syntax-caddadr x.1) ,'t:E3:E2:E1:M0)))
      ,(syntax-caadr x.1))
    `(,'if:E3:E2:E1:M0 ,(syntax-caadr x.1) (,'begin:E3:E2:E1:M0 ,@(syntax-cdadr x.1)))))))
 ((lambda:E2:E1:M0 (=>:E2:E1:M0) (cond:E2:E1:M0 (#t =>:E2:E1:M0 'ok))) #f))
E3 ((x:M0 subst x.1))
E2 ((cond:M0 macro <proc>))
E1 ((letrec-syntax:M0 special) (syntax-quote:M0 special) (lambda:M0 special)
    (if:M0 special) (begin:M0 special) ...)

続いて、 letrec-syntax 構文の展開部が同構文の本体の展開を始めます。 まず、 λ構文を展開して、 新しい環境フレーム E4 を追加します。

(letrec-syntax
 ((cond (lambda (x.1) 省略 )))
 ((lambda (=>.2) (cond:E4:E2:E1:M0 (#t =>:E4:E2:E1:M0 'ok))) #f))
E4 ((=>:M0 subst =>.2))
E3 ((x:M0 subst x.1))
E2 ((cond:M0 macro <proc>))
E1 ((letrec-syntax:M0 special) (syntax-quote:M0 special) (lambda:M0 special)
    (if:M0 special) (begin:M0 special) ...)

識別子 cond:E4:E2:E1:M0 の意味を code:M0 として構文環境 E4 E2 E1 の順に探して、 局所マクロ cond 構文キーワードの意味を見つけます。 マクロ変換の前に、 新しいマーク M5 を識別子に追加します。

(letrec-syntax
 ((cond (lambda (x.1) 省略 )))
 ((lambda (=>.2) (cond:M5:E4:E2:E1:M0 (#t =>:M5:E4:E2:E1:M0 'ok))) #f))
E4 ((=>:M0 subst =>.2))
E3 ((x:M0 subst x.1))
E2 ((cond:M0 macro <proc>))
E1 ((letrec-syntax:M0 special) (syntax-quote:M0 special) (lambda:M0 special)
    (if:M0 special) (begin:M0 special) ...)

そして、 cond:M0 マクロ手続きを呼びます。 free-identifier=? アプリケーションの評価の実引数は次のようになっています。 シンボル => の識別子の意味を、 それぞれにくっつけてある構文環境から求めます。 手続きに quote 構文で含めてある方は、 =>:M0 を E3 E2 E1 の順に探し見つからないので、 => への変数置換になります。 一方、 本体から渡った方は =>:M5:M0:M5 をすぐに省いて、 =>:M0 を E4 E2 E1 の順に探して、 =>.2 への変数置換を見つけます。 異なるシンボルへの変数置換のため意味が一致しません。 よって、 free-identifier=? アプリケーションの評価値は偽です。

(free-identifier=? (quote =>:E3:E2:E1:M0) (syntax-cadadr x.1)) ;=> #f
;                   =>:E3:E2:E1:M0         =>:M5:E4:E2:E1:M0
;                   E3 |- =>:M0            =>:M5:M0
;                   E2 |- =>:M0            E4 |- =>:M0
;                   E1 |- =>:M0            #(subst =>.2)
;                   #(subst =>)

局所マクロ cond:M0 は if 構文の else 節の評価値で本体を置き換えます。

(letrec-syntax
 ((cond:E1:M0 (lambda (x.1) 省略 )))
 ((lambda (=>.2) (if:E3:E2:E1:M0 #t (begin:E3:E2:E1:M0 =>:M5:E4:E2:E1:M0 'ok))) #f))
E4 ((=>:M0 subst =>.2))
E3 ((x:M0 subst x.1))
E2 ((cond:M0 macro <proc>))
E1 ((letrec-syntax:M0 special) (syntax-quote:M0 special) (lambda:M0 special)
    (if:M0 special) (begin:M0 special) ...)

マクロの展開部が変換結果にマーク M5 を加え、 元からある =>:M5:E4:E2:E1:M0 から先頭の M5 を取り除きます。 新しく挿入した if は if:M5:E3:E2:E1:M0 になり、 begin も同様に変化します。

(letrec-syntax
 ((cond:E1:M0 (lambda (x.1) 省略 )))
 ((lambda (=>.2) (if:M5:E3:E2:E1:M0 #t (begin:M5:E3:E2:E1:M0 =>:E4:E2:E1:M0 'ok))) #f))
E4 ((=>:M0 subst =>.2))
E3 ((x:M0 subst x.1))
E2 ((cond:M0 macro <proc>))
E1 ((letrec-syntax:M0 special) (syntax-quote:M0 special) (lambda:M0 special)
    (if:M0 special) (begin:M0 special) ...)

識別子 if:M5:E3:E2:E1:M0begin:M5:E3:E2:E1:M0 の意味を、 if:M0 等として E3 E2 E1 の順に探して、 それぞれが if 構文と begin 構文の意味であることを求めます。 識別子 =>:E4:E2:E1:M0 の意味は =>:M0 として E4 E2 E1 の順に探して、 =>.2 への変数置換の意味を見つけます。

((lambda (=>.2) (if #t (begin =>.2 'ok))) #f)

これで、 R4RS の展開結果が手に入りました。