Possible wrong expectation in foldLeft function test of List Ops exercise

There is a test of the foldLeft function:

  @Test("direction dependent function applied to non-empty list", .enabled(if: RUNALL))
  func testDirectionDependentFunctionAppliedToNonEmptyList6() {
    let value = ListOps.foldLeft([1, 2, 3, 4], accumulated: 24.0) { $0 / $1 }
    let expected = 64.0
    #expect(value == expected)
  } 

In my opinion left fold looks like:

(((24 / 1) / 2) / 3) / 4

and gives 1.0 as result.

it gives 64 if one flips operands (haskell flip /)

The function being used is (meant to be) accumulator = element / accumulator for this test. Are you using the args in the right order? This test is from the problem specs repo.

Yeah, got it.

I thought foldLeft used parentheses to group to the left, and naturally iterating elements from left to right, where on each iteration the accumulator is always the left operand and the collection element is right one.

For example foldl from Haskell has following signature:

foldl :: (b -> a -> b) -> b -> t a -> b

where b is the type of the accumulator and returning value, and a is a type of element in the collection t a and can be interpreted as:

foldl :: (acc -> element -> acc) -> initial_acc -> [element] -> result

And we can easily get left folding:

24 / 1 / 2 / 3 / 4 ~> (becomes)

(((24 / 1) / 2) / 3) / 4 ~>
((24 / 2) / 3) / 4 ~>
(12 / 3) / 4 ~>
4 / 4 ~>
1

If accumulator and element are flipped, we won’t get classic left folding:

4 / (3 / (2 / (1 / 24))) ~> 64

which doesn’t look like left folding: parentheses used to group to the right (right folding), but elements are iterated from left to right (left folding).

Eg Haskell’s foldr:

foldr :: (a -> b -> b) -> b -> t a -> b
foldr :: (element -> acc -> acc) -> initial_acc -> [element] -> result

1 / 2 / 3 / 4 / 24 ~>
1 / ( 2 / (3 / (4 / 24)))

Good that @yura could reveal some of their assumptions:

  • It is not a requirement because it is “natural” and Haskell does it like this
  • Guessing a functions parameter meaning from a known usage by position is not safe

Anyways, I think the “illogic” was introduced ~6 months ago when an update to the exercise did 2 things at once - replace manual code with a template and update the tests to the latest problem specifications. Problem specifications intentionally flipped and renamed the operands from (x,y) => x/y to (acc,el) => el/acc. But the change in the Swift track did not revert the operands inside the passed in function to { $1 / $0 } to meet that. So now the students have to pass in reverted parameters to acheive the expected result: (el, acc) => el/acc.

@Meatball @Sencudra What would be the impact of fixing this?

We can definitely fix that. I will create the PR in a few days, as I’d like to polish some other aspects of this exercise as well. However, I think it would be appreciated if @yura made the changes himself, if he intends to do so.

What would be the impact of fixing this?

Students would need to change the order in which they pass arguments to the closure.

Done Correct expected value for `foldLeft` function by yura · Pull Request #894 · exercism/swift · GitHub. Just need to merge Correct expected value for `foldLeft` function by yura · Pull Request #894 · exercism/swift · GitHub commit

1 Like

So I want to iterate what Isaac said, if there is something wrong it is the order of the operators not the output.

Also please keep only relevant changes in the pr.

1 Like