I like to call foldr
as "fold from the right", while foldl
is "fold from the
left".
This is due to the position of the seed value, and the order of evaluation in
the AST. As you can see, for foldl
the position of the seed value is on the left
hand side of the tree, while foldr
seed value is on the right hand side of the
tree. In terms of total evaluation order (if we were to strictly evaluate the
entirely cobined result), the foldr
would evaluate from the right to left.
While foldl
would evaluate from left to right. The deepest node in the AST has
to get evaluated first before the parent nodes can take the recursive value with
the terminal value and apply both to its respective combining operation.
This order of evaluation can be illustrated with a simple left-associative minus combining operation:
foldr (-) 0 [1,2,3,4]
(1 - (2 - (3 - (4 - 0)))) = -2
-
/ \
1 -
/ \
2 -
/ \
3 -
/ \
4 0
foldl (-) 0 [1,2,3,4]
((((0 - 1) - 2) - 3) - 4) = -10
-
/ \
- 4
/ \
- 3
/ \
- 2
/ \
0 1
Another way to remember it is that foldr
has a right biased tree, while foldl
is a left biased tree. The tree is the AST. Looking at the tree again, one can
also identify that the foldr
preserves the order of the right-recursive list
constructors. While the foldl
reverses the order of list constructors.
It is interesting to note that most strict/imperative languages naturally gravitate to
foldl
, they obviously mostly call it "array reduce" or some variant of it. Strict
languages don't have much use for foldr
, and their reduce functions seem to always
default to foldl
. When programming imperatively and strictly, you tend to think
left to right, hence evaluating/combining a list in a left to right manner induces
the foldl
bias.
Only foldr
is lazy and can be used for codata/infinite streams. While foldl
is tail-recursive (enhanced with strict application foldl'
can avoid stack overflow).
This is why foldr
should be used by default in Haskellin order preserve laziness
across function composition. However the laziness can only be taken advantage
of, if the combining function is a data constructor, which can be lazily deconstructed.
Refer to https://wiki.haskell.org/Maintaining_laziness for more information!