5

In a online course it was stated that foldLeft and foldRight are equivalent for operators that are associative and commutative.

One of the students is adamant that such operators only need to be associative. So this property should be true for operations like function composition and matrix multiplication.

As far as I can tell an associative operation that isn't commutative will not produce equivalent results for foldLeft and foldRight unless z is neutral and the operation is accumulated in such a way that the order of the operands remains untouched. IMO the operation has to be commutative in the general case.

list.foldLeft(z)(operation) == list.foldRight(z)(operation)

So, for foldLeft and foldRight to be equivalent should operation be simultaneously associative and commutative or is it enough for operation to be associative?

Anthony Accioly
  • 21,918
  • 9
  • 70
  • 118

4 Answers4

8

String concatenation ("abc" + "xyz") is associative but not commutative and foldLeft/foldRight will place the initial/zero element at opposite ends of the resulting string. If that zero element is not the empty string then the results are different.

jwvh
  • 50,871
  • 7
  • 38
  • 64
5

The function must be both commutative and associative.

If our function is f, and our elements are x1 to x4, then:

foldLeft is f(f(f(x1, x2), x3), x4)

foldRight is f(x1, f(x2, f(x3, x4)))

Let's use the average function, which is commutative but not associative ((a + b) / 2 == (b + a) / 2):

scala> def avg(a: Double, b: Double): Double = (a + b) / 2
avg: (a: Double, b: Double)Double

scala> (0 until 10).map(_.toDouble).foldLeft(0d)(avg)
res4: Double = 8.001953125

scala> (0 until 10).map(_.toDouble).foldRight(0d)(avg)
res5: Double = 0.9892578125

EDIT: I missed the boat on only associative vs only commutative. See @jwvy's example of string concatenation for an associative but not commutative function.

Tim
  • 3,675
  • 12
  • 25
4

foldLeft is (...(z op x1)... op xn) foldRight is x1 op (x2 op (... xn op z)...) So the op needs to be commutative and associative for the two to be equivalent in the general case

radumanolescu
  • 4,059
  • 2
  • 31
  • 44
1

There are at least three relevant cases with separate answers:

  1. In the general case where op: (B, A) -> B or op: (A, B) -> B, as in the signatures of foldLeft and foldRight, neither associativity nor commutativity are defined.

  2. If B >: A and z is a two-sided identity of op: (B, B) -> B and op is associative then for all L of type List[A], L.foldLeft(z)(op) returns the same result as L.foldRight(z)(op).

    This is closely related to the fact that if B >: A and op: (B, B) -> B then, if op is associative, for all L of type List[A] L.reduceLeft(op) returns the same result as L.reduceRight(op).

  3. If B >: A, and op: (B, B) -> B is both associative and commutative then for all L of type List[A] and z of type B, L.foldLeft(z)(op) returns the same result as L.foldRight(z)(op).

Community
  • 1
  • 1
Elroch
  • 132
  • 1
  • 7