【问题标题】:Questions about folds and stack overflows关于折叠和堆栈溢出的问题
【发布时间】:2023-03-05 22:13:01
【问题描述】:

Learn You a Haskell 谈到 foldl' 作为 foldl 的替代品,因为 foldl 容易发生堆栈溢出。

  • 根据 LYAH,foldl (+) 0 (replicate 1000000 1) 应该堆栈溢出,但它不会在我的机器上。 为什么不呢?即使我将这个数字增加到 1000 万,它也不会溢出。它只会占用大量内存,直到我的 OS X 计算机无法使用,我必须重新启动它。
  • 在什么情况下我应该使用foldl 而不是foldl'?根据我的经验,foldl'“正常工作”,而foldl 基本上可以让我的计算机崩溃(见上文)。
  • 我不明白为什么foldr 没有类似的东西。为什么foldr不能堆栈溢出,为什么没有foldr'?

【问题讨论】:

  • 第二个和第三个问题的答案在haskell.org/haskellwiki/Foldr_Foldl_Foldl'等着你
  • 计算机遇到堆栈溢出或内存不足的确切时间取决于您的操作系统、您正在运行的其他程序以及您的物理硬件。例如,树莓派会比图形渲染计算机(可能有 >100GB 的 RAM)更快地耗尽 RAM。您的操作系统也可能以特定方式使用交换来管理请求更多内存的程序。
  • 快速而肮脏的 answers 假设: 1. 不确定,您是否有任何优化? 2. 始终使用 foldl'!这是一个经验法则。 3. foldr 是惰性的(顺便说一句,它可以在无限列表上工作),所以 IIRC 它甚至没有一个严格的累加器(它不是尾递归)。
  • Haskell中左右折叠的区别也有很多资源,包括many questions on SO already。

标签: haskell stack-overflow fold


【解决方案1】:

它不会因堆栈溢出而崩溃,因为堆栈现在默认是无限的。也就是说,默认的 GHC 运行时行为是允许堆栈无限增长——没有可以触发堆栈溢出错误的界限。

https://ghc.haskell.org/trac/ghc/ticket/8189

下面是它的工作原理:

线程堆栈(包括主线程的堆栈)位于堆上。作为 堆栈增长,根据需要添加新的堆栈块;如果堆栈 再次收缩,这些额外的堆栈块被垃圾回收 集电极。默认的初始堆栈大小故意很小,在 为了将线程创建的时间和空间开销保持在 最低限度,并且使生成线程变得实用,即使是很小的 作品。

【讨论】:

  • 你可能想充实你的答案,但无论如何 +1。
【解决方案2】:

为什么foldr堆栈不能溢出,为什么没有foldr'?

嗯,foldr 不是尾递归的,即它不直接调用自身:

foldr f a (x:xs) = f x (foldr f a xs)

在减少foldr 之后,控制权传递给用户提供的f。因此,没有必要在将参数传递给f 之前强制使用foldr':如果需要,调用者可以只传递严格的f(例如利用爆炸模式或seq)。

它是否溢出堆栈取决于f 做了什么。例如,

f x y = x

将导致列表仅在其第一个元素中被访问。相反,

f x y = seq y x

可能导致堆栈溢出(或内存密集型行为)。相反,

f x y = x+1 : y

将导致输出列表延迟生成,类似于map (+1) xs,不会有任何令人讨厌的意外。

正如@dfeuer 指出的那样,存在Data.Foldable.foldr',它在任何Foldable 上作为严格的右折叠操作。如上所述,在列表上,这几乎是多余的。在其他 Foldable 类型上,它可能是有意义的。

【讨论】:

  • 值得指出的是有 foldr'。它只是在Data.Foldable 而不是Data.List,因为它对于列表几乎完全没用。
  • 换一种说法,foldr' 在列表上几乎没用的原因是它们从一开始就是单链接的,你必须递归到最后才能找到开始折叠的元素.因此,对于一个长列表,无论如何您最终都会使用大量堆栈或堆。
猜你喜欢
  • 2010-10-27
  • 2010-10-15
  • 2011-01-09
  • 2017-05-02
  • 1970-01-01
  • 1970-01-01
  • 2019-05-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多