问题在于尾递归优化是一种内存优化,而非执行时间优化!
尾递归优化避免了需要为每个递归调用记住值的需求。
因此,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的工作方式:
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在最后一次调用时评估整个表达式,避免了需要回到记住的值的需求,而foldr则不同。foldr为每个调用记住一个整数,并在每个调用中执行加法。
需要记住的是,foldr和foldl并不总是等价的。例如,在hugs中尝试计算这些表达式:
foldr (&&) True (False:(repeat True))
foldl (&&) True (False:(repeat True))
在特定条件下,foldr和foldl是等价的,这些条件在这里进行了描述。
(对不起我的英语不太好)
a
写成a = (:)
。 - John Lb
定义为b = flip (:)
。 - Will Ness