Reputation: 24518
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
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?
executionContext
is initialized during B
construction.implicitly
tries to find a value with ExecutionContext
type.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