In my previous post I discussed about composing Futures. I also discussed that Futures are monadic and they can be composed using map or flatmap. Since, for comprehension is just a sugar on top we can use it as well. Lets understand their usage in a bit more detail than my previous post.
Using the same example of direct use of Futures and using a timeTakingIdentity function. Code snippet below has two future creation and a function definition.
def timeTakingIdentityFunction(number: Int): Int = {
// sleeping three seconds
Thread.sleep(3000)
number
}
val future1 = Future(timeTakingIdentityFunction(1))
val future2 = Future(timeTakingIdentityFunction(2))
Now we need to run them in parallel. The way to do this is to construct a single Future out of future1 and future2 and collect the result from them.
Since Future is a Monad we have useful methods like map and flatMap to compose a new Future. The resulting Future can then be used for assembling result. Lets use just a map method and see what happens.
Using future1 first we can call map method on it. We can then use nested map method on future2. On the inner map method we can then sum result1 and result2. We expect this Future to help us getting sum of two results from timeTakingIdentityFunction.
val future1 = Future(timeTakingIdentityFunction(1))
val future2 = Future(timeTakingIdentityFunction(2))
// This will give us Future[Future[Int]]. This is not we want
val finalFutureFuture = future1 map {
result1 =>
future2 map {
result2 => result1 + result2
}
}
def timeTakingIdentityFunction(number: Int): Int = {
Thread.sleep(3000)
number
}
Not surprisingly, what we get is a Future[Future[Int]]. Not entirely useful conciseness wise. We need a final future that is a Future of Int. This is why we use a flatMap. We can make it work using a flatMap on future1 and then a map on future2. Here is the code.
// we use flatMap instead for composition
val finalFuture = future1 flatMap {
result1 =>
future2 map {
result2 => result1 + result2
}
}
If we want, we can use this to get sum of two results from timeTakingIdentityFunction. Here is the complete code listing of an Application.
import akka.actor._
import akka.dispatch._
object FutureApp extends App {
implicit val system = ActorSystem("future")
val startTime = System.currentTimeMillis
val future1 = Future(timeTakingIdentityFunction(1))
val future2 = Future(timeTakingIdentityFunction(2))
val finalFuture = future1 flatMap {
result1 =>
future2 map {
result2 => result1 + result2
}
}
finalFuture onSuccess {
case sum => println("Sum of results from timeTakingIdentityFunction is " + sum + " calculated in " + (System.currentTimeMillis - startTime) / 1000 + " seconds")
}
def timeTakingIdentityFunction(number: Int): Int = {
Thread.sleep(3000)
number
}
}
We made two calls to timeTakingIdentityFunction and got result in three seconds!!
But this is not a concise way to do things. Imagine there is a third future then our code will start looking a bit messy.
val anotherFinalFuture = future1 flatMap {
result1 =>
future2 flatMap {
result2 =>
future3 map {
result3 => result1 + result2 + result3
}
}
}
For this we use for expressions. It is a sugar on top and here is a concise code we can use.
val finalFutureUsingForExpression = for {
result1 <- future1
result2 <- future2
result3 <- future3
} yield result1 + result2 + result3
You can compare how better it is!!
Now there is a common pitfall we can get into when composing futures. I did it too. Rule of the thumb is to create Futures before you get into composing them. For example the code below will run in nine seconds.
val finalFutureUsingFaultyForExpression = for {
result1 <- Future(timeTakingIdentityFunction(1))
result2 <- Future(timeTakingIdentityFunction(2))
result3 <- Future(timeTakingIdentityFunction(3))
} yield result1 + result2 + result3
It is so because creation of future2 and future3 happens after first future completes. Not so hard figuring it out but a common error nonetheless
We did composing futures using map and flatMap. We also used for expressions and noticed they are so better. And we know now the rule of the thumb is to create them before we go about composing them.






Pingback: Knolx Session: Using Akka Futures | Knoldus