haskellfunctional-programminglazy-evaluationhigher-order-functionsfold

Why does `foldl'` have the implicit reversal?


Recently to better understand the advantages and disadvantages of lazy evaluation, I check for some details of Haskell. I did that to do exercise 5.9 in SDF (Software Design for Flexibility)

More generally, the Lisp community has avoided changing cons to be kons, as recommended by Friedman and Wise (see footnote 17 on page 254). What potentially serious problems are avoided by using cons rather than kons? Assume that we do not care about small constant factors in performance.

kons is just lazy cons.


I found foldl is interesting with one foldl' variant. I checked QA1 How is foldl lazy? with its answer, QA2 foldl parameter order problem and QA3 how to implement foldl with foldr. I understand those. However I am still confused about something said by haskell wiki

You should pick foldl' principally in two cases:

... you do not care about the implicit reversal (for example, because your combining function is commutative like (+), (*), or Set.union) ...

IMHO foldl' does something like "(...((z f x1) f x2)... xn)" which just consumes the list from left to right, then why does the doc say foldl' has "implicit reversal"?

p.s. After understanding what "implicit reversal" means hinted by is7s and Louis Wasserman, here I give more related infos: 0. Here non-commutative function can also have no "implicit reversal", e.g. concat in "foldl parameter order problem" link.


Later after Daniel Wagner's and is7s's answers are given, I found where that ambiguous paragrph is added. Then I checked the author wiki contribution and found this talk

Moreover foldl' conceptually reverses the order of the list is a misleading formulation:

divisel :: [Double] -> Double
divisel = foldl (/) 1

diviser :: [Double] -> Double
diviser = foldr (/) 1

divisel [1,2] = diviser [1,2] = 0.5

Both divide 1 by 2.

I think it would be better to say foldl/foldr associates to the left/right.

--Klt (talk) 14:28, 19 July 2024 (UTC)

As the original author of the erroneous language, I think you are right.

CarlEdman (talk) 15:07, 19 July 2024 (UTC)

What Klt says at the end is same as the shared part of those 2 answers

a reversal of association

evaluate as follows: ...

But IMHO the (/) example is not good although works sometimes as that talk shows if identity holds, i.e. (1 / 1) == 1 in foldr example and (2 / 1) == 2 in foldl example there. The reason is due to (/) is not associative. That is said by "foldl parameter order problem" link. So (foldl (/) 1 [2,2]) /= foldr (/) 1 [2,2] since (1 / 2) /= 2.

I can provide a commutative example where foldl and foldr return different result.

i.e. foldr (++) "c" ["a", "b"] example of Daniel Wagner's which doesn't use one correct identity value (see QA2).

For one more detailed explanation of "implicit reversal", please see is7s's answer and this chat comment for the related wiki contents.


Solution

  • I think the wiki has been imprecise. Instead of commutative, it should say associative; the "reversal" is a reversal of association.

    foldr1 (+) [x0, x1, x2, x3, ...] = x0 + (x1 + (x2 + (x3 + ...)))
    foldl1 (+) [x0, x1, x2, x3, ...] = (((x0 + x1) + x2) + x3) + ...
    

    I've used foldr1 and foldl1 to avoid quibbles about the base case, which also moves in a meaningful way. In most cases, the base case is a neutral element, so this doesn't matter, but I suppose technically it could be another candidate for what is meant by "implicit reversal" when the base case is not neutral. For example:

    > foldr (++) "c" ["a", "b"]
    "abc"
    > foldl (++) "c" ["a", "b"]
    "cab"
    

    The base case appears at the "reversed" end of the calculation. I really do not think this is what the wiki was talking about, though.