Lgd. Viktor Klang

Systems all the way down

Viktor Klang bio photo


This is the second of several posts describing the evolution of scala.concurrent.Future in Scala 2.12.x. For the previous post, click here.

Missing canonical combinators: zipWith

Being able to join two Futures together to produce a new Future which contains a Tuple of the results of both has been available for a while with the zip operation, and this looks something like this:

val future1: Future[String] = 
val future2: Future[Int] = 
val zippedFuture: Future[(String, Int)] = future1 zip future2

But what if we don’t want a Tuple? What if we want to combine the results of the two Futures to create something else?

This typically means having to combine zip and map:

/* The reason why we use a «PartialFunction literal» (yes, I made that up)
 * here is because otherwise the code has to use the arguably uglier _._1 and
 * _._2 syntax for `Tuple` value extraction.
 */
val combinedFuture: Future[String] =
    future1 zip future2 map { case (string, int) => s"$string & $int" }

Now, if you’re like me and you dislike allocations, tuples and lack of genericity, you’ll see that zip is a specialization of x.zipWith(y)(Tuple2.apply)! Well, perhaps it helps if we share the signature of said zipWith:

def zipWith[U, R](that: Future[U])(f: (T, U) => R)(implicit executor: ExecutionContext): Future[R]

This allows us to express the example above as:

val combinedFuture: Future[String] =
    future1.zipWith(future2)((string, int) => s"$string & $int")

Benefits:

  1. Less to type
  2. Less to read
  3. Fewer allocations
  4. Fewer asynchronous steps
  5. More general than zip

Click here for the next part in this blog series.

Cheers,