While going through Functional Programming in Scala, I came across this question:
Can you right foldLeft in terms of foldRight? How about the other way
I just did this exercise, and would like to share how I arrived at an answer (basically the same as what's in the question, only the letters are different), in the hope that it may be useful to someone.
As background, let's start with what foldLeft
and foldRight
do. For example, the result of foldLeft on the list [1, 2, 3] with the operation *
and starting value z
is the value
((z * 1) * 2) * 3
We can think of foldLeft as consuming values of the list incrementally, left to right. In other words, we initially start with the value z
(which is what the result would be if the list were empty), then we reveal to foldLeft
that our list starts with 1 and the value becomes z * 1
, then foldLeft
sees our list next has 2
and the value becomes (z * 1) * 2
, and finally, after acting on 3, it becomes the value ((z * 1) * 2) * 3
.
1 2 3
Initially: z
After consuming 1: (z * 1)
After consuming 2: ((z * 1) * 2
After consuming 3: (((z * 1) * 2) * 3
This final value is the value we want to achieve, except (as the exercise asks us) using foldRight
instead. Now note that, just as foldLeft
consumes values of the list left to right, foldRight
consumes values of the list right to left. So on the list [1, 2, 3],
(((z * 1) * 2) * 3
In other words: using foldRight
, we first arrive at what the result would be if the list were empty, then the result if the list contained only [3], then the result if the list were [2, 3], and finally the result for the list being [1, 2, 3].
That is, these are the values we would like to arrive at, using foldRight
:
1 2 3
Initially: z
After consuming 3: z * 3
After consuming 2: (z * 2) * 3
After consuming 1: ((z * 1) * 2) * 3
So we need to go from z
to (z * 3)
to (z * 2) * 3
to ((z * 1) * 2) * 3
.
As values, we cannot do this: there's no natural way to go from the value (z * 3)
to the value (z * 2) * 3
, for an arbitrary operation *
. (There is for multiplication as it's commutative and associative, but we're only using *
to stand for an arbitrary operation.)
But as functions we may be able to do this! We need to have a function with a "placeholder" or "hole": something that will take z
and put it in the proper place.
z => (z * 3)
. Or rather, as a function must take arbitrary values and we've been using z
for a specific value, let's write this as t => (t * 3)
. (This function applied on input z
gives the value (z * 3)
.)t => (t * 2) * 3
maybe?Can we go from the first placeholder function to the next? Let
f1(t) = t * 3
and f2(t) = (t * 2) * 3
What is f2
in terms of f1
?
f2(t) = f1(t * 2)
Yes we can! So the function we want takes 2
and f1
and gives f2
. Let's call this g
. We have g(2, f1) = f2
where f2(t) = f1(t * 2)
or in other words
g(2, f1) =
t => f1(t * 2)
Let's see if this would work if we carried it forward: the next step would be g(1, f2) = (t => f2(t * 1))
and the RHS is same as t => f1((t * 1) * 2))
or t => (((t * 1) * 2) * 3)
.
Looks like it works! And finally we apply z
to this result.
What should the initial step be? We apply g
on 3
and f0
to get f1
, where f1(t) = t * 3
as defined above but also f1(t) = f0(t * 3)
from the definition of g
. So looks like we need f0
to be the identity function.
Let's start afresh.
Our foldLeft(List(1, 2, 3), z)(*) is ((z * 1) * 2) * 3
Types here: List(1, 2, 3) is type List[A]
z is of type B
* is of type (B, A) -> B
Result is of type B
We want to express that in terms of foldRight
As above:
f0 = identity. f0(t) = t.
f1 = g(3, f0). So f1(t) = f0(t * 3) = t * 3
f2 = g(2, f1). So f2(t) = f1(t * 2) = (t * 2) * 3
f3 = g(1, f2). So f3(t) = f2(t * 1) = ((t * 1) * 2) * 3
And finally we apply f3 on z and get the expression we want. Everything works out. So
f3 = g(1, g(2, g(3, f0)))
which means f3 = foldRight(xs, f0)(g)
Let's define g
, this time instead of x * y
using an arbitrary function s(x, y)
:
g
is of type A
g
is of the type of these f
's, which is B => B
g
is (A, (B=>B)) => (B=>B)
So g
is:
def g(a: A, f: B=>B): B=>B =
(t: B) => f(s(t, a))
Putting all this together
def foldLeft[A, B](xs: List[A], z: B)(s: (B, A) => B): B = {
val f0 = (b: B) => b
def g(a: A, f: B=>B): B=>B =
t => f(s(t, a))
foldRight(xs, f0)(g)(z)
}
At this level of working through the book, I actually prefer this form as it's more explicit and easier to understand. But to get closer to the form of the solution, we can inline the definitions of f0
and g
(we no longer need to declare the type of g
as it's input to foldRight
and the compiler infers it), giving:
def foldLeft[A, B](xs: List[A], z: B)(s: (B, A) => B): B =
foldRight(xs, (b: B) => b)((a, f) => t => f(s(t, a)))(z)
which is exactly what is in the question, just with different symbols. Similarly for foldRight in terms of foldLeft.