我一直认为尾递归函数在性能方面比非尾递归版本更好 . 因此,计算列表中的项目可能会像这样实现:
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
的应用程序的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非严格评估似乎使得尾递归不那么重要 . 然而,我有这种感觉,对于计算列表中的项目,严格 foldl
应该比任何 foldr
表现更好,因为我们无法从 Integer
中提取任何内容 .
总而言之,我认为这些是 Map 和计数的更好实现(使用折叠):
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
它是否正确?
1 回答
这是正确的,尾部调用更有效 . 但是,创造大量雷的成本可能超过这种好处,
foldl
就是这种情况 .吃蛋糕和吃它的方法是确保累积器不会被打碎,而是急切地评估:
当然,这是
foldl'
函数 .TL; DR:永远不要使用
foldl
,但请使用foldl'
.