In general, neither foldr
nor foldl
can be implemented in terms of each other. The core operation of Foldable
is foldMap
, from which all the other operations may be derived. Neither foldr
nor foldl
are enough. However, the difference only shines through in the case of infinite or (partially) undefined structures, so there's a tendency to gloss over this fact.
@DamianLattenero has shown the "implementations" of foldl
and foldr
in terms of one another:
foldl' c = foldr (flip c)
foldr' c = foldl (flip c)
But they do not always have the correct behavior. Consider lists. Then, foldr (:) [] xs = xs
for all xs :: [a]
. However, foldr' (:) [] /= xs
for all xs
, because foldr' (:) [] xs = foldl (flip (:)) n xs
, and foldl
(in the case of lists) has to walk the entire spine of the list before it can produce an output. But, if xs
is infinite, foldl
can't walk the entire infinite list, so foldr' (:) [] xs
loops forever for infinite xs
, while foldr (:) [] xs
just produces xs
. foldl' = foldl
as desired, however. Essentially, for []
, foldr
is "natural" and foldl
is "unnatural". Implementing foldl
with foldr
works because you're just losing "naturalness", but implementing foldr
in terms of foldl
doesn't work, because you cannot recover that "natural" behavior.
On the flipside, consider
data Tsil a = Lin | Snoc (Tsil a) a
-- backwards version of data [a] = [] | (:) a [a]
In this case, foldl
is natural:
foldl c n Lin = n
foldl c n (Snoc xs x) = c (foldl c n xs) x
And foldr
is unnatural:
foldr c = foldl (flip c)
Now, foldl
has the good, "productive" behavior on infinite/partially undefined Tsil
s, while foldr
does not. Implementing foldr
in terms of foldl
works (as I just did above), but you cannot implement foldl
in terms of foldr
, because you cannot recover that productivity.
foldMap
avoids this issue. For []
:
foldMap f [] = mempty
foldMap f (x : xs) = f x <> foldMap f xs
-- foldMap f = foldr (\x r -> f x <> r) mempty
And for Tsil
:
foldMap f Lin = mempty
foldMap f (Snoc xs x) = foldMap f xs <> f x
-- foldMap f = foldl (\r x -> r <> f x) mempty
Now,
instance Semigroup [a] where
[] <> ys = ys
(x : xs) <> ys = x : (xs <> ys)
-- (<>) = (++)
instance Monoid [a] where mempty = []
instance Semigroup (Tsil a) where
ys <> Lin = ys
ys <> (Snoc xs x) = Snoc (ys <> xs) x
instance Monoid (Tsil a) where mempty = Lin
And we have
foldMap (: []) xs = xs -- even for infinite xs
foldMap (Snoc Lin) xs = xs -- even for infinite xs
Implementations for foldl
and foldr
are actually given in the documentation
foldr f z t = appEndo (foldMap (Endo . f) t ) z
foldl f z t = appEndo (getDual (foldMap (Dual . Endo . flip f) t)) z
f
is used to turn each a
in the t a
into a b -> b
(Endo b
), and then all the b -> b
s are composed together (foldr
does it one way, while foldl
composes them backwards with Dual (Endo b)
) and the final b -> b
is then applied to the initial value z :: b
.
foldr
is replaced with foldl
in specializations sum
, minimum
, etc. in the instance Foldable []
, for performance reasons. The idea is that you can't take the sum
of an infinite list anyway (this assumption is false, but it's generally true enough), so we don't need foldr
to handle it. Using foldl
is, in some cases, more performant than foldr
, so foldr
is changed to foldl
. I would expect, for Tsil
, that foldr
is sometimes more performant than foldl
, and therefore sum
, minimum
, etc. can be reimplemented in terms of foldr
, instead of fold
in order to get that performance improvement. Note that the documentation says that sum
, minimum
, etc. should be equivalent to the forms using foldMap
/fold
, but may be less defined, which is exactly what would happen.
Bit of an appendix, but I think it's worth noticing that:
genFoldr c n [] = n; genFoldr c n (x : xs) = c x (genFoldr c n xs)
instance Foldable [] where
foldl c = genFoldr (flip c)
foldr c = foldl (flip c)
-- similarly for Tsil
is actually a valid, lawful Foldable
instance, where both foldr
and foldl
are unnatural and neither can handle infinite structures (foldMap
is defaulted in terms of foldr
, and thus won't handle infinite lists either). In this case, foldr
and foldl
can be written in terms of each other (foldl c = foldr (flip c)
, though it is implemented with genFoldr
). However, this instance is undesirable, because we would really like a foldr
that can handle infinite lists, so we instead implement
instance Foldable [] where
foldr = genFoldr
foldl c = foldr (flip c)
where the equality foldr c = foldl (flip c)
no longer holds.