Why is Future[Set[Unit]] not accepted as Future[Unit]?

Viewed 352

A common gotcha when working with futures is that when you expect Future[Unit], even Future[Future[Unit]] will be accepted (see e.g. Why Shouldn’t You Use Future[Unit] as a Return Type in a Scala Program).

I was surprised recently Future.sequence(setOfFutures) is not accepted in such situation:

import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global

val set = Set(Future(()))

def fuu: Future[Unit] = {
  Future.sequence(set)
}

With Scala 2.12.13 I get error:

type mismatch;

found : scala.concurrent.Future[scala.collection.immutable.Set[Unit]]

With Scala 2.13 I get:

Cannot construct a collection of type Unit with elements of type Unit based on a collection of type scala.collection.immutable.Set[scala.concurrent.Future[Unit]].

When I change the body of the function to:

  val s = Future.sequence(set)
  s

I get set the same error as before.

Why is Future[Future[Unit]] accepted as a Future[Unit] and Future[Set[Unit]] or Future[List[Unit]] is not?

1 Answers

Consider the signature of Future.sequence in Scala 2.13

def sequence[A, CC[X] <: IterableOnce[X], To](in: CC[Future[A]])(
  implicit 
  bf: BuildFrom[CC[Future[A]], A, To], 
  executor: ExecutionContext
): Future[To]

so given

val set = Set(Future(()))
def fuu: Future[Unit] = Future.sequence(set)

then inference will assign type paramers of sequence like so

To = Unit
A = Unit
CC = Set

For example consider fuu's return type Future[Unit] = Future[To]. Hence we have

def fuu: Future[Unit] = Future.sequence[Unit, Set, Unit](set)

so compiler needs to implicitly assign bf parameter

scala> implicitly[BuildFrom[Set[Future[Unit]], Unit, Unit]]
                 ^
       error: Cannot construct a collection of type Unit with elements of type Unit based on a collection of type Set[scala.concurrent.Future[Unit]].

Now consider Scala 2.12 signature of Future.sequence

def sequence[A, M[X] <: TraversableOnce[X]](in: M[Future[A]])(
  implicit 
  cbf: CanBuildFrom[M[Future[A]],A,M[A]],
  executor: ExecutionContext
): Future[M[A]]

so given

val set = Set(Future(()))
def fuu: Future[Unit] = Future.sequence(set)

inference becomes

A = Unit
M = Set

so we have

def fuu: Future[Unit] = Future.sequence[Unit, Set](set)

where compiler can successfully implicitly assign cbf parameter

scala>  implicitly[CanBuildFrom[Set[Future[Unit]],Unit,Set[Unit]]]
res4: scala.collection.generic.CanBuildFrom[Set[scala.concurrent.Future[Unit]],Unit,Set[Unit]] = scala.collection.generic.GenSetFactory$$anon$1@1bff70a6

hence we effectively have in 2.12 the following situation

scala> def fuu: Future[Unit] = Future.sequence(set) : Future[Set[Unit]]
<console>:25: error: type mismatch;
 found   : scala.concurrent.Future[Set[Unit]]
 required: scala.concurrent.Future[Unit]
def fuu: Future[Unit] = Future.sequence(set) : Future[Set[Unit]]

This should explain the difference between the two compiler error messages between the two Scala versions is not related to value discarding but to how inference assigned the corresponding types.

Related