Haskell的folder / l和Clojure的reduce

时间:2019-02-25 01:02:09

标签: haskell clojure stack-overflow reduce fold

我决定对Haskell认真对待,并全神贯注地使用 foldl folder 。他们确实感觉很像Clojure的 reduce -但我可能错了,很快就遇到了一个问题,希望有人能轻松解释。

使用此文档: https://wiki.haskell.org/Foldr_Foldl_Foldl'

在深入实现自己的foldr / foldl版本之前,我决定先测试Prelude中的现有版本:

± |master U:2 ✗| → ghci
GHCi, version 8.6.3: http://www.haskell.org/ghc/  :? for help
Loaded GHCi configuration from /Users/akarpov/.ghc/ghci.conf
Prelude> foldr (+) 0 [1..9999999]
49999995000000
Prelude> foldr (+) 0 [1..99999999]
*** Exception: stack overflow

看不到它来(使用foldl时结果相同);我宁愿期待Clojure的帮助:

> (time (reduce +' (range 1 99999999)))
"Elapsed time: 3435.638258 msecs"
4999999850000001

唯一明显(且无关紧要)的区别是使用+'而不是+,但这只是为了容纳JVM的类型系统-生成的数字不适合[default] Long,而+'将自动使用需要时使用BigInteger。最重要的是,没有堆栈溢出。 因此,这似乎表明,Haskell / Clojure中的折叠/缩小实现方式非常不同,或者我对haskell的实现使用了错误的信息。

如果相关,这些是全局项目设置: -套餐:[] -解析器:lts-13.8

1 个答案:

答案 0 :(得分:-2)

问题

the Wiki所述,函数(+)在两个参数上都是严格的,这意味着当您尝试执行1 + (2 + 3)时,首先需要计算(2 + 3)。尽管乍一看这似乎不是一个大问题,但当您的清单很长时,它就会变成问题。引用维基,

  

评估:          1 + (2 + (3 + (4 + (...)))),将1压入堆栈。

     

然后:2 + (3 + (4 + (...)))被求值。因此2被压入堆栈。

     

然后:3 + (4 + (...))被求值。因此3被压入堆栈。

     

然后:4 + (...)被求值。因此4被压入堆栈。

     

然后:当您评估一个足够大的(+)链时,您的有限堆栈最终将耗尽。然后触发堆栈溢出异常。

我对Clojure不太了解,但是如果(+')有效,那么它肯定不需要在还原之前就需要对其参数进行评估,这也是Haskell的解决方案。

解决方案

Foldl无法解决问题,因为众所周知,Foldl必须在返回结果之前遍历整个列表两次,这样效率不高,而且即使(+)仍然严格,因此可还原的表达没有减少。

要解决此问题,必须使用非严格函数。在标准的序曲中,seq :: a -> b -> b可以完全用于此目的,这就是foldl'的工作方式。

再次引用Wiki,

  

文件夹不仅是正确的折叠,而且也是最正确的折叠   折叠使用,尤其是在转换列表(或其他)时   折叠式)放入具有相同顺序的相关元素的列表中。   值得注意的是,即使对于无限列表,文件夹也将有效   进入其他无限列表为此,它应该是您的第一个   和最自然的选择。例如,请注意文件夹(:) [] == id。

foldl'的问题在于它会反转列表。如果您有交换函数,这不是问题,那么如果您的列表是有限的(请记住foldl必须全部通过),foldl'通常会更好。另一方面,如果由于某些原因您的函数不一定需要整个列表,或者列表可能是无限的,请使用foldr