Haskell的foldr / l和Clojure减少了

问题描述 投票:0回答:1

我决定认真对待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

haskell clojure stack-overflow reduce fold
1个回答
-2
投票

Problem

正如the Wiki所解释的那样,函数(+)在其两个参数中都是严格的,这意味着当你尝试做1 + (2 + 3)时,首先需要计算(2 + 3)。虽然这看起来似乎不是一个大问题,但当你有一个很长的清单时,它就变成了。引用维基,

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

然后:评估2 + (3 + (4 + (...)))。所以2被推到了堆栈上。

然后:评估3 + (4 + (...))。所以3被推到了堆栈上。

然后:评估4 + (...)。所以4被推到了堆栈上。

然后:当你评估一个足够大的(+)s链时,你的有限堆栈最终会运行。然后,这会触发堆栈溢出异常。

我对Clojure并不多,但是如果(+')有效,那么它绝对不需要在减少之前对它的参数进行求值,这也是Haskell中的解决方案。

Solution

Foldl不能解决这个问题,因为众所周知,foldl必须在返回结果之前两次遍历整个列表 - 这是无效的 - 甚至那时(+)仍然是严格的,因此可简化的表达式不会减少。

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

再次引用维基,

foldr不仅是正确的折叠,它也是最常使用的折叠,特别是在将列表(或其他可折叠)转换为具有相同顺序的相关元素的列表时。值得注意的是,foldr可以有效地将无限列表转换为其他无限列表。出于这样的目的,它应该是您的第一个也是最自然的选择。例如,请注意foldr(:) [] == id。

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

© www.soinside.com 2019 - 2024. All rights reserved.