break/next名前付きfor-each

他の主要な手続き型言語でいうところのbreakやcontinueの仕組みを、Schemeの組込みのfor-eachに追加してやろう、ということらしい。
つまり、次のようなことができるようにしたい。

gosh> (for-each-ext return
                    next
                    (lambda (x) (cond ((odd? x) (next #t))  ;#tには意味はない
                                      ((< 10 x) (return x)) ;xがfor-each-extの戻り値
                                      (else (print x))))
                    '(0 1 1 2 3 5 8 13 21 34))              ;フィボナッチ数
0
2
8
34

他の言語のように「break」「continue」というキーワードを強制するのではなく、ここではbreakするのにreturn(第1引数)、continueするのにnext(第2引数)という識別子を指定している。
第3引数に与える手続きの中で、これらを呼び出せばbreakやcontinueできる。
上の例では、

  • 奇数だったらcontinue
  • 10より大きかったらその値でbreak(というか戻り値を返すreturn的?)
  • さもなくば値を表示する

している。
結果を見ると、0と2と8はprintによる出力で、34はfor-each-extの戻り値。


さて、ではfor-each-extの定義をば。

(define-syntax for-each-ext
  (syntax-rules ()
    ((_ break next proc lis ...)
     (let ((args1 (list lis ...)))
       (call/cc (lambda (break)
                  (apply for-each
                         (lambda args2
                           (call/cc (lambda (next)
                                      (apply proc args2))))
                         args1)))))))

一見すると複雑で難しく見えるが、for-eachにあわせて可変長引数にするためのコードが複雑に見えるだけのようだ。
理解に苦しんでいる人はid:nyaago69:20090518:1242654785を読むべし。引数の個数を4個に限定して単純化したfor-each-extの例がとても分かりやすかった。


for-each-extでは、次の2種類の継続を捕捉する必要がある。

  • breakのための継続
  • continueのための継続

前者はなんてこと無いけど、後者単にprocだけじゃ継続を捕捉できないから、(lambda args2 …)を被せてるわけやね。