我决定认真对待Haskell,并围绕着使用foldl和foldr。他们真的觉得很像Clojure的减少 - 但我可能错了,很快就遇到了问题,我希望有人会轻易解释。
使用此文档: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的类型系统 - 产生的数字不适合[默认] Long,而+'会在需要时自动使用BigInteger 。最重要的是,没有堆栈溢出。因此,似乎表明Haskell / Clojure中的折叠/缩减要么实现得非常不同,要么我对haskell实现的使用是错误的。
如果它是相关的,这些是全局项目设置: - 包:[] - 解析器:lts-13.8
正如the Wiki所解释的那样,函数(+)
在其两个参数中都是严格的,这意味着当你尝试做1 + (2 + 3)
时,首先需要计算(2 + 3)
。虽然这看起来似乎不是一个大问题,但当你有一个很长的清单时,它就变成了。引用维基,
评估:
1 + (2 + (3 + (4 + (...))))
,1被推入堆栈。然后:评估
2 + (3 + (4 + (...)))
。所以2被推到了堆栈上。然后:评估
3 + (4 + (...))
。所以3被推到了堆栈上。然后:评估
4 + (...)
。所以4被推到了堆栈上。然后:当你评估一个足够大的(+)s链时,你的有限堆栈最终会运行。然后,这会触发堆栈溢出异常。
我对Clojure并不多,但是如果(+')
有效,那么它绝对不需要在减少之前对它的参数进行求值,这也是Haskell中的解决方案。
Foldl不能解决这个问题,因为众所周知,foldl必须在返回结果之前两次遍历整个列表 - 这是无效的 - 甚至那时(+)
仍然是严格的,因此可简化的表达式不会减少。
要解决此问题,您必须使用非严格函数。在标准前奏曲中,seq :: a -> b -> b
可以完全用于此,这就是foldl'
的工作原理。
再次引用维基,
foldr不仅是正确的折叠,它也是最常使用的折叠,特别是在将列表(或其他可折叠)转换为具有相同顺序的相关元素的列表时。值得注意的是,foldr可以有效地将无限列表转换为其他无限列表。出于这样的目的,它应该是您的第一个也是最自然的选择。例如,请注意foldr(:) [] == id。
foldl'的问题在于它反转了列表。如果你有一个没有问题的交换函数,所以如果你的列表是有限的(记住foldl必须全部去),foldl'
通常会更好。另一方面,如果你的函数由于某些原因不一定需要整个列表,或者列表可能是无限的,请去foldr