我有一个简洁的想法(好吧,这是有争议的,但让我说有一个想法),使Scala中的隐式依赖注入更容易。我遇到的问题是,如果你调用任何需要隐式依赖的方法,你还必须装饰具有相同依赖关系的调用方法,直到具体的依赖关系最终在范围内。我的目标是能够将特征编码为在混合到具体类时需要一组含义,因此可以调用需要隐含的方法,但是将它们的定义推迟到实现者。
这样做的显而易见的方法是使用某种类型的自我类型来处理这个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]
以获得良好的语法之后,我认为从无形开始并看看我是否可以随时随地获取它会更聪明。我找到了这样的东西:
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
之间获得无限的相互递归。
我认为这是由于我在无形中犯了一些错误的原因是当我单步执行时,它会进入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]
而不再编译,我真的不知道如何解决。除了我的想法可能从一开始就被误导之后,我很想知道人们通常如何调试这些神秘的,细节的东西。有什么指针吗?
答案 0 :(得分:2)
当我对与implicits /类型级别行为相关的事情感到困惑时,我倾向于发现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
(因为&# 39; s你所说的话,所以只需拨打provide
即可获得所需的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机制的应用程序 - 尽管要注意二次编译时间。