R5RSマクロでは、繰り返しをコーディングするのに再帰を使うしかない。 '...'を使ったパターンマッチで済まない場合 (例えば、一時変数を必要な だけ生成するとか) に、そのような再帰パターンを書くことになる。
部分的なループを実現したい場合、現状ではつぎの二つの方法が有力。
R5RSの例にも出てくる方法。
例えばこんなdefine-values。"helper"が渡されるパターンは内部の繰り返しに 当てられていて、そこで<name> ...それぞれに対応する一時変数<temp> ...を つくり出す (コードは Ken Dickeyによるもの)。
(define-syntax define-values
(syntax-rules ()
((define-values (<name> ...) <body> ...)
(define-values "helper" (<name> ...) () (<name> ...) <body> ...)
)
((define-values "helper" () (<temp> ...) (<name> ...) <body> ...)
(begin
(define <name> #f) ...
(call-with-values
(lambda () <body> ...)
(lambda (<temp> ...)
(set! <name> <temp>) ...
) )
))
((define-values "helper" (<var1> <var2> ...) <temp>s
(<name> ...) <body> ...)
(define-values "helper" (<var2> ...) (<temp> . <temp>s)
(<name> ...) <body> ...)
)
))
この方法の利点は、マクロがself-containedにできること。 でもどうもad hocな感じは免れない。また、ユーザがうっかりか故意にか
(define-values "helper" ...)
と呼び出してしまったらどうなるんだ、という問題もある。
この例はGaucheのdefine-valuesの定義で、ループ用にdefine-values-sub という補助マクロを導入している。
(define-syntax define-values
(syntax-rules ()
((_ (var ...) expr)
(define-values-sub () (var ...) (var ...) expr))
((_ . else)
(syntax-error "malformed define-values" (define-values . else)))
))
(define-syntax define-values-sub
(syntax-rules ()
((_ (tmp ...) () (var ...) expr)
(begin (define var (undefined)) ...
(receive (tmp ...) expr
(set! var tmp) ...
(undefined))))
((_ (tmp ...) (v v2 ...) (var ...) expr)
(define-values-sub (tmp ... tmp1) (v2 ...) (var ...) expr))
))
この方式の欠点は、トップレベルに補助マクロも見えてしまうこと。
Gaucheの場合はmoduleがあるので、これ全体をmoduleに入れてdefine-valuesだけ exportしておけば、ユーザからは補助マクロの方は見えない (Gaucheではdefine-syntaxの参照等価性はmoduleも考慮するので、 define-valuesの展開結果に表れるdefine-values-subはdefine-valuesの 「定義環境」で参照される。そのため、ユーザがdefine-valuesを呼ぶ 環境でdefine-values-subが見えている必要はない。 define-macroにはそういう性質が無いので、define-macroの展開結果から 呼んでいるマクロや補助関数はマクロの使用環境からも見えている必要がある)
ふつうのdefine/letとの対称性を考えると、 こんなことが出来てもよさそうな気もする。
(define-syntax define-values
(letrec-syntax ((define-values-sub
(syntax-rules ()
((_ (tmp ...) () (var ...) expr)
(begin (define var (undefined)) ...
(receive (tmp ...) expr
(set! var tmp) ...
(undefined))))
((_ (tmp ...) (v v2 ...) (var ...) expr)
(define-values-sub (tmp ... tmp1) (v2 ...) (var ...) expr))
))
)
(syntax-rules ()
((_ (var ...) expr)
(define-values-sub () (var ...) (var ...) expr))
((_ . else)
(syntax-error "malformed define-values" (define-values . else)))
)))
(let ((a 1))
(let-syntax ((foo (syntax-rules () ((_ b) (cons a b)))))
(let ((a 2))
(foo a))))
これが(1 . 2)になるのは、syntax-rulesが外側のaをクローズしているからではなく、aが適当に
(let ((g0 1))
(let ((g1 2))
(cons g0 g1)))
というように改名されているからだという考え方です。これだとマクロが適用される場所で変数名を改名しても参照できないものへの参照はエラーになります(モジュールはまた別なんでしょうけど)。ここでは、define-valuesのsyntax-rulesが適用されるとき、それが適用される場所からdefine-values-subを参照できません。syntax-rulesではdefine-syntaxとsyntax-rulesの間になにかをおくことはできないのでどちらにせよ問題は表れませんが、これを許すことにすると、やっぱり改名でマクロを説明できなくなるのは同じなんではないかと思います。
(define-syntax define-values
(syntax-rules ()
((_ (var ...) expr)
(begin
(define var (undefined)) ...
(letrec-syntax ((define-values-sub
(syntax-rules ()
((_ (tmp (... ...)) () (var (... ...)) expr)
(receive (tmp (... ...)) expr
(set! var tmp) (... ...)
(undefined)))
((_ (tmp (... ...)) (v v2 (... ...)) (var (... ...)) expr)
(define-values-sub (tmp (... ...) tmp1) (v2 (... ...)) (var (... ...)) expr))
))
)
(define-values-sub () (var ...) (var ...) expr)))
((_ . else)
(syntax-error "malformed define-values" (define-values . else)))
))