Question

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.
Was it helpful?

Solution

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.

OTHER TIPS

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."

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top