継続渡しスタイルの練習

アトムatomと、アトムからなるリストlatを受け取り、latの中にatomが含まれるかどうかを調べる以下のような関数

(define contains
  (lambda (atom lat)
    (cond
     ((null? lat) #f)
     ((eq? (car lat) atom) #t)
     (else (contains atom (cdr lat))))))

を、multirember&co関数を参考に(ってか、ほとんどそのまんまだけど...)継続渡しに書き換えてみた。
方針は

  • プロトタイプは(contains/cps atom lat cont)とする。
  • atomとlatは同じ意味で、contは継続。
  • 継続のプロトタイプは(cont targets ohers)とする。
  • latの要素を先頭から見ていって、atomと等しければtargetに、さもなくばothersに追加していく。

ってな感じです。

(define contains/cps
  (lambda (atom lat cont)
    (cond
     ((null? lat) (cont '() '()))
     ((eq? (car lat) atom)
      (contains/cps atom (cdr lat)
		    (lambda (targets others) (cont (cons (car lat) targets) others))))
     (else
      (contains/cps atom (cdr lat)
		    (lambda (targets others) (cont targets (cons (car lat) others))))))))

うん、multireber&coとほとんど同じ...。


実行結果

gosh> (define lat '(foo bar baz bar))
lat
gosh> lat
(foo bar baz bar)
gosh> (contains 'foo lat)
#t
gosh> (contains 'hoge lat)
#f
gosh> (define cont (lambda (targets others) (not (null? targets))))
cont
gosh> (contains/cps 'foo lat cont)
#t
gosh> (contains/cps 'hoge lat cont)
#f

出来てるんじゃないかなー。たぶん。


はじめは「継続とは次のステップでやりたいことをパッケージ化したもの」なんて説明、チンプンカンプンだったけど、ちょびっと分かってきた気がする。
確かに、contは再帰的に呼び出した(「呼び出す」って言い方、手続き的だなぁ...)先でやりたいことを無名の関数でもって指定している。


ただ、新しい継続を作る時、やっぱり何かが気持ち悪い。
何かハッキリわからないんだけど、確かに何かが気持ち悪いんですよ。

(contains/cps 'foo lat (lambda (targets others) (not (null? targets))))

って書くときは全然気持ち悪くないのに、再帰するときの

(contains/cps atom (cdr lat)
              (lambda (targets others) (cont (cons (car lat) targets) others)))

はナゼにこんなにも気持ち悪いのか。


latの先頭の要素から見ていって、その要素をtargetsかothersに振り分けつつ末尾の方に向って追加していくんだけど、targetsやothersは(null? lat)が成立して(cont '() '())が評価されるまで実体がわからない気持ち悪さっていうのかな。

前から後ろに向って伸びていくはずのリストなんだけど、(null? lat)で折り返してリストが末尾から確定していく気持ち悪さ感じっていうのかなぁー。


そんなこんな考えていると、(car lat)をどうやってtargetsやothersにくっつけたらいいのか分らなくなってくる。


僕の脳みそは受け入れることをかたくなに拒否。
とにかく、何がそんなに理解できないのか、自分でもよくわからない。
わからないことが、わからない。
くそー。