星田さんの記事に対するコメント。と言うより余談。
まずcase-lambdaに付いて。
これはSchemeでは新参で、確か、R6RSではじめて導入された構文だ。
そしてR7RS-smallでも仕様に含まれた。新参なんだけど、今後のSchemeでは重要な基礎の1つとなる。
元ネタはSRFI-16。可変長引数の関数を簡易に定義出来る仕様として提案されたモノだ。
Schemeで元々、可変長引数を扱う為には、ラムダ式では次の構文を用いる。
lambda x ...
仮引数のxがカッコを伴っていない。ここに複数の実引数を突っ込めるわけだ。
> ((lambda x x) 1 2 3 4 5 6 7 8)
'(1 2 3 4 5 6 7 8)
>
見たら分かるが、与えられた引数は全部リストとしてパックされる。
言い換えると、関数listはこの機能を用いて定義されてるように見えるだろう。
(define my-list
(lambda x x))
いずれにせよ、これがSchemeでの可変長引数関数作成の基本で、これがdefineになると、星田さんが混乱してた.(ドット記法)になる。
(define (my-list . args)
args)
この様に、ラムダ式とdefineの間では、可変長引数の「受け入れ」で表記法が若干違うんで混乱しないようにすること。
いずれにせよ、これらを使えば可変長引数を取る関数は書けるが、「リストをバラす」操作がちょっとメンド臭い。この辺はどの言語でもそうで、ある意味悩みのタネだ。Pythonなんかでもアンパック、って構文が出来たのはこの辺の煩雑さを避ける為のアイディアと思われる。
Schemeでは、基本をこれらラムダ式に委ねながら、もっと簡単に可変長引数関数が書けないか、と言う解決策としてcase-lambdaが取り上げられた、と言って良いだろう。
次。SchemeとActor理論を見てみよう。
ここではかなり丁寧に「継続渡し方式」に付いて書かれている。そして「継続渡し方式」から「末尾再帰最適化」への流れに対しても記述されている。
件の、結構実装に苦労する、可変長引数を取るmapなんだけど、実はこれは効率が良くない。なんせ末尾再帰になってないから、だ。
フツーの再帰から末尾再帰に変換する際に、「SchemeとActor理論」に従って、間に「継続渡し形式(CPS)」を挟めば割にすぐ変換が可能だ、と言う事が分かると思う。
CPS版の可変長引数版map用ツールは次のように書ける。
(define (map1 proc xs k)
(if (null? xs)
(k '())
(map1 proc (cdr xs) (lambda (u)
(k (cons (proc (car xs)) u))))))
(define (map-n proc xss k)
(if (member '() xss)
(k '())
(map-n proc (map1 cdr xss (lambda (x) x))
(lambda (u)
(k (cons (apply proc
(map1 car xss (lambda (x) x))) u))))))
これで少なくとも形式的末尾再帰には変換出来、これを使ってmap2をでっち上げる事が出来る。
(define map2
(let ((f (lambda (x) x)))
(case-lambda
((proc xs)
(map1 proc xs f))
((proc xs . args)(map-n proc `(,xs ,@args) f)))))
上の形式的末尾再帰は、ここまで来ると名前付きletで書き換えるのも難しくはないだろう。
その辺は宿題にしておこうと思う。