Published 2017-05-25.
Time to read: 2 minutes.
Ever since Scala’s Future
s 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 Future
s within the
Intermediate Scala course.
As Viktor Klang points out in his blog,
the following code does not run 3 Future
s 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 Future
s 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 accidentally 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 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)
Because the compiler ‘knows’ that the programmer’s intention was to run the Future
s 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 Future
s are part of the standard runtime.
If and when Future
s are hived out of the standard runtime, the macro should be packaged with Future
s.
PS: @flaviusbraz tweeted on May 25, 2017: “Easily doable with a macro transformation. In fact, I’ve implemented this transformation at Twitter, but it’s not open source.”