Implicit ExecutionContext priority in Scala 2.12
Asked Answered
F

1

7

In Scala 2.12 importing the global execution context and then having another implicit execution context defined in the scope results in an ambiguous implicit, while in 2.11 it works just fine.

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

class A(implicit ec: ExecutionContext) {
  x = implicitly[ExecutionContext]
}

Compiler gives error:

error: ambiguous implicit values:
 both lazy value global in object Implicits of type => scala.concurrent.ExecutionContext
 and value ec in class A of type scala.concurrent.ExecutionContext
 match expected type scala.concurrent.ExecutionContext
       val x = implicitly[ExecutionContext]
                         ^

What is the cause of this and how to work around it in code?

Frottage answered 5/11, 2016 at 20:10 Comment(4)
The relevant commit in the scala/scala repo is github.com/scala/scala/commit/… and the justification for the change is issues.scala-lang.org/browse/SI-8849Tape
Viktor Klang comments on that JIRA ticket: "recommend to either pass ExecutionContext.global explicitly or introduce their own implicit val pointing to it where needed"Tape
If I understood the issue correctly, in 2.11 it actually works "wrong" or at the very least unexpectedly, as the "global" is selected over the implicit in the scope? And 2.12 exposes this potential programming mistake by making it ambiguous. Would be worth mentioning in the release notes and/or in the documentation.Frottage
Viktor Klang on Gitter: "in < 2.12 the implicit resolution of global vs user-provided ECs was surprising to say the least. So any conflicts are now indications that you had a bug" (gitter.im/scala/scala?at=5822218431c5cbef43d051d8)Tape
M
2

The spec treats overload resolution as the disambiguation of a selection of members of a class. But implicit resolution uses static overload resolution to choose between references which are not members.

Arguably, the following is a misinterpretation of the spec, since zzz is defined in a class derived from X much as yyy is:

$ scala
Welcome to Scala 2.12.0 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_101).
Type in expressions for evaluation. Or try :help.

scala> import concurrent._, ExecutionContext.global
import concurrent._
import ExecutionContext.global

scala> trait X { implicit val xxx: ExecutionContext = global }
defined trait X

scala> class Y extends X { implicit val yyy: ExecutionContext = global ; def f = implicitly[ExecutionContext] }
defined class Y

scala> class Z extends X { def f(implicit zzz: ExecutionContext) = implicitly[ExecutionContext] }
<console>:16: error: ambiguous implicit values:
 both value xxx in trait X of type => scala.concurrent.ExecutionContext
 and value zzz of type scala.concurrent.ExecutionContext
 match expected type scala.concurrent.ExecutionContext
       class Z extends X { def f(implicit zzz: ExecutionContext) = implicitly[ExecutionContext] }
                                                                             ^

Currently, you must rely on naming to shadow the implicit from enclosing scope:

scala> class Z extends X { def f(implicit xxx: ExecutionContext) = implicitly[ExecutionContext] }
defined class Z

Or,

scala> :pa
// Entering paste mode (ctrl-D to finish)

package object p { import concurrent._ ; implicit val xxx: ExecutionContext = ExecutionContext.global }
package p { import concurrent._ ;
  class P { def f(implicit xxx: ExecutionContext) = implicitly[ExecutionContext]
            def g = implicitly[ExecutionContext] }
}

// Exiting paste mode, now interpreting.


scala> 
Muck answered 5/11, 2016 at 21:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.