【问题标题】: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 中有点痛苦。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-17
    • 1970-01-01
    • 2023-04-05
    • 2014-02-28
    • 2018-09-03
    • 2014-11-13
    相关资源
    最近更新 更多