使用无形选择[U]

时间:2015-07-02 05:40:57

标签: scala shapeless implicits

我有一个简洁的想法(好吧,这是有争议的,但让我说有一个想法),使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.provideRequired.provide之间获得无限的相互递归。

我认为这是由于我在无形中犯了一些错误的原因是当我单步执行时,它会进入select[T],然后进入HListOps(有意义,因为HListOps是有的select[T]方法)然后似乎又反弹回另一个Requires.provide的调用。

我的第一个想法是,它试图从Selector[L,T]获得一个隐含的provide,因为provide没有明确地防范这一点。但是,

  1. 编译器应该已经意识到它不会从Selector中获取provide,并且选择了另一个候选者或者无法编译。
  2. 如果我保护provide,要求它收到隐式Selector[L,T](在这种情况下,我可以apply Selector获取T)然后它由于diverging implicit expansion for type shapeless.ops.hlist.Selector[Int :: String :: HNil]而不再编译,我真的不知道如何解决。
  3. 除了我的想法可能从一开始就被误导之后,我很想知道人们通常如何调试这些神秘的,细节的东西。有什么指针吗?

1 个答案:

答案 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机制的应用程序 - 尽管要注意二次编译时间。