为什么foldl看起来有害,尽管它是尾递归的?

时间:2018-01-22 14:00:11

标签: haskell recursion fold

我一直认为尾递归函数更好 性能比非尾递归版本。因此,计算列表中的项目可能的实现方式如下:

count:: [a] -> Int
count [] = 0
count (x:xs) = 1 + count xs

但是这个函数不是尾递归的,因此不尽可能高效。修复是累积计数,如下所示:

_count:: Num b => b -> [a] -> b
_count b [] = b
_count b (x:xs) = _count (b + 1) xs

count:: [a] -> Int
count = _count 0

这可以通过尾递归折叠轻松实现:

myfold:: (b -> a -> b) -> b -> [a] -> b
myfold f b [] = b
myfold f b (x:xs) = myfold f (f b x) xs

count = myfold incr 0
   where incr c _ = c + 1

但是,我记得有关左右对折的事情。结果是 myfold是左侧折叠,根据Real World Haskell的说法不应该使用:

  

这对测试很方便,但我们绝不会在实践中使用foldl。

...因为f b x

的应用程序的中断

所以,我试图将myfold重写为正确的折叠:

myfoldr:: (a -> b -> b) -> b -> [a] -> b
myfoldr f b [] = b
myfoldr f b (x:xs) = f x (myfoldr f b xs)

但这不是尾递归。

然而,似乎Haskell非严格评估会产生尾递归 不太重要。然而,我有这种感觉,对于计算列表中的项目,严格的foldl应该比任何foldr都要好,因为我们无法从{{1}中提取任何内容}}

总而言之,我认为这些是地图和计数的更好实现(使用折叠):

Integer

这是对的吗?

1 个答案:

答案 0 :(得分:8)

  

然而,似乎Haskell非严格评估使得尾递归不那么重要。然而,我有这种感觉,对于计算列表中的项目,严格的foldl应该比任何foldr都要好,因为我们无法从Integer中提取任何内容。

这是正确的,尾部调用 更有效。但是,创建大型thunk的成本可能超过这个好处,foldl就属于这种情况。

吃蛋糕和吃它的方法是确保累积器不会被打碎,而是急切地评估:

myfold:: (b -> a -> b) -> b -> [a] -> b
myfold f !b [] = b
myfold f !b (x:xs) = myfold f (f b x) xs

当然,这是foldl'函数。

TL; DR:永远不要使用foldl,但请使用foldl'