In scala2 or 3 this function compiles:
def returnsFutureOfFutureButCompiles(): Future[Unit] = {
for {
_ <- Future.unit
} yield Future.sequence(Seq(Future.unit)) // Returns Future[Future[Seq[Unit]]]
}
but this one does not:
def returnsFutureOfFutureButDoesNotCompile(): Future[Unit] = {
val a = for {
_ <- Future.unit
} yield Future.sequence(Seq(Future.unit))
a // Error: Required: Future[Unit], Found: Future[Future[Seq[Unit]]]
}
I am aware of for-comprehension syntaxic sugar, differences between map and flatMap and how monads work.
The compiler should warns for both functions
I don't understand these different behaviours since the functions are almost doing the same job.
Maybe it's related to type inference limitations but then, why?
Also, if I declare these functions:
def a(): Future[Int] = {
for {
_ <- Future.successful(1)
} yield Future.successful(2) // Error: Required: Int, Found: Future[Int]
}
def b(): Future[Int] = {
val value = for {
_ <- Future.successful(1)
} yield Future.successful(2)
value // Error: Required: Future[Int], Found: Future[Future[Int]]
}
then the compiler warns correctly, as expected, about the two functions.
So, I think the problem is maybe related to Future.sequence
?
It's related to the fact that you used specifically Unit
.
As you know:
val a = for {
_ <- Future.unit
} yield Future.sequence(Seq(Future.unit))
Compiles to:
Future.unit.map { _ =>
Future.sequence(Seq(Future.unit))
}
It should infer to Future[Future[Seq[Unit]]]
. But when you told it that the result should be Future[Unit]
, the compiler started working backward, from the type it should get, to the type it gets.
Therefore it inferred that you wanted:
Future.unit.map[Unit] { _ =>
Future.sequence(Seq(Future.unit))
}
Which is correct because when the type returned is Unit
it can treat the code as if you wrote:
Future.unit.map[Unit] { _ =>
Future.sequence(Seq(Future.unit))
()
}
However "dropping non-Unit value" can happen only if the whole inference happens at once. When you do:
val a = for {
_ <- Future.unit
} yield Future.sequence(Seq(Future.unit))
a
You have two statements/expressions, so a single inference cannot work backward. Now not knowing that val a
should be constrained to the type Future[Unit]
it infer it to Future[Future[Seq[Unit]]]
. The next, separate step, tries to match it against Future[Unit]
and fails, because it is too late now to change the type of a previous statement and modify the body of map to drop the returned value.
In Scala 2 you could use -Wvalue-discard
or -Ywarn-value-discard
to force you to manually insert that ()
if you want to discard non-Unit
value in a body that should return Unit
. Scala 3 is still waiting to obtain such a warning flag.