【问题标题】:Infinite recursion with Shapeless select[U]无形选择的无限递归[U]
【发布时间】:2015-07-02 05:40:57
【问题描述】:

我有一个巧妙的想法(嗯,这是有争议的,但假设我有一个想法)在 Scala 中使隐式依赖注入更容易。我遇到的问题是,如果您调用任何需要隐式依赖项的方法,您还必须使用相同的依赖项装饰调用方法,一直到该具体依赖项最终在范围内。我的目标是能够将特征编码为在混入具体类时需要一组隐式,因此它可以调用需要隐式的方法,但将它们的定义推迟到实现者。

执行此操作的明显方法是使用某种 selftype 一个 la this psuedo-scala:

object ThingDoer {
  def getSomething(implicit foo: Foo): Int = ???
}

trait MyTrait { self: Requires[Foo and Bar and Bubba] =>
  //this normally fails to compile unless doThing takes an implicit Foo
  def doThing = ThingDoer.getSomething
}

经过几次勇敢的尝试实际实现 trait and[A,B] 以获得良好的语法后,我认为从 shapeless 开始会更聪明,看看我是否能得到任何结果。我遇到了这样的事情:

import shapeless._, ops.hlist._

trait Requires[L <: HList] {
  def required: L
  implicit def provide[T]:T = required.select[T]
}

object ThingDoer {
  def needsInt(implicit i: Int) = i + 1
}

trait MyTrait { self: Requires[Int :: String :: HNil] =>
  val foo = ThingDoer.needsInt
}

class MyImpl extends MyTrait with Requires[Int :: String :: HNil] {
  def required = 10 :: "Hello" :: HNil
  def showMe = println(foo)
}

我不得不说,当它实际编译时,我非常兴奋。但是,事实证明,当你实际实例化MyImpl 时,你会得到MyImpl.provideRequired.provide 之间的无限相互递归。

我认为这是由于我在 shapeless 上犯了一些错误的原因是,当我通过时,它会到达 select[T],然后进入 HListOps(有道理,因为 HListOps 是 @987654328 @ 方法)然后似乎反弹到另一个对 Requires.provide 的调用。

我的第一个想法是它试图从provide 中获取隐含的Selector[L,T],因为provide 并没有明确地防止这种情况发生。但是,

  1. 编译器应该意识到它不会从provide 中得到Selector,因此要么选择了另一个候选者,要么编译失败。
  2. 如果我通过要求 provide 接收隐含的 Selector[L,T] 来保护 provide(在这种情况下,我可以通过 applySelector 来获得 T)然后它不会再编译,因为 @ 987654340@,我真的不知道如何处理。

除了我的想法可能一开始就被误导的事实之外,我很想知道人们通常如何调试这些神秘的、细节的东西。有什么指点吗?

【问题讨论】:

  • 您的方法与trait HasFoo { implicit def foo: Foo }trait MyTrait { self: HasFoo =&gt; ... } 有何不同,仅仅是为了避免定义HasFoo 吗?你可以做trait Thing { self: HasFoo with HasBar =&gt;
  • 您说得对,您提到的方法更简单且同样有效。我认为对它的反对(不是说这是一个有效的反对)是您将最终实现的命名空间与显式命名的依赖项混为一谈。这与隐含它们的目的背道而驰,因为此时您可以将foo 显式传递给隐式需要它的任何方法调用。我的想法的目标是避免这种情况(不确定这是否是一个值得的目标)。

标签: scala shapeless implicits


【解决方案1】:

当我对与隐式/类型级行为相关的事情感到困惑时,我倾向于发现reify 技术很有用:

scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> val required: HList = HNil
required: shapeless.HList = HNil
scala> reify { implicit def provide[T]:T = required.select[T] }
res3: reflect.runtime.universe.Expr[Unit] =
Expr[Unit]({
  implicit def provide[T]: T = HList.hlistOps($read.required).select[T](provide);
  ()
})

此时很容易看出哪里出了问题——编译器认为provide 可以提供任何任意 T(因为这是你告诉它的),所以它只调用@ 987654325@ 获取所需的Selector[L, T]。在编译时它只解决一次,因此在编译时没有发散隐式,没有混淆 - 只有在运行时。

由于编译器寻找Selector[Int :: String :: HNil],它认为provide可以给它一个Selector[Int :: String :: HNil, Selector[Int :: String :: HNil]],它认为provide可以给它一个Selector[Int :: String :: HNil, Selector[Int :: String :: HNil, Selector[Int :: String :: HNil]]和在某些时候它意识到这是一个无限循环。您希望它在哪里/如何获得它需要的Selector?我认为您的provide 被误导了,因为它太笼统了。尝试使用显式 int 调用ThingDoer.needsInt,然后再尝试使其全部隐式。

这种通用方法确实有效 - 我已经编写了将其用作 DI 机制的应用程序 - 尽管要注意二次编译时间。

【讨论】:

    猜你喜欢
    • 2014-11-26
    • 2016-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-14
    • 2011-08-16
    相关资源
    最近更新 更多