こんな関数が出てきた。
(define multirember&co (lambda (a lat col) (cond ((null? lat) (col (quote ()) (quote ()))) ((eq? (car lat) a) (multirember&co a (cdr lat) (lambda (newlat seen) (col newlat (cons (car lat) seen))))) (else (multirember&co a (cdr lat) (lambda (newlat seen) (col (cons (car lat) newlat) seen)))))))
これはかなり複雑だ。
基本的には multirember と同じような形をしているけど、引数がひとつ多い。 a はアトム、lat はラット、で、col は何かの関数らしい。
それから、(eq? (car lat) a)
のときも else
のときも (cdr lat)
で再帰している。しかもそれぞれ新しい関数を作っている。
じっくりいこう。
col の簡単な例が出てきた。
(define a-friend (lambda (x y) (null? y)))
2つの引数をとり、1つ目は単に無視して2つ目が null? かどうかを返している。
で、この関数を使ってまずは簡単な例、(multirember&co 'tuna '() a-friend)
の値を求めてみよう。この場合、lat が空リストなので、cond の最初の条件に当てはまる。ということはその値は (col (quote ()) (quote ()))
で、いま col は a-friend なので値は #t になる。
次は、もうちょっと難しい例、(multirember&co 'tuna '(tuna) a-friend)
の値を求めてみよう。今度は、(eq? (car lat) a)
(ここで a は tuna、lat は (tuna))なので、cond の2番目の値になる。そして (cdr lat)
と新しい関数 (lambda (newlat seen) (col newlat (cons (car lat) seen)))
に対して再帰している。この関数は multirember&co の引数 col に当たるもので、col とは collector (収集子)の短縮形だそうだ(収集子は「continuation(継続)」とも呼ばれる)。
この新しい関数(収集子)に名前をつけてやったほうがわかり易い。new-friend としよう。このとき col は a-friend、(car lat)
は tuna なので、new-friend は次のようになる。
(define new-friend (lambda (newlat seen) (a-frined newlat (cons 'tuna seen))))
この関数を使って、multirember&co の再帰部分を書くと、(multirember&co 'tuna '() new-friend)
となる。
そうすると今度は (null? lat)
(ここで lat は ‘())なので、値は (new-friend (quote ()) (quote ()))
となり、new-friend の定義より (a-friend (quote ()) (cons 'tuna (quote ())))
、つまり #f だ(2番目の引数が空リストじゃないから)。
さらに難しい例、(multirember&co 'tuna '(and tuna) a-friend)
の値を求めてみよう。今度は、cond の3番目の条件に当てはまり、また別の収集子で再帰する。この収集子の名前を latest-friend とすれば次のようになる。
(define latest-friend (lambda (newlat seen) (a-friend (cons 'and newlat) seen))))
すると、再帰は (multirember&co 'tuna '(tuna) latest-friend)
となり、cond の2番目の条件でさらに (multirember&co 'tuna '() (lambda (newlat seen) (latest-friend newlat (cons 'tuna seen))))
で再帰する。
最後は cond の最初の条件の値 ((lambda (newlat seen) (latest-friend newlat (cons 'tuna seen))) (quote ()) (quote ()))
に行き当たる。
さあ、今度は逆にさかのぼろう。最後の関数適用の値は (latest-friend (quote ()) ‘(tuna)) だ。さらにさかのぼると (a-friend ‘(and) ‘(tuna)) となり、最終的な値は #f になる。
結局、(multirember&co a lat col) がどんな関数かというと、lat をすべて検索して、a と eq? であるアトムを最初のリスト l1 に、eq? でないアトムを2番目のリスト l2 に収集子、最後に (col l1 l2)
の値を返す関数、ということができる。
試してみよう。
(define multirember&co (lambda (a lat col) (cond ((null? lat) (col (quote ()) (quote ()))) ((eq? (car lat) a) (multirember&co a (cdr lat) (lambda (newlat seen) (col newlat (cons (car lat) seen))))) (else (multirember&co a (cdr lat) (lambda (newlat seen) (col (cons (car lat) newlat) seen))))))) (define a-friend (lambda (x y) (null? y))) (print (multirember&co 'tuna '() a-friend)) (print (multirember&co 'tuna '(tuna) a-friend)) (print (multirember&co 'tuna '(and tuna) a-friend))
^o^ > gosh multirember_and_co.scm #t #f #f
OKのようだ。
最後に、次の関数 last-friend を col とした場合を求めてみよう。
(define multirember&co (lambda (a lat col) (cond ((null? lat) (col (quote ()) (quote ()))) ((eq? (car lat) a) (multirember&co a (cdr lat) (lambda (newlat seen) (col newlat (cons (car lat) seen))))) (else (multirember&co a (cdr lat) (lambda (newlat seen) (col (cons (car lat) newlat) seen))))))) (define last-friend (lambda (x y) (length x))) (print (multirember&co 'tuna '(strawberries tuna and swordfish) last-friend))
^o^ > gosh multirember_and_co2.scm 3
last-friend は tuna 以外のアトムを集めたリストの長さを求めている。だから答は3になるわけだ。
第10の戒律
同時に2つ以上の値を集める際には関数を作るべし。