色んな意見があるとは思いますが、アナフォリックマクロが書けない時点でSchemeの衛生的マクロは設計としては失敗してる、と思います。
実際、R6RSの衛生的マクロ、
syntax-case
では書けるようになっている、と言う話なんですが、相当ややこしくなってる、との事です。またsyntax-case
に関してのまとまった文献なり入門、ってのも見たことないです(ビューティフルコードsyntax-case
は使いようがない。ポール・グレアムのOn Lisp
alambda
と呼ばれるアナフォリックマクロが紹介されています。これはLET OVER LAMBDA Edition 1.0
CL-USER> (defmacro alambda (parms &body body)
`(labels ((self ,parms ,@body))
#'self))
ALAMBDA
CL-USER>
同書には次のような例が掲載されています。
CL-USER> (alambda (x) (if (zerop x) 1 (* x (self (1- x)))))
#<FUNCTION (LABELS SELF) {BB55A75}>
CL-USER>
ご覧の通り、
alambda
は代名詞self
で束縛されたクロージャを返す。上のワンライナーは階乗の定義なんで、階乗を計算するクロージャですね。続けてインタプリタに次のように入力します。
CL-USER> (funcall * 10)
3628800
CL-USER>
10の階乗がキチンと返ってきます。ちなみに、上の
*
は「掛け算」の意味ではなくって、CLのインタプリタ上では、その前の計算結果を参照する場合、*
で参照出来ます。つまり、関数としての役割ではなくって、大域変数なんです。詳しくはこちらをご覧下さい。*
を掛け算と解釈すると、上の入力は意味不明になります。話を元に戻すと、最初の定義、
CL-USER> (defmacro alambda (parms &body body)
`(labels ((self ,parms ,@body))
#'self))
ALAMBDA
CL-USER>
に於いて、一見不用意に
self
と言う変数名が挿入されているように見えます。これはマクロが展開される場所の外側からself
が指示されていたらそれを参照してもおかしくないんです。と言うか、前の例示の通り、明らかに参照している。そのお陰で代名詞として役割を果たしてるんですね。上手い手です。そしてこれはSchemeで実現するのが難しい。と言うよりR5RSでは出来ません。
一見、R5RSマクロで次のような定義方法に翻訳出来て、それで良さそうに見えます。
;; この定義方法じゃ実はダメ。
(define-syntax alambda
(syntax-rules ()
((_ params body ...)
(letrec ((self
(lambda params
body ...)))
self))))
Schemeインタプリタで次のように走らせてみても、一見上手く動きそうに見えるのです。
> (alambda (n)
(if (zero? n)
'()
(cons
n
(self (- n 1)))))
#<procedure:self>
>
確かに手続きのクロージャ
self
が返ってるように見える。ところが、これに実値を与えてみると、破綻している事が分かる。
> ((alambda (n)
(if (zero? n)
'()
(cons
n
(self (- n 1))))) 10)
reference to undefined identifier: self
=== context ===
/usr/lib/plt/collects/scheme/private/misc.ss:74:7
>
つまり、
alambda
を用いたクロージャの定義で現れるself
は、alambda
自体の定義で使われているself
とは別物だ、と認識されるのです。CLでのalambda
では同一と見なされるものがR5RSでは同一と見做されない。変数衝突が起きない。これが衛生的と言われる所以です。
R5RSマクロは「letが変数束縛をする」ということを知っており、マクロが挿入する束縛変数とマクロの「外から来た」変数とを区別するのです。
-----<中略>-----
Schemeは、レキシカルクロージャを導入したのと同じ発想を、一段メタな領域に適用しようとしています。そこではもはやプログラムはS式そのものではありません。一度読み込まれて構文解析されたプログラムは「S式+環境」となり、マクロは「S式+環境」に対して作用します。
つまり、Schemeで翻訳を試みた
alambda
の定義で使われているself
はクロージャによって守られていて、外側から中を参照する事が出来ません。コンテクストが外部定義からは切り離されているのです。故にアナフォリックマクロが定義出来ない。
Schemeではむしろ、束縛する変数を呼び出し環境から与えてやる書き方が推奨されます。
プログラミングGauche
alambda
は次のように定義した方が無難だと言うことです。
(define-syntax alambda
(syntax-rules ()
((_ self params body ...)
(letrec ((self
(lambda params
body ...)))
self))))
つまり、外部から
self
を与えてやるようにする。そうすると、
> (alambda self (n)
(if (zero? n)
'()
(cons
n
(self (- n 1)))))
#<procedure:self>
>
とクロージャ
self
が返るのは同じですが、引数を与えてやると、
> ((alambda self (n)
(if (zero? n)
'()
(cons
n
(self (- n 1))))) 10)
(10 9 8 7 6 5 4 3 2 1)
>
となります。
しかし、こうなると実際、アナフォリックマクロと言うよりクロージャを返す
named-let
の変種ですね。こんな感じで、CLのマクロをSchemeに移植しようとすると、色んな障害が立ちはだかります。また、外部から変数を与えてPseudoなアナフォリックを狙っても、CLと違い、手続き定義に於いては味方の筈のクロージャが敵にまわり、同じ効果を出すには大きな障害として立ちふさがるのです。