It is not widely known, but foldl'
is actually non-strict in its accumulator argument! Recall the type:
foldl' :: (a -> b -> a) -> a -> [b] -> a
Its strictness in argument 2 depends on the strictness of the function given for argument 1, as you see if you pass const
:
Prelude Data.List> foldl' (const (+1)) undefined [1]
2
Prelude Data.List> foldl' (const (+1)) undefined [1..4]
5
You would have thought, naively, that "foldl' is strict" means "strict in the accumulator argument". The above contradicts that.
However, it is even more insidious, as the strictness is only on the result of the function application in the cons case of the loop. So you still get bottoms if you enter the base case, but not the inductive case:
Prelude Data.List> foldl' (const (+1)) undefined []
*** Exception: Prelude.undefined
So the strictness in argument 2 also depends on the value of argument 3!
This is how I'd write it: "fully" strict in its 2nd argument.
foldl' f z0 xs0 = go z0 xs0
where
go !z [] = z
go !z (x:xs) = go (f z x) xs
Which is truly strict in its second argument, as you can see :
Prelude Data.List.Stream> foldl' (\a b -> 1) undefined [undefined]
*** Exception: Prelude.undefined
Compared with the Haskell2010 version:
Prelude Data.List.Stream> Data.List.foldl' (\a b -> 1 ) undefined [undefined]
1
This actuall has a practical impact -- the current definition will not unbox its accumulator argument consistently.
Historical note: this was discovered when we were specifying the list library's strictness semantics for the stream fusion paper in 2007, and the approach to specifying strictness is given in Duncan Coutt's PhD thesis.