Abhijit Sarkar
Abhijit Sarkar

Reputation: 24518

Scala: Why's implicit ExecutionContext not found by implicitly?

I've a trait:

trait Crawler {
  implicit def system: ActorSystem

  implicit def executionContext: ExecutionContext

  implicit def materializer: Materializer

  // other methods
}

And a test class:

class CrawlerSpec extends AsyncFlatSpec with Matchers with Crawler {
  override implicit val system: ActorSystem = ActorSystem("ufo-sightings")

  override implicit val executionContext: ExecutionContext = implicitly[ExecutionContext]

  override implicit val materializer: Materializer = ActorMaterializer()

  // test
}

According to Scalatest doc:

Asynchronous style traits extend AsyncTestSuite, which provides an implicit scala.concurrent.ExecutionContext named executionContext.

But the test blows up with a NPE due to the ExecutionContext being null (should fail gracefully, but that's another matter).

java.lang.NullPointerException was thrown.
java.lang.NullPointerException
    at scala.concurrent.impl.Future$.apply(Future.scala:31)
    at scala.concurrent.Future$.apply(Future.scala:494)

Why isn't the implicit ExecutionContext picked up?

<rant>
  Implicit resolution is a nightmare. At the expense of saving a few 
  keystrokes, it makes code so fragile that removal of a single
  import breaks it. There's a reason other statically typed languages like
  Haskell or Kotlin don't have it; it's a stinking mess.
</rant>

Upvotes: 2

Views: 2176

Answers (1)

Mateusz Kubuszok
Mateusz Kubuszok

Reputation: 27535

Let's see what happens here:

trait A {
  implicit def executionContext: ExecutionContext
}

you declare here that A would provide implicit value. Then

class B extends A {
  override implicit val executionContext: ExecutionContext = implicitly[ExecutionContext]
}

So what does happen here?

  1. value executionContext is initialized during B construction.
  2. then implicitly tries to find a value with ExecutionContext type.
  3. it finds such value: executionContext.

So effectively you did something like:

class B extends A {

  val executionContext: ExecutionContext = executionContext
}

You created circular dependency on initialization: you are initializing value with itself. So you take a value though a "getter" returning a property that is still null (as it is just being initialized).

I agree, that implicits are concepts are something that requires a lot of effort, though I would not antagonize them as much as you. Here you had a problem with circular dependency on initialization. It cannot fail gracefully, anything other than Exception would put the program into invalid state.

Solution would be initializing you implicit value without the usage of implicitly. Just put some value there by hand. Or don't use that trait and import implicit from somewhere else.

Upvotes: 4

Related Questions