How reduceLeft works on sequence of Functions returning Future

This might be a naive question and I am sorry for that. I am studying Scala Futures and stumbled on below code:

object Main extends App {
    def await[T](f: Future[T]) = Await.result(f, 10.seconds)

    def f(n: Int): Future[Int] = Future {n + 1}
    def g(n: Int): Future[Int] = Future {n * 2}
    def h(n: Int): Future[Int] = Future {n - 1}

    def doAllInOrder[T](f: (T => Future[T])*): T => Future[T] = {
      f.reduceLeft((a,b) => x => a(x).flatMap(y => b(y)))
    }

    println(await(doAllInOrder(f, g, h)(10))) // 21
  }

I know how reduceLeft works when it is applied on Collections. But in the above example, as I understand, in the first pass of reduceLeft value x i.e. 10 is applied to Function a and the result is applied to Function b using flatMap which eventually return Future[Int] (say, I call it result). In the next pass of reduceLeft the result and Function h has to be used, but here is I am troubled.

The result is actually an already executed Future, but the reduceLeft next pass expects a Function which returns a Future[Int].Then how it is working?

Another thing I am not able to understand how each pass sending its result to next pass of reduceLeft, i.e. how x is getting it’s value in subsequent passes.

Though both of my confusions are interrelated and a good explanation may help clear my doubt.

Thanks in advance.

I would recommend you to execute the algorithm by hand on a paper or whiteboard.

You are right that reduceLeft expects the result to be of the same type as the elements over which we are reducing. Also, the doAllInOrder function has a return type of T => Future[T].

Where you are wrong is the assumption, that the result of the first pass is an executed future. Here is the same function, but with more indentation and type annotations:

def doAllInOrder[T](f: (T => Future[T])*): T => Future[T] = {
  f.reduceLeft(
    (a: (T => Future[T]) ,b: (T => Future[T])) =>
        (x: T) => a(x).flatMap(y => b(y))  //this whole line returned
  )
}

So the x does not have a value here (where should it come from? our doAllInOrder function doesn’t have a value of type T). Instead it is the parameter of a new lambda, which is the result of type T => Future[T]. It’s easy to overlook, because it is nested in another lambda (the one passed to reduceLeft).

So doAllInOrder combines the functions, the result of the reduceLeft is a function. Passing 10 happens here after that method has already finished, while all the flatMaps only happen, after the combined function receives that value.

It is basically the same as function composition without Future:

def doAllInOrder[T](f: (T => T)*): T => T = {
	f.reduceLeft((a,b) => x => b(a(x)))
	// or f.reduceLeft((a,b) => a andThen b)
}    
1 Like

The other thing to keep in mind is that you basically should never think of Future in terms of “already executed”. You know that a Future is already started, which is a very different thing.

The way you work with Futures is that you’re always saying, “When this Future is done, do this next thing” – that’s what map and flatMap do. So this reduceLeft is basically defining a chain of executions, where one finishing will produce a result, that allows the next step to begin.

1 Like