我有一个巧妙的想法(嗯,这值得商榷,但假设我有一个想法)让 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
}
经过几次勇敢地尝试实际实现 atrait 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 是有select[T]
方法的)然后似乎反弹回来进入另一个呼叫Requires.provide
。
Selector[L,T]
我的第一个想法是它试图从中获取隐含provide
,因为provide
没有明确地防止这种情况发生。但,
- 编译器应该已经意识到它不会
Selector
退出provide
,要么选择了另一个候选者,要么编译失败。 - 如果我
provide
通过要求它接收隐式来保护Selector[L,T]
(在这种情况下,我可以只apply
得到)Selector
,T
那么由于 ,它不再编译diverging implicit expansion for type shapeless.ops.hlist.Selector[Int :: String :: HNil]
,我真的不知道如何进行寻址。
除了我的想法可能一开始就被误导的事实之外,我很想知道人们通常如何调试这些神秘的、细节的东西。任何指针?