Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.
like image 616
Roman Avatar asked Dec 01 '13 00:12

Roman


2 Answers

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.

like image 74
vptheron Avatar answered Sep 22 '22 16:09

vptheron


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

like image 33
som-snytt Avatar answered Sep 19 '22 16:09

som-snytt