他の主要な手続き型言語でいうところの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 …)を被せてるわけやね。