1

我有一个巧妙的想法(嗯,这值得商榷,但假设我有一个想法)让 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.provideRequired.provide

我认为这是由于我在 shapeless 上犯的一些错误的原因是,当我逐步完成时,它会到达那个select[T]位置,然后进入 HListOps(有道理,因为 HListOps 是有select[T]方法的)然后似乎反弹回来进入另一个呼叫Requires.provide

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

  1. 编译器应该已经意识到它不会Selector退出provide,要么选择了另一个候选者,要么编译失败。
  2. 如果我provide通过要求它接收隐式来保护Selector[L,T](在这种情况下,我可以只apply得到) SelectorT那么由于 ,它不再编译diverging implicit expansion for type shapeless.ops.hlist.Selector[Int :: String :: HNil],我真的不知道如何进行寻址。

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

4

1 回答 1

2

当我对与隐式/类型级别行为相关的事情感到困惑时,我倾向于发现该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的(因为这是你告诉它的),所以它只是调用provide来获取所需的Selector[L, T]. 在编译时它只解决一次,因此在编译时没有发散隐式,没有混淆 - 只有在运行时。

发生分歧的隐式扩展是因为编译器寻找 a Selector[Int :: String :: HNil],它认为provide如果给定 a 可以给它一个Selector[Int :: String :: HNil, Selector[Int :: String :: HNil]],它认为如果给定 aprovide可以给它一个,Selector[Int :: String :: HNil, Selector[Int :: String :: HNil, Selector[Int :: String :: HNil]]并且在某些时候它意识到这是一个无限循环。你期望它在哪里/如何得到Selector它需要的?我认为你provide被误导了,因为它太笼统了。ThingDoer.needsInt在尝试使其全部隐式之前,请先尝试使用显式 int进行调用。

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

于 2015-07-02T13:28:39.217 回答