この記事は Lisp Advent Calendar 2016 17日目の記事です。
R6RS Scheme には識別子の等価性を判定する bound-identifier=? と free-identifier=? という述語がある。
詳しい説明は R6RS の本文に譲るとして、使い方で言うと、 free-identifier=? は cond の else のような構文キーワードの一致判定に使い、 bound-identifier=? は (lambda (a a) ...) の a のように変数束縛の中に重複があるケースを検出するのに使うのが代表的な使い途だ。
これらの手続きは、 R6RS では syntax-case を使った低レベルマクロを書くために提供されているが、 http://okmij.org/ftp/Scheme/macro-symbol-p.txt の方法を使うと R[567]RS の syntax-rules でこれらの手続きと同様の意味の述語を書ける。
定義は以下の通り。
(define-syntax syn-free-identifier=?
(syntax-rules ()
((_ a b kt kf)
(let-syntax ((test (syntax-rules (a)
((_ a) kt)
((_ x) kf))))
(test b)))))
(define-syntax syn-bound-identifier=?
(syntax-rules ()
((_ id b kt kf)
(let-syntax ((id (syntax-rules ()
((_) kf)))
(ok (syntax-rules ()
((_) kt))))
(let-syntax ((test (syntax-rules ()
((_ b) (id)))))
(test ok))))))
手続き版と区別するために、 syn- という接頭辞を付けている。また、 Scheme のマクロは最外簡約であるため、手続きのように値を返すことができないので、識別子 a, b が等しいときとそうでないときの継続 kt と kf を渡し、判定結果によりいずれに展開されるかを選択するようにしている(これは syntax-rules で合成可能なマクロを書くときの常套手段だ)。
syn-free-identifier=? の方は、 syntax-rules のリテラルが、ちょうど free-identifier=? の意味で一致判定をすることを利用している(syntax-rules を使った cond マクロの定義を思い出すとよい)。
syn-bound-identifier=? の方はもう少し込み入っているが、 bound-identifier=? の定義である「マクロ展開結果で一方が束縛変数になるとき、もう一方はその変数のスコープ内に現れた場合その束縛を参照する1ならば #t を返す」というのをそのまま書き下したようになっている。
(syn-bound-identifier=? a a #t #f) で、ふたつの a が字面としては等しく、識別子として異なる場合、これを a#0 と a#1 のようにして表記することにすると、 (syn-bound-identifier=? a#0 a#1 #t #f) 展開結果は下のようになる。
(let-syntax ((a#0 (syntax-rules ()
((_) kf)))
(ok (syntax-rules ()
((_) kt))))
(let-syntax ((test (syntax-rules ()
((_ a#1) (a#0)))))
(test ok)))
a#0 と a#1 は異なる識別子なので、 test のパターン部の a#1 はテンプレート内の a#0 を捕捉しない。
この結果、 (test ok) は (a#0) に展開され、結果は kf になる。
ふたつの識別子が bound-identifier=? である場合は、ふたつを a#0 と書くと、展開結果は次のようになる
(let-syntax ((a#0 (syntax-rules ()
((_) kf)))
(ok (syntax-rules ()
((_) kt))))
(let-syntax ((test (syntax-rules ()
((_ a#0) (a#0)))))
(test ok)))
syn-bound-identifier=? の第一、第二引数が bound-identifier=? である場合、 test のパターン変数とテンプレートが同一の変数を参照し、 (test ok) の展開結果は (ok) になり、最終的に kt になる。
というような感じで、 R6RS の低レベルマクロの一部として紹介されているものでも、 syntax-rules で記述できるものがある。 syntax-rules には想像以上の表現力がある。
-
識別子 a, b があるとき、例えば、
(lambda (a) ...b...)に a は束縛変数として現れる。このスコープ内の b が a の束縛を参照するなら ↩