Better Syntactic Sugar for Scala Futures

2017-05-25 / All Blog posts

Ever since Scala's Futures were initially provided as part of Akka 2.0, programmers have been confused by the non-intuitive syntax. That is why ScalaCourses.com dedicates an entire lecture to For-comprehensions With Futures, as part of an 8 lecture series on Scala Futures within the Intermediate Scala course.

As Viktor Klang points out in his blog, the following code does not run 3 Futures in parallel:

  def doSomething(someParameter: SomeType)
                 (implicit ec: ExecutionContext): Future[Something] =
    for {
      v1 <- Future(someCalculation())
      v2 <- Future(someOtherCalculation())
      v3 <- Future(someDifferentCalculation())
    } yield doSomethingWith(v1, v2, v3)

The compiler has no way of ascertaining the programmer's intent – perhaps it is desirable for some reason to run the 3 Futures one after the other.

Viktor suggests this syntax to make futures run in parallel:

  def doSomething(someParameter: SomeType)
                 (implicit ec: ExecutionContext): Future[Something] =
    for {
      f1 = Future(someCalculation())
      f2 = Future(someOtherCalculation())
      f3 = Future(someDifferentCalculation())
      v1 <- f1
      v2 <- f2
      v3 <- f3
    } yield doSomethingWith(v1, v2, v3)

While Viktor's solution works, it is verbose. Worse, it silently fails to run the futures in parallel if the programmer accidently writes even one of the expressions out of order:

  def doSomething(someParameter: SomeType)
                 (implicit ec: ExecutionContext): Future[Something] =
    for {
      f1 = Future(someCalculation())
      v1 <- f1
      f2 = Future(someOtherCalculation())
      v2 <- f2
      f3 = Future(someDifferentCalculation())
      v3 <- f3
    } yield doSomethingWith(v1, v2, v3)

Again, the compiler has no way of ascertaining the programmer's intent, so it should not generate an error or warning message.

We Need A Macro

A new right-associative operator, implemented as a Scala macro, would make the programmer's intent clear. Let's call this operator <=: (parallel generator). The above code could be rewritten using the parallel generation operator like this:

  def doSomething(someParameter: SomeType)
                 (implicit ec: ExecutionContext): Future[Something] =
    for {
      v1 <=: Future(someCalculation())
      v2 <=: Future(someOtherCalculation())
      v3 <=: Future(someDifferentCalculation())
    } yield doSomethingWith(v1, v2, v3)

There is no longer any doubt that the programmer intended for all 3 futures to run in parallel.

The macro would examine all of the for-expression's generators and expand consecutive expressions that use the <=: operator to a series of variable declarations using the = operator followed by a series of assignments using the <- operator, exactly as we saw earlier:

 def doSomething(someParameter: SomeType)
                (implicit ec: ExecutionContext): Future[Something] =
   for {
     f1 = Future(someCalculation())
     f2 = Future(someOtherCalculation())
     f3 = Future(someDifferentCalculation())
     v1 <- f1
     v2 <- f2
     v3 <- f3
   } yield doSomethingWith(v1, v2, v3)

What's more, because the compiler 'knows' that the programmer's intention was to run the Futures in parallel, this sort of error could cause an error or warning message to be generated:

 def doSomething(someParameter: SomeType)
                (implicit ec: ExecutionContext): Future[Something] =
   for {
     v1 <=: Future(someCalculation())
     x <- List(1, 2, 3)
     v2 <=: Future(someOtherCalculation())
     y <- List("a", "b")
     v3 <=: Future(someDifferentCalculation())
   } yield doSomethingWith(v1, v2, v3)
 

... or the macro might reorder the generators and issue a warning that it did so.

Include the Macro in Scala 2.12.x

This macro should become part of the Scala language so long as Futures are part of the standard runtime. If and when Futures are hived out of the standard runtime, the macro should be packaged with Futures.


PS: @flaviowbrasil tweeted: "Easily doable with a macro transformation. In fact, I've implemented this transformation at Twitter, but it's not open source."


comments powered by Disqus