multirember&co

こんな関数が出てきた。

(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つ以上の値を集める際には関数を作るべし。