How to convert Map[A,Future[B]] to Future[Map[A,B]]?

See if this works for you:

val map = Map("a" -> future{1}, "b" -> future{2}, "c" -> future{3})    
val fut = Future.sequence(map.map(entry => entry._2.map(i => (entry._1, i)))).map(_.toMap)

The idea is to map the map to an Iterable for a Tuple of the key of the map and the result of the future tied to that key. From there you can sequence that Iterable and then once you have the aggregate Future, map it and convert that Iterable of Tuples to a map via toMap.

Now, an alternative to this approach is to try and do something similar to what the sequence function is doing, with a couple of tweaks. You could write a sequenceMap function like so:

def sequenceMap[A, B](in: Map[B, Future[A]])(implicit executor: ExecutionContext): Future[Map[B, A]] = {
  val mb = new MapBuilder[B,A, Map[B,A]](Map())
  in.foldLeft(Promise.successful(mb).future) {
    (fr, fa) => for (r <- fr; a <- fa._2.asInstanceOf[Future[A]]) yield (r += ((fa._1, a)))
  } map (_.result)
}

And then use it in an example like this:

val map = Map("a" -> future{1}, "b" -> future{2}, "c" -> future{3})    
val fut = sequenceMap(map)
fut onComplete{
  case Success(m) => println(m)
  case Failure(ex) => ex.printStackTrace()
}

This might be slightly more efficient than the first example as it creates less intermediate collections and has less hits to the ExecutionContext.


I think the most succinct we can be with core Scala 2.12.x is

val futureMap = Map("a" -> future{1}, "b" -> future{2}, "c" -> future{3}) 

Future.traverse(futureMap.toList) { case (k, fv) => fv.map(k -> _) } map(_.toMap)

Update: You can actually get the nice .sequence syntax in Scalaz 7 without too much fuss:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{ Future, future }

import scalaz._, Scalaz.{ ToTraverseOps => _, _ }
import scalaz.contrib.std._

val m = Map("a" -> future(1), "b" -> future(2), "c" -> future(3))

And then:

scala> m.sequence.onSuccess { case result => println(result) }
Map(a -> 1, b -> 2, c -> 3)

In principle it shouldn't be necessary to hide ToTraverseOps like this, but for now it does the trick. See the rest of my answer below for more details about the Traverse type class, dependencies, etc.


As copumpkin notes in a comment above, Scalaz contains a Traverse type class with an instance for Map[A, _] that is one of the puzzle pieces here. The other piece is the Applicative instance for Future, which isn't in Scalaz 7 (which is still cross-built against pre-Future 2.9), but is in scalaz-contrib.

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scalaz._, Scalaz._
import scalaz.contrib.std._

def sequence[A, B](m: Map[A, Future[B]]): Future[Map[A, B]] = {
   type M[X] = Map[A, X]
   (m: M[Future[B]]).sequence
}

Or:

def sequence[A, B](m: Map[A, Future[B]]): Future[Map[A, B]] =
  Traverse[({ type L[X] = Map[A, X] })#L] sequence m

Or:

def sequence[A, B](m: Map[A, Future[B]]): Future[Map[A, B]] =
  TraverseOpsUnapply(m).sequence

In a perfect world you'd be able to write m.sequence, but the TraverseOps machinery that should make this syntax possible isn't currently able to tell how to go from a particular Map instance to the appropriate Traverse instance.

Tags:

Scala

Future

Akka