问题
Can foldr
and foldl
be defined in terms of each other?
Programming in Haskell by Hutton says
What do we need to define manually? The minimal complete definition for an instance of the
Foldable
class is to define eitherfoldMap
orfoldr
, as all other functions in the class can be derived from either of these two using the default definitions and the instance for lists.
So how can foldl
be defined in terms of foldr
?
Can foldr
be defined in terms of foldl
, so that we can define a Foldable
type by defining foldl
?
Why is it that in Foldable
, fold
is defined in terms of foldMap
which is defined in terms of foldr
, while in list foldable, some specializations of fold
are defined in terms of foldl
as:
maximum :: Ord a => [a] -> a
maximum = foldl max
minimum :: Ord a => [a] -> a
minimum = foldl min
sum :: Num a => [a] -> a
sum = foldl (+) 0
product :: Num a => [a] -> a
product = foldl (*) 1
? Can they be rewritten as
maximum :: Ord a => [a] -> a
maximum = foldr max
minimum :: Ord a => [a] -> a
minimum = foldr min
sum :: Num a => [a] -> a
sum = foldr (+) 0
product :: Num a => [a] -> a
product = foldr (*) 1
Thanks.
回答1:
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.
回答2:
In the case of lists: foldl
can be defined in terms of foldr
but not vice-versa.
foldl f a l = foldr (\b e c -> e (f c b)) id l a
For other types which implement Foldable
: the opposite may be true.
回答3:
Here's a type for which neither foldl
nor foldr
can be implemented in terms of the other:
import Data.Functor.Reverse
import Data.Monoid
data DL a = DL [a] (Reverse [] a)
deriving Foldable
The Foldable
implementation looks like
instance Foldable DL where
foldMap f (DL front rear) = foldMap f front <> foldMap f rear
Inlining the Foldable
instance for Reverse []
, and adding the corresponding foldr
and foldl
,
foldMap f (DL front rear) = foldMap f front <> getDual (foldMap (Dual . f) (getReverse rear))
foldr c n (DL xs (Reverse ys)) =
foldr c (foldl (flip c) n ys) xs
foldl f b (DL xs (Reverse ys)) =
foldr (flip f) (foldl f b xs) ys
If the front list is infinite, then foldr
defined using foldl
won't work. If the rear list is infinite, then foldl
defined using foldr
won't work.
回答4:
Edit 2:
There is another way also that satisfy (based on this article) for foldl
:
foldl f a list = (foldr construct (\acc -> acc) list) a
where
construct x r = \acc -> r (f acc x)
Edit 1
Flipping the arguments of the function will not create a same foldr/foldl, meaning this examples does not satisfy the equality of foldr-foldl:
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
and foldl
in terms of foldr:
foldl' :: Foldable t => (b -> a -> b) -> b -> t a -> b
foldl' f b = foldr (flip f) b
and foldr
:
foldr' :: Foldable t => (a -> b -> b) -> b -> t a -> b
foldr' f b = foldl (flip f) b
The converse is not true, since foldr may work on infinite lists, which foldl variants never can do. However, for finite lists, foldr can also be written in terms of foldl although losing laziness in the process. (for more check here)
Ando also not satisfy this examples:
foldr (-) 2 [8,10] = 8 - (10 - 2) == 0
foldl (flip (-)) 2 [8,10] = (flip (-) (flip (-) 2 8) 10) == 4
来源:https://stackoverflow.com/questions/57260253/can-foldr-and-foldl-be-defined-in-terms-of-each-other