The difference is that foldl *must* produce the entire list of thunks, even if f is lazy in its first argument.
There's no foldl that can perform better given a sufficiently-lazy f; given
head = foldr go fail where
go x y = x
fail = error "head: empty list"
head [a,b,c,d]
= foldr go fail [a,b,c,d]
= go a (foldr go fail [b,c,d])
= a
you might think you can define
last = foldl go fail where
go x y = y
fail = error "last: empty list"
but this fails to be sufficiently lazy:
last [a,b,c,d]
= foldl go fail [a,b,c,d]
= foldl go (go fail a) [b,c,d]
= foldl go (go (go fail a) b) [c,d]
= foldl go (go (go (go fail a) b) c) [d]
= foldl go (go (go (go (go fail a) b) c) d) []
= go (go (go (go fail a) b) c) d
= d
which allocates lots of extra space for thunks, which may even take more memory than the original list.
Whereas if last uses foldl':
last [a,b,c,d]
= foldl' go fail [a,b,c,d]
= let z = go fail a in seq z $ foldl' go z [b,c,d]
= foldl' go a [b,c,d]
= let z = go a b in seq z $ foldl' go z [c,d]
= foldl' go b [c,d]
= ...
= let z = go c d in seq z $ foldl' go z []
= foldl' go d []
= d
-- ryan