尽管 foldl 是尾递归的,但为什么它似乎是有害的?
Why does foldl seems to be harmful despite being tail-recursive?
我一直认为尾递归函数在以下方面更好
性能优于非尾递归版本。因此,对列表中的项目进行计数 可能会 像这样实现:
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 不应使用:
This is convenient for testing, but we will never use foldl in practice.
...因为 f b x
.
的应用程序的 thunking
因此,我尝试将 myfold
重写为右折:
myfoldr:: (a -> b -> b) -> b -> [a] -> b
myfoldr f b [] = b
myfoldr f b (x:xs) = f x (myfoldr f b xs)
但这不是尾递归。
看来,Haskell 非严格求值使得尾递归
不太重要。然而,我有这种感觉,对于 counting 列表中的项目,严格的 foldl
应该比任何 foldr
表现得更好,因为我们无法从列表中提取任何东西Integer
.
总而言之,我认为这些是 map 和 count 的更好实现(使用折叠):
map:: (a -> b) -> [a] -> [b]
map f = foldr g []
where g x fxs = (f x):fxs
count:: [a] -> Int
count = foldl incr 0
where incr c _ = c + 1
这是正确的吗?
It seems, then, that Haskell non-strict evaluation makes tail-recursiveness less important. Yet, I have this feeling that for counting items in lists a strict foldl
should perform better than any foldr
, because there's no way we can extract anything from an 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'
。
我一直认为尾递归函数在以下方面更好 性能优于非尾递归版本。因此,对列表中的项目进行计数 可能会 像这样实现:
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 不应使用:
This is convenient for testing, but we will never use foldl in practice.
...因为 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 非严格求值使得尾递归
不太重要。然而,我有这种感觉,对于 counting 列表中的项目,严格的 foldl
应该比任何 foldr
表现得更好,因为我们无法从列表中提取任何东西Integer
.
总而言之,我认为这些是 map 和 count 的更好实现(使用折叠):
map:: (a -> b) -> [a] -> [b]
map f = foldr g []
where g x fxs = (f x):fxs
count:: [a] -> Int
count = foldl incr 0
where incr c _ = c + 1
这是正确的吗?
It seems, then, that Haskell non-strict evaluation makes tail-recursiveness less important. Yet, I have this feeling that for counting items in lists a strict
foldl
should perform better than anyfoldr
, because there's no way we can extract anything from anInteger
.
这是正确的,尾调用 更有效。但是创建大型 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'
。