【发布时间】: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.provide 和Required.provide 之间的无限相互递归。
我认为这是由于我在 shapeless 上犯了一些错误的原因是,当我通过时,它会到达 select[T],然后进入 HListOps(有道理,因为 HListOps 是 @987654328 @ 方法)然后似乎反弹到另一个对 Requires.provide 的调用。
我的第一个想法是它试图从provide 中获取隐含的Selector[L,T],因为provide 并没有明确地防止这种情况发生。但是,
- 编译器应该意识到它不会从
provide中得到Selector,因此要么选择了另一个候选者,要么编译失败。 - 如果我通过要求
provide接收隐含的Selector[L,T]来保护provide(在这种情况下,我可以通过apply和Selector来获得T)然后它不会再编译,因为 @ 987654340@,我真的不知道如何处理。
除了我的想法可能一开始就被误导的事实之外,我很想知道人们通常如何调试这些神秘的、细节的东西。有什么指点吗?
【问题讨论】:
-
您的方法与
trait HasFoo { implicit def foo: Foo }、trait MyTrait { self: HasFoo => ... }有何不同,仅仅是为了避免定义HasFoo吗?你可以做trait Thing { self: HasFoo with HasBar => -
您说得对,您提到的方法更简单且同样有效。我认为对它的反对(不是说这是一个有效的反对)是您将最终实现的命名空间与显式命名的依赖项混为一谈。这与隐含它们的目的背道而驰,因为此时您可以将
foo显式传递给隐式需要它的任何方法调用。我的想法的目标是避免这种情况(不确定这是否是一个值得的目标)。