问题在于尾部递归优化是内存优化,而不是执行时间优化!
尾递归优化避免了记住每个递归调用的值的需要。
因此,foldl实际上是“好”,而foldr是“坏”。
例如,考虑foldr和foldl的定义:
foldl f z [] = z
foldl f z (x:xs) = foldl f (z `f` x) xs
foldr f z [] = z
foldr f z (x:xs) = x `f` (foldr f z xs)
这就是表达式“ foldl(+)0 [1,2,3]”的计算方式:
foldl (+) 0 [1, 2, 3]
foldl (+) (0+1) [2, 3]
foldl (+) ((0+1)+2) [3]
foldl (+) (((0+1)+2)+3) [ ]
(((0+1)+2)+3)
((1+2)+3)
(3+3)
6
请注意,foldl不会记住值0、1、2 ...,而是将整个表达式(((0 + 1)+2)+3)惰性地传递为参数,并且直到最后一次求值时才对其求值foldl,它到达基本情况并返回传递的值,因为尚未评估第二个参数(z)。
另一方面,这就是文件夹的工作方式:
foldr (+) 0 [1, 2, 3]
1 + (foldr (+) 0 [2, 3])
1 + (2 + (foldr (+) 0 [3]))
1 + (2 + (3 + (foldr (+) 0 [])))
1 + (2 + (3 + 0)))
1 + (2 + 3)
1 + 5
6
此处的重要区别是foldl在上一次调用中对整个表达式求值,从而避免了需要返回到记住的值(folder no)的情况。folder为每个调用记住一个整数,并在每个调用中执行加法运算。
重要的是要记住,foldr和foldl并不总是等效的。例如,尝试以拥抱方式计算以下表达式:
foldr (&&) True (False:(repeat True))
foldl (&&) True (False:(repeat True))
foldr和foldl仅在此处描述的某些条件下等效
(对不起,我的英语不好)
a
为a = (:)