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

いつもの or 構文の展開も試してみます。 これまで同様、 構文環境フレーム E1 とマーク M0 をすべてのシンボルにくっつけてから展開を始めます。 syntax-quasiquote 構文はここで独自にでっちあげた構文で、 quasiquote 構文と一点を除いて同じです。 その一点とは、 quasiquote 構文ではリテラルを quote 構文にするのに対して、 syntax-quasiquote 構文は syntax-quote 構文にすることです。 SRFI-72 の quasisyntax 構文に近いのですけど、 ここでの syntax-quote 構文と、 SRFI-72 の syntax 構文はふるまいが異なるので、 結果的には別物のようにふるまいます。

(letrec-syntax:E1:M0
 ((or:E1:M0 (lambda:E1:M0 (x:E1:M0)
   (if:E1:M0 (syntax-null?:E1:M0 (syntax-cdr:E1:M0 x:E1:M0))
    #f
   (if:E1:M0 (syntax-null?:E1:M0 (syntax-cddr:E1:M0 x:E1:M0))
    (syntax-cadr:E1:M0 x:E1:M0)
   (if:E1:M0 (syntax-pair?:E1:M0 (syntax-cddr:E1:M0 x:E1:M0))
    (syntax-quasiquote:E1:M0
     ((lambda:E1:M0 (t:E1:M0)
       (if:E1:M0 t:E1:M0 t:E1:M0 (or:E1:M0 ,@(syntax-cddr:E1:M0 x:E1:M0))))
      ,(syntax-cadr:E1:M0 x:E1:M0)))
   ;else
    (error:E1:M0 "no matching" (syntax->datum:E1:M0 x:E1:M0))))))))
 ((lambda:E1:M0 (x:E1:M0) ((lambda:E1:M0 (if:E1:M0 t:E1:M0)
   (or:E1:M0 1 t:E1:M0)) list:E1:M0 x:E1:M0)) 2))
E1 ((syntax-quote:M0 special) (lambda:M0 special) (letrec-syntax:M0 special)
    (if:M0 special) ...)

識別子 letrec-syntax:E1:M0 の意味は letrec-syntax 構文のキーワードなので、 新しく構文環境フレーム E2 を追加してマクロ定義を展開し、 展開結果の評価値をマクロ手続きとして登録します。 マクロ手続き中のリテラルには定義時の構文情報 :E3:E2:E1:M0 がくっついています。 ここで、 構文環境フレーム E3 は or 変換手続きの lambda 構文の展開が作成したものです。

(letrec-syntax
 ((or (lambda (x.1)
   (if (syntax-null? (syntax-cdr x.1))
    #f
   (if (syntax-null? (syntax-cddr x.1))
    (syntax-cadr x.1)
   (if (syntax-pair? (syntax-cddr x.1))
    `((,'lambda:E3:E2:E1:M0 (,'t:E3:E2:E1:M0)
       (,'if:E3:E2:E1:M0 ,'t:E3:E2:E1:M0 ,'t:E3:E2:E1:M0
        (,'or:E3:E2:E1:M0 ,@(syntax-cddr x.1)))) ,(syntax-cadr x.1))
   ;else
    (error "no matching" (syntax->datum x.1))))))))
 ((lambda:E2:E1:M0 (x:E2:E1:M0) ((lambda:E2:E1:M0 (if:E2:E1:M0 t:E2:E1:M0)
   (or:E2:E1:M0 1 t:E2:E1:M0)) list:E2:E1:M0 x:E2:E1:M0)) 2))
E3 ((x:M0 subst x.1))
E2 ((or:M0 macro <proc>))
E1 ((syntax-quote:M0 special) (lambda:M0 special) (letrec-syntax:M0 special)
    (if:M0 special) ...)

本体の 2 つのλ構文を展開します。

(letrec-syntax
 ((or (lambda (x.1) 省略)))
 ((lambda (x.2) ((lambda (if.3 t.3)
   (or:E5:E4:E2:E1:M0 1 t:E5:E4:E2:E1:M0)) list:E4:E2:E1:M0 x:E4:E2:E1:M0)) 2))
E5 ((if:M0 subst if.3) (t:M0 subst t.3))
E4 ((x:M0 subst x.2))
E3 ((x:M0 subst x.1))
E2 ((or:M0 macro <proc>))
E1 ((syntax-quote:M0 special) (lambda:M0 special) (letrec-syntax:M0 special)
    (if:M0 special) ...)

新しいマーク M6 を使い局所マクロ or:M0 で展開します。

(letrec-syntax
 ((or (lambda (x.1) 省略)))
 ((lambda (x.2) ((lambda (if.3 t.3)
   ((lambda:M6:E3:E2:E1:M0 (t:M6:E3:E2:E1:M0)
     (if:M6:E3:E2:E1:M0 t:M6:E3:E2:E1:M0 t:M6:E3:E2:E1:M0
      (or:M6:E3:E2:E1:M0 t:E5:E4:E2:E1:M0))) 1))
   list:E4:E2:E1:M0 x:E4:E2:E1:M0)) 2))
E5 ((if:M0 subst if.3) (t:M0 subst t.3))
E4 ((x:M0 subst x.2))
E3 ((x:M0 subst x.1))
E2 ((or:M0 macro <proc>))
E1 ((syntax-quote:M0 special) (lambda:M0 special) (letrec-syntax:M0 special)
    (if:M0 special) ...)

lambda 構文を展開して、 新しい構文環境フレーム E7 に識別子 t:M6:M0 から変数 t.4 への置換を加えます。

(letrec-syntax
 ((or (lambda (x.1) 省略)))
 ((lambda (x.2) ((lambda (if.3 t.3)
   ((lambda (t.4)
     (if:E7:M6:E3:E2:E1:M0 t:E7:M6:E3:E2:E1:M0 t:E7:M6:E3:E2:E1:M0
      (or:E7:M6:E3:E2:E1:M0 t:E7:E5:E4:E2:E1:M0))) 1))
   list:E4:E2:E1:M0 x:E4:E2:E1:M0)) 2))
E7 ((t:M6:M0 subst t.4))
E5 ((if:M0 subst if.3) (t:M0 subst t.3))
E4 ((x:M0 subst x.2))
E3 ((x:M0 subst x.1))
E2 ((or:M0 macro <proc>))
E1 ((syntax-quote:M0 special) (lambda:M0 special) (letrec-syntax:M0 special)
    (if:M0 special) ...)

識別子 if:E7:M6:E3:E2:E1:M0 の意味を、 if:M6:M0 を E7 から、 if:M0 を E3 E2 E1 から順に探し、 if 構文のキーワードの意味束縛を見つけます。 識別子 t:E7:M6:E3:E2:E1:M0 の意味を同様に探して、 変数 t.3 への置換規則を見つけます。

(letrec-syntax
 ((or (lambda (x.1) 省略)))
 ((lambda (x.2) ((lambda (if.3 t.3)
   ((lambda (t.4)
     (if t.4 t.4 (or:E7:M6:E3:E2:E1:M0 t:E7:E5:E4:E2:E1:M0))) 1))
   list:E4:E2:E1:M0 x:E4:E2:E1:M0)) 2))
E7 ((t:M6:M0 subst t.4))
E5 ((if:M0 subst if.3) (t:M0 subst t.3))
E4 ((x:M0 subst x.2))
E3 ((x:M0 subst x.1))
E2 ((or:M0 macro <proc>))
E1 ((syntax-quote:M0 special) (lambda:M0 special) (letrec-syntax:M0 special)
    (if:M0 special) ...)

識別子 or:E7:M6:E3:E2:E1:M0 の意味にマクロ or:M0 を見つけ、 新たなマーク M8 を使って展開します。

(letrec-syntax
 ((or (lambda (x.1) 省略)))
 ((lambda (x.2) ((lambda (if.3 t.3)
   ((lambda (t.4)
     (if t.4 t.4 t:E7:E5:E4:E2:E1:M0)) 1))
   list:E4:E2:E1:M0 x:E4:E2:E1:M0)) 2))
E7 ((t:M6:M0 subst t.4))
E5 ((if:M0 subst if.3) (t:M0 subst t.3))
E4 ((x:M0 subst x.2))
E3 ((x:M0 subst x.1))
E2 ((or:M0 macro <proc>))
E1 ((syntax-quote:M0 special) (lambda:M0 special) (letrec-syntax:M0 special)
    (if:M0 special) ...)

識別子 t:E7:E5:E4:E2:E1:M0 の意味を探して、 E5 で変数 t.3 への置換規則を見つけます。 残りの list と x も置換規則を見つけて変数を置き換えて、 展開が終わります。

((lambda (x.2) ((lambda (if.3 t.3) ((lambda (t.4) (if t.4 t.4 t.3)) 1)) list x.2)) 2)

この展開状況を理解できると、 4 年前の「Beautiful Code 25章 構文の抽象化: syntax-case マクロ」の例題のどこで自分が間違えていたのかがわかります。 Hieb-Dybvig は、 マクロ変換手続きの中の個々のリテラルに定義時構文環境を閉じ込める方式なので、 define-syntax で作った変換手続きの中のリテラルも識別子になっています。 そのことに即座に思い当たれなかったわけで、 4 年前の自分の理解は不十分でした。