Search code examples
scalacompiler-errorsfuturemonads

Why does the compiler infer incorrectly return type for Future.sequence declaration in function?


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?


Solution

  • 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.