私は現在、スキームのもう少し高度な機能を使用することを学んでおり、遅延リストで障害にぶつかりました。
基本的に、私は無限の遅延生成リストを作成し、それに遅延フィルターを適用して、単一の要素のみを取得しようとしています。私の望みは、これがメモリをほとんど消費しないことでした: フィルタは一度に 1 つの要素だけを調べ、以前のエントリを保存する必要はありません。これが私の試みです:
(define lazy-inf-seq
(lambda (start next)
(delay (cons start (lazy-inf-seq (next start) next)))))
(define lazy-arithmetic-sequence
(lambda (start d)
(lazy-inf-seq start (lambda (v) (+ v d)))))
(define lazy-filter
(lambda (pred seq)
(delay
(let loop ([sequence seq])
(let ([forced (force sequence)])
(cond [(null? forced) '()]
[(pred (car forced))
(cons (car forced) (lazy-filter pred (cdr forced)))]
[else (loop (cdr forced))]))))))
したがって、明確にするために、ここでの「遅延リスト」は、(force)
d のときに を生成するプロシージャです。これがスキームの「標準的な」怠惰なリストなのかどうかはわかりませんが、私にとって最も理にかなっているのは変種でした。(head . tail)
head
tail
(lazy-arithmetic-sequence a b)
関数は(遅延して)無限リストを生成しますa, a+b, a+2b, a+3b, ...
このlazy-filter
関数は問題の核心です。述語と遅延リストを取り、フィルタリングされたすべての要素を含む遅延リストを返します。強制されると、入力リストを調べて、含める必要がある最初の要素を見つけ、残りのリストの遅延フィルターで処理されたその要素を返します。
これをテストするために、次の行を実行します。
(force (lazy-filter (lambda (v) (= v 1000000000)) (lazy-arithmetic-sequence 0 1)))
もちろん、これはかなり無意味なフィルター (「このリストで 0 から無限大までの値が 10 億の要素を見つける」) ですが、ポイントはコードをテストすることです。問題は、これが途方もない量のメモリを消費することです。数秒以内に数ギガバイトに達し、速度が低下する兆候は見られません。その理由はわかりません。
ガベージ コレクターがリストから生成されたメモリを再利用しない理由がわかりません。ループインlazy-filter
は末尾再帰であり、レイジーリストへの参照は他にないため、GC はそのメモリをすべてむさぼり食うべきだと思います。レイジー フィルター ループの反復ごとにガベージ コレクターを実行するバージョンも作成しましたが、もちろん役に立ちませんでした。
私の疑いは、私が見ていないリストの先頭にぶら下がっているいくつかの参照があるということです. delay
同様に、 in lazy-filterによって作成されたクロージャーは、どういうわけかseq
参照をハングアップさせているか、何かです。
これを書き直して、無限のメモリを消費しないようにするにはどうすればよいですか?
それが違いを生む場合、私は Chez Scheme を実行していますが、問題はスキームの実装ではなく、私にあると思われます