【问题标题】:Scala: Why's implicit ExecutionContext not found by implicitly?Scala:为什么隐式找不到隐式 ExecutionContext?
【发布时间】:2017-03-12 00:39:24
【问题描述】:

我有一个特点:

trait Crawler {
  implicit def system: ActorSystem

  implicit def executionContext: ExecutionContext

  implicit def materializer: Materializer

  // other methods
}

还有一个测试类:

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
}

根据 Scalatest doc:

异步风格特征扩展了 AsyncTestSuite,它提供了一个 隐式 scala.concurrent.ExecutionContext 命名为 executionContext。

但是由于ExecutionContext 为空,测试因 NPE 而失败(应该正常失败,但这是另一回事)。

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)

为什么隐含的ExecutionContext 没有被拾取?

<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>

【问题讨论】:

  • 把隐式关键字从类和特征中去掉,看是否编译。
  • @pedrofurla 不会,特征中的方法需要隐式 ec。
  • 你知道这如何回答你的问题吗?
  • 我认为这种咆哮有点不必要。隐式是为提供类型类而创建的,并允许诸如类型类派生和类型级编程之类的东西。据我所知,它们通常不用于普通的依赖注入和传递东西,尽管手动参数在 Scala 世界中仍然是一回事。但是,我同意它们经常被解释得很差,有时被过度使用,并且由于implicit 关键字(隐含的defvalclass,论点是略有不同的野兽)做了不止一件事,它们可能是让刚接触这个概念的人感到困惑。
  • @MateuszKubuszok 据我所知,没有什么能阻止一个人从另一种类型显式转换。无论如何,这不是这个问题的重点,所以我把它留在这里。

标签: scala implicit-conversion implicit scalatest


【解决方案1】:

让我们看看这里发生了什么:

trait A {
  implicit def executionContext: ExecutionContext
}

您在这里声明A 将提供隐含值。那么

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

那么这里发生了什么?

  1. executionContextB构造过程中被初始化。
  2. 然后implicitly 尝试查找ExecutionContext 类型的值。
  3. 它找到了这样的值:executionContext

如此有效地你做了类似的事情:

class B extends A {

  val executionContext: ExecutionContext = executionContext
}

您创建了对初始化的循环依赖:您正在使用自身初始化值。因此,您可以通过“getter”获取一个值,该值返回一个仍为 null 的属性(因为它刚刚被初始化)。

我同意,隐含的概念是需要付出很多努力的东西,尽管我不会像你那样反对它们。在这里,您遇到了循环依赖初始化的问题。它不能优雅地失败,除了Exception 之外的任何东西都会使程序进入无效状态。

解决方案是在不使用 implicitly 的情况下初始化您的 implicit 值。只需手动将一些值放在那里。或者不要使用该特征并从其他地方隐式导入。

【讨论】:

  • 你能帮我理解循环依赖的来源吗?赋值语句的RHS不应该先求值,求值的时候找AsyncFlatSpec提供的隐式EC?
  • 编译器查看范围内的隐含项有一个顺序:这里你用CrawlerSpec提供的一个遮蔽了AsyncFlatSpec提供的一个。它的范围或多或少更接近于先行。比“相同的嵌套级别”更难接近。
猜你喜欢
  • 2013-02-04
  • 1970-01-01
  • 2016-01-23
  • 1970-01-01
  • 2013-06-22
  • 1970-01-01
  • 2019-07-21
  • 2020-12-08
  • 1970-01-01
相关资源
最近更新 更多