Overloading is evil, but

This is a followup to my Debugging transparent inline functions question as it turns out my problem has more to do with overloading than transparent inline.

This code works, and I don’t understand why:

def wf[A](code: ExecutionContext => A): Unit =
   code(ExecutionContext.global)

def wf[A](code: ExecutionContext => Future[A]): A =
   Await.result(code(ExecutionContext.global), Duration.Inf)

wf(e => Future("X")(using e))
wf(_ => "Y")

Don’t both wf functions have the same signature, Function1, after type erasure?

Then, this doesn’t work:

def wc[A](code: ExecutionContext ?=> A): Unit =
   code(using ExecutionContext.global)

def wc[A](code: ExecutionContext ?=> Future[A]): A =
   Await.result(code(using ExecutionContext.global), Duration.Inf)

wc(Future("X"))
wc("Y")

The wc functions have the same bytecode-level signatures as the wf functions and can be compiled. However, neither can be invoked:

None of the overloaded alternatives of method wc with types
 [A](code: (scala.concurrent.ExecutionContext) ?=> scala.concurrent.Future[A]): A
 [A](code: (scala.concurrent.ExecutionContext) ?=> A²): Unit
match arguments (scala.concurrent.ExecutionContext => scala.concurrent.Future[String])

Found:    ("Y" : String)
Required: scala.concurrent.ExecutionContext => <?>

I understand that overloading is messy (I’m trying to quit…), but I’d still like to understand what’s going on here.

I don’t know if it’s a bug or limitation, but it works with explicit context function literal.

wc(ExecutionContext ?=> Future("X"))
wc(ExecutionContext ?=> "Y")

Maybe it has to do with the expected type computed for overloading.

The answer to the other question about why overloading with functions works is that the return type is part of the signature. If the return types are the same, you would resort to @targetName.

I am not sure how to solve the problem, though I note that context functions evaluate the context parameter very eagerly, which suggests but does not enforce that they search less thoroughly than when the type is already known.

I offer this magic spell as my way of getting around this issue. However, although I love overloading normally, both the working lambda and the non-working context function make me nervous: there really isn’t much to distinguish these things.

Here is the incantation:

transparent inline def wc[A](code: ExecutionContext ?=> A): Any = summonFrom:
  case ev: (A <:< Future[b]) =>
    Await.result(ev(code(using ExecutionContext.global)), Duration.Inf)
  case _ =>
    code(using ExecutionContext.global)
    ()
1 Like