Reputation: 23
I'm trying to wrap my head around Scala and I'm wondering what the following does:
val fFuture: Future[Int] = Future { println("f called"); 3 }
val gFuture: Future[Int] = Future { println("g called"); 4 }
for {
f <- fFuture
g <- gFuture
} yield f
The futures are executed inside of the for comprehension, correct? So what does f
do in the yield statement? Does that just mean it's available? Is that considered the return value
if this were inside of a function?
Upvotes: 2
Views: 5210
Reputation: 1239
Scala Futures are evaluated eagerly, which means that their value is immediately calculated in a separate thread.
When you run operations that depend on result of the future, the current thread is going to block waiting until it becomes evaluated. If you transform Futures using combinator methods such as map or flatMap, you are getting another Future that represents the end result (the code running these operations doesn't need to block).
So in your example the for comprehension is desugared by the compiler to the following expression:
fFuture.flatMap(f => gFuture.map(g => f + g))
Which itself is a Future[Int] calculated in another thread.
Upvotes: 1
Reputation: 22374
The futures are starting to execute here:
val fFuture: Future[Int] = Future { println("f called"); 3 }
val gFuture: Future[Int] = Future { println("g called"); 4 }
So both executions are starting in parallel. However if you accidentally put Future{...}
inside for-comprehension - they gonna be executed sequentially.
For-comprehension basically subscribes and merges two results into one Future. However, in your case it seems like second future's result was ignored, which doesn't makes sense. The code that makes sense:
for {
f <- fFuture
g <- gFuture
} yield f + g
This code returns Future[Int]
(same as code in your example). If you extract value from this Future - you get 3 + 4 = 7
. However it's still not a best approach as your computations are independent and probability of developer's mistake (stated above) that makes them sequential is still high, so recommended approach for independent computations is:
(fFuture zip gFuture) map {
case (f, g) => f + g
}
This code is referentially transparent in meaning that even if you replace fFuture
with Future{...}
- it still behaves same (in case of Future
-they're going to be executed in prallel, but it might be different for other concurrency primitives)
Where would for-comprehension
actually make sense? Here:
for {
f <- Future{... 9}
g <- if (f > 0) Future{...} else Future{...}
} yield g
As g
depends on f
here - there is no way to run those in parallel, so for
provides a non-blocking way of composing several Future
s
Upvotes: 5
Reputation: 143
Here in your scenario, you dont have to use for comprehension, you can simply use OnComplete.
ffuture.OnComplete {
Case Success(result) => println(s"$result')
Case Failure(ex) => ex.printStackTrace
}
For Comprehension is needed when you have future(s) dependent on other future(s) like:
var result = for {
f <- fFuture
g <- f
} yield g
Here g future will be resolved after f completes, and yield will return whatever is the result you are returning. In this case it will be a Future[Int]. With yield you can do things like.
yield g+f
To read the result you can simply use
result.onSuccess or onComplete
For Futures, I found this article as the best one: http://alvinalexander.com/scala/concurrency-with-scala-futures-tutorials-examples
I will say yes, this is similar to return value in a function. But this is for comprehension syntax, you cant use return statement with it. For comprehension is a better way to write map.
Upvotes: -2