【问题标题】:Does Haskell discards intermediary results during lazy evaluation?Haskell 会在惰性求值期间丢弃中间结果吗?
【发布时间】:2021-05-07 04:15:25
【问题描述】:
如果我递归定义斐波那契数列:
fibo_lazy_list = 0 : 1 : zipWith (+) fibo_lazy_list (tail fibo_lazy_list)
然后询问给定值之上的第一个元素,例如:
print $ find (>100) fibo_lazy_list
我了解 Haskell 仅评估获得打印结果所需的元素。但是它们是否都保存在内存中直到打印?由于计算最后一个元素只需要列表的两个元素,Haskell 是释放最左边的元素还是列表在内存中不断增长?
【问题讨论】:
标签:
haskell
lazy-evaluation
lazy-sequences
【解决方案1】:
视情况而定。
对于真实世界的 Haskell 代码来说,这实际上是最棘手的事情之一:为了避免因持有不必要的数据而导致的内存泄漏,这应该只是中介,但实际上是依赖于一些尚未评估的惰性重击,因此不能被垃圾收集。
在您的示例中,fibo_lazy_list 的前导元素(顺便说一句,请在 Haskell 中使用 camelCase,而不是 underscore_case)不会被垃圾收集,只要 fibo_lazy_list 被仍然可能被引用的东西评估。但是一旦超出范围,这是不可能的。所以如果你这样写
print $ let fibo_lazy_list = 0 : 1 : zipWith (+) fibo_lazy_list (tail fibo_lazy_list)
in find (>100) fibo_lazy_list
那么您可以非常确定未使用的元素将被垃圾回收,甚至可能在找到要打印的元素之前。
如果fibo_lazy_list 在顶层定义,并且是CAF(如果类型不是多态的,则为CAF)
fiboLazyList :: [Integer]
fiboLazyList = 0 : 1 : zipWith (+) fiboLazyList (tail fiboLazyList)
main :: IO ()
main = do
...
print $ find (>100) fiboLazyList
...
那么,即使在提取了>100 之后,您最好期望所有前导元素都保留在内存中。
编译器优化可能在这里很有帮助,严格性注释也可以。但正如我所说,这在 Haskell 中有点痛苦。