我决定对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
答案 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