[Haskell-beginners] Is foldr slow?

Zhi-Qiang Lei zhiqiang.lei at gmail.com
Mon Feb 6 17:42:09 CET 2012


Appreciate. That's favorable to understand recursion in Haskell.

On Feb 5, 2012, at 9:25 PM, Daniel Fischer wrote:

> On Sunday 05 February 2012, 11:33:35, Zhi-Qiang Lei wrote:
>> Thank you very much. You are right. foldl performs much better here. I
>> thought foldr always has a performance advantage due to its tail
>> recursion.
> 
> Firstly, tail recursion isn't automatically better in Haskell. Due to 
> laziness, the evaluation sequence is different from what it'd be in eager 
> languages, and not necessarily the order in which the steps are written in 
> the code.
> 
> If the recursive call to a (non tail-recursive) function is in a lazy 
> argument position of the result, a lot of evaluation can take place before 
> the recursive call is demanded.
> 
> A tail recursive function on the other hand cannot deliver any part of the 
> result before the recursion is completed.
> 
> Secondly, foldr is not tail recursive, foldl is:
> 
> foldr f z (x:xs) = x `f` (foldr f z xs)
> 
> foldl f z (x:xs) = foldl f (f z x) xs
> 
> Thus if 'f' is lazy in its second argument, like for example (++), the 
> result can be partially delivered before the recursive call, in
> 
> x ++ (foldr (++) [] xs)
> 
> the whole list x has to be evaluated before the evaluation of foldr takes 
> its next step.
> 
> But if 'f' is strict in its second argument, like for example (+) on Int, 
> the recursive call has to be evaluated before the top level call to 'f' can 
> be evaluated, so a stack of function calls has to be built before any 
> evaluation of an 'f'-call can start, and that stack has to be unwound 
> during the evaluation of the 'f'-calls from the innermost to the outermost.
> That gives you two traversals: build stack, unwind.
> 
> In such cases, tail recursion (a left fold) is better because then the call 
> to 'f' can be evaluated before the recursive call - but it has to be 
> forced, or it will generate a thunk ((...(z `f` x1) ... ) `f` xn-1) `f` xn 
> (and when that thunk is finally evaluated it nuilds a stack of 'f'-calls, 
> which is then unwound, so you get three traversals: build thunk, thunk -> 
> stack, unwind - unless 'f' is lazy in its first argument, like flip (++), 
> then the thunk may be consumed without building the stack).


Best regards,
Zhi-Qiang Lei
zhiqiang.lei at gmail.com




More information about the Beginners mailing list