マクロ
多くのLisp語族にはマクロと呼ばれるユーザ定義の構文を定義する機構が備わっている。Schemeも例外ではない。Schemeのマクロはhygienic macroと呼ばれるマクロである。これは健全なマクロもしくは清潔なマクロと訳されることが多いが、ここでは健全なマクロを訳語として使うこととする。小難しい用語が出てきたが、特に覚える必要はないので心配は不要である。
まずは簡単なマクロの例を見てみよう。
(define-syntax when (syntax-rules () ((_ test expr ...) (if test (begin expr ...)))))
define-syntax
がマクロ定義の構文である。R7RSではdefine-syntax
は以下のように定義されている。(define-syntax keyword transformer-spec)
syntax-rules
のみが定義されている。syntax-rules
は与えられた式をパターンマッチで分解し、テンプレートで定義された式に展開する。syntax-rules
は以下のように定義されている。(syntax-rules (identifier*) syntax-rule)
(syntax-rules identifier (identifier*) syntax-rule)
(pattern template)
ここで一つ実際にどのように
syntax-rules
で定義された構文が展開されるのかを見てみよう。上記のwhen
を使い、入力式として以下を考える:
(when (assq 'a lst) (display 'ok) (newline))
when
のパターンは(_ test expr ...)
である。R7RSでは_
(アンダースコア)は何にでもマッチするプレースホルダーとして定義されているので、この場合は最初のwhen
がマッチする。パターンの一つ目がマッチしたので、二つ目を見る。testのような識別子はリストの要素一つにマッチするので、(assq 'a lst)
がマッチする。最後exprは多少特殊なマッチをする。exprの後ろに
...
(三点、ellipsisと呼ばれる)がある場合は0個以上の要素にマッチするという定義になる。この場合は(display 'ok)
と(newline)
の二つにマッチする。
(when (assq 'a lst) (display 'ok) (newline)) #| マッチ結果 _ -> when test -> (assq 'a lst) expr ... -> (display 'ok) (newline) |#テンプレート部分ではパターン部分で使用した識別子が変数のように束縛される。
_
はプレースホルダーなのでテンプレート部分で使用しても置き換えが発生しない点に注意したい。上記のマッチ結果を踏まえて展開してみる。
(if test (begin expr ...)) ;; test -> (assq 'a lst) ;; expr ... -> (display 'ok) (newline) (if (assq 'a lst) (begin (display 'ok) (newline))
syntax-rules
の感じは掴めただろうか?パターンマッチで入力式を分解し、テンプレートを置換して展開式を得る。基本はこれだけである。設問 7.1
unless
は与えられた条件が偽であった際に続く式を評価する構文である。これをsyntax-rules
を使って実装せよ。健全性
Schemeのマクロは健全なマクロを定義している。では、健全なマクロとはどういうものだろうか?簡単に言えばマクロ定義内で束縛された変数とマクロの外側で束縛された変数が衝突しないというものである。例えば上記の
when
の定義を以下のように変更してみよう:
(define-syntax when (syntax-rules () ((_ test expr ...) (let ((t test)) (if t (begin expr ...))))))
when
の内部でtが束縛されている。ではこのwhen
を以下のように使ってみよう。
(let ((t (list 't))) (when (equal? t '(t)) (display t) (newline)))最も外側の
let
でtが束縛されているが、実行結果は(t)
が表示されるはずだ。当たり前のように思うかもしれないが、syntax-rules
が非健全なマクロであった場合、#t
が表示されることになる。では健全なマクロの何がうれしいのだろう?上記程度のものであれば、マクロの書き手が気をつければよいだけなのだが、以下のような例だと以下に書き手が気をつけても非健全なマクロでは変数の衝突が避けられない。
(define-syntax unless (syntax-rules () ((_ test expr ...) (cond (test #f) (else expr ...))))) (let ((else #f)) (unless else (display "hygienic macro!") (newline)))この例では、マクロ内で
cond
構文とelse
補助構文を使っているが、これが非健全なマクロだった場合、let
で束縛したelseがunless
マクロ展開後の式に含まれるelse
と衝突する。もちろん、マクロ使用者が気をつければいいのだが、マクロ展開器自体がこの問題を解決した方がプログラムの規模が大きくなった際に意図しない挙動を起こす可能性を減らすことができる。極力プログラムの処理に集中できるという点は健全なマクロの利点の一つになるだろう。
コラム:自由識別子
Schemeのマクロはここで記述した以上に奥が深い。例えば、
設問 7.2
Common Lispでは
Schemeのマクロはここで記述した以上に奥が深い。例えば、
syntax-rules
は補助構文のキーワードを受け取ることができる。ではこの識別子の比較はどのように行われているのだろうか?例としてelse
補助構文を考えてみよう。上記の例であれば、単にシンボルの比較を行っただけでは同一のものとして扱われてしまう。そこでSchemeの規格(R5RS以降)では識別子が同一の束縛を指すかどうかで判別するように既定している。R6RSでは識別子が同一の束縛を指しているかどうかをチェックする手続きとしてfree-identifier=?
がある。R7RSにこの手続きが存在しないのは、R7RSでは高レベルマクロしか既定していないからである。上記のunless
ではlet
で束縛した識別子とマクロ内で定義された識別子が別のものを指すので展開後の式ではelse
は別物として扱われるのである。設問 7.2
Common Lispでは
aif
と呼ばれるマクロが存在する。このマクロをsyntax-rules
を使って記述することは可能であろうか?
No comments:
Post a Comment