我目前正在学习使用方案的一些更高级的功能,并且我遇到了惰性列表的障碍。
基本上,我正在尝试创建一个无限的、延迟生成的列表,并在其上应用一个延迟过滤器,并且只采用一个元素。我希望这会消耗很少的内存:过滤器一次只查看一个元素,并且不需要存储以前的条目。这是我的尝试:
(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 亿的元素”),但重点是测试代码。问题是这会消耗大量内存。几秒钟之内,它就达到了许多 GB,而且没有任何减速的迹象,我不明白为什么。
我不明白为什么垃圾收集器不回收从列表中产生的内存。循环lazy-filter
是尾递归的,并且没有其他对惰性列表的引用,所以我觉得 GC 应该只是吞噬所有内存。为了确保我什至制作了一个在惰性过滤器循环的每次迭代中都运行垃圾收集器的版本,当然它没有帮助。
我的怀疑是,列表的头部有一些我没有看到的参考。就像,由delay
in 惰性过滤器创建的闭包以某种方式使seq
引用徘徊,或者其他什么。
我怎样才能重写它以不消耗无限量的内存?
如果这有什么不同,我正在运行 Chez Scheme,但我怀疑问题出在我身上,而不是方案实施