Welcome to the world of lazy appreciation.
When you think of it in terms of rigorous evaluation, foldl looks “good” and foldr looks “bad” because foldl is tail recursive, but foldr would have to build a tower on the stack in order to process the last element first.
However, lazy appreciation turns tables. Take, for example, the definition of a mapping function:
map :: (a -> b) -> [a] -> [b] map _ [] = [] map f (x:xs) = fx : map f xs
This would not be too good if Haskell used a strict estimate, since he had to calculate the tail first and then add the element (for all elements in the list). It seems that the only way to do this effectively is to create elements in the opposite direction.
However, thanks to Haskell's lazy appreciation, this map feature is really effective. Lists in Haskell can be thought of as generators, and this map function generates its first element by applying f to the first element of the input list. When he needs a second element, he does the same thing again (without using extra space).
It turns out that map can be described in terms of foldr :
map f xs = foldr (\x ys -> fx : ys) [] xs
It is hard to say looking at it, but a lazy estimate, because foldr can immediately give its first argument f :
foldr fz [] = z foldr fz (x:xs) = fx (foldr fz xs)
Since f defined by map can return the first element of the result list using only the first parameter, addition can work lazily in constant space.
Now lazy appreciation bites off. For example, try running the amount [1..1000000]. This causes a stack overflow. Why should it be? He should just evaluate left to right, right?
Let's see how this evaluates Haskell:
foldl fz [] = z foldl fz (x:xs) = foldl f (fzx) xs sum = foldl (+) 0 sum [1..1000000] = foldl (+) 0 [1..1000000] = foldl (+) ((+) 0 1) [2..1000000] = foldl (+) ((+) ((+) 0 1) 2) [3..1000000] = foldl (+) ((+) ((+) ((+) 0 1) 2) 3) [4..1000000] ... = (+) ((+) ((+) (...) 999999) 1000000)
Haskell is too lazy to add on as they become available. Instead, it ends with a tower of unreasonable thunders who must be forced to obtain a number. During this evaluation, the stack overflows, as it must deeply re-evaluate all the tricks.
Fortunately, Data.List has a special function called foldl' that works strictly. foldl' (+) 0 [1..1000000] will not overflow the overflow. (Note: I tried replacing foldl with foldl' in your test, but it actually ran it slower.)