Why does this list-of-futures to future-of-list transformation compile and work?

2.9k views Asked by At

Disclaimer: the code snippet below relates to one of ongoing Coursera courses. Let's consider it's posted just for a learning purpose and should not be used for submitting as a solution for one's homework assignment.

As the comment below states, we need to transform a list of Futures to a single Future of a list. More than that, the resulting Future should fail if at least one of input futures failed.

I met the following implementation and I don't understand it completely.

/** Given a list of futures `fs`, returns the future holding the list of values of all the futures from `fs`.
 *  The returned future is completed only once all of the futures in `fs` have been completed.
 *  The values in the list are in the same order as corresponding futures `fs`.
 *  If any of the futures `fs` fails, the resulting future also fails.
 */
def all[T](fs: List[Future[T]]): Future[List[T]] = 
             fs.foldRight(Future(Nil:List[T]))((f, fs2) =>
  for {
    x <- f
    xs <- fs2
  } yield (x::xs))

In particular, I don't understand the next things in it:

  1. Where does Future[T] -> T transformation happen? It looks like xs <- fs2 is the only place we touch initial Futures, and each of xs type should be Future[T] (but somehow it becomes just T).
  2. How are failures handled? It looks like the resulting Future object does fail when one of the input Futures fails.
2

There are 2 answers

0
vptheron On BEST ANSWER

1) Say f is a Future[T], then writing

for {
 t <- f
}  yield List(t)

will store the result of the Future f in t - therefor t is of type T. The yield turns it into a List[T], and the type of the whole for-comprehension ends up being Future[List[T]]. So the for-comprehension is where you extract your Ts from your Futures, do something with them, and put them back in a Future (OK, I'm simplifying a little bit here).

It's equivalent to

f.map(t => List(t))

2) If your Future f contains a Failure, then the for-comprehension will just return this failed Future instead of executing the yield.

As a general note, for-comprehension in Scala is just sugar that can be rewritten with map, flatMap, filter, foreach.

0
som-snytt On

I'm an English-speaking right-hander, so normally I foldLeft, but each step of the fold looks like:

Fn flatMap ((x: T) => Fs map (xs => x :: xs))

Your value is x.

The function is applied on success, which explains why a failure stops you cold:

scala> timed(Await.ready(all(List(Future{Thread sleep 5*1000; 1},Future(2),Future{Thread sleep 10*1000; 3})), Duration.Inf))
res0: (Long, scala.concurrent.Awaitable[List[Int]]) = (10002419021,scala.concurrent.impl.Promise$DefaultPromise@2a8025a0)

scala> timed(Await.ready(all(List(Future{Thread sleep 5*1000; 1},Future(???),Future{Thread sleep 10*1000; 3})), Duration.Inf))
res1: (Long, scala.concurrent.Awaitable[List[Int]]) = (5000880298,scala.concurrent.impl.Promise$DefaultPromise@3750d517)

Notice that the failing version short-circuits.

See the ScalaDoc for flatMap for both bits of information.

Edit: I was speaking cautiously because it is Coursera work, but more plainly, this requirement is not met: "The returned future is completed only once all of the futures in fs have been completed."