Scheme:マクロ内でのループ

Scheme:マクロ内でのループ

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の展開結果から 呼んでいるマクロや補助関数はマクロの使用環境からも見えている必要がある)

改善案

letrec-syntax

ふつうの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)))
      )))

Last modified : 2012/02/07 08:47:16 UTC