2017-02-11 68 views
4

在下面的代码中,我尝试导出无形状的类型实例。然而,对于更复杂的案例类(它被转换为更复杂的HList),编译器给了我一个“发散的隐式扩展”,即使它似乎没有两次解决同一种隐式类型。也许我错过了编译器的其他规则?为什么scalac在这里出现“分歧的隐含扩展”错误?

(小提琴:https://scalafiddle.io/sf/WEpnAXN/0

import shapeless._ 

trait TC[T] 

sealed trait Trait1 
case class SimpleClass(a: String) extends Trait1 

sealed trait Trait2 
case class ComplexClass(a: String, b: String) extends Trait2 

object Serialization extends App { 

    //Instances for HList 
    implicit val hnilInstance: TC[HNil] = ??? 
    implicit def hconsInstance[H, T <: HList] (implicit t: TC[T]): TC[H :: T] = ??? 

    //Instances for CoProduct 
    implicit val cnilInstance: TC[CNil] = ??? 
    implicit def cconsInstance[H, T <: Coproduct] (implicit h: TC[H], t: TC[T]): TC[H :+: T] = ??? 

    //Instances for Generic, relying on HNil & HCons 
    implicit def genericInstance[T, H] (implicit g: Generic.Aux[T, H], t: TC[H]): TC[T] = ??? 

    the[TC[SimpleClass :+: CNil]] //Works 
    the[TC[Trait1]]    //Works 
    the[TC[ComplexClass :+: CNil]] //Works 
    the[TC[Trait2]]    //Fails with diverging implicit expansion 
} 

当试图解决the[TC[Trait1]]编译器应该做这样的事情:

TC[Trait1] 
    Generic[Trait1] 
    TC[SimpleClass :+: CNil] 
     TC[SimpleClass] 
      Generic[SimpleClass] 
      TC[String :: HNil] 
     TC[CNil] 

这似乎工作。但是,对于2字段的case类,编译器无法做到这一点 - 所以我想知道:为什么我必须在这里使用Lazy才能使它工作?

TC[Trait2] 
    Generic[Trait2] 
    TC[ComplexClass :+: CNil] 
     TC[ComplexClass] 
      Generic[ComplexClass] 
      TC[String :: String :: HNil] 
     TC[CNil] 

我创造了一些小提琴这样你就可以执行的代码有directy。

+1

我怀疑迈尔斯的答案[这里](http://stackoverflow.com/a/27911353/334519)是解释,尽管在这个问题上我的情况并不完全一样。 –

回答

6

几年前,当我通过some issues like this工作时,我发现找出分歧检查器所做的最简单的方法就是将一些println s放入编译器并在本地发布。在2.12的相关代码是dominates方法here,在这里我们可以像这样的东西代替的最后一行:

overlaps(dtor1, dted1) && (dtor1 =:= dted1 || { 
    val dtorC = complexity(dtor1) 
    val dtedC = complexity(dted1) 
    val result = dtorC > dtedC 

    println(if (result) "Dominates:" else "Does not dominate:") 
    println(s"$dtor (complexity: $dtorC)") 
    println(s"$dted (complexity: $dtedC)") 
    println("===========================") 
    result 
}) 

然后我们就可以sbt publishLocal scalac并尝试编译代码:

Dominates: 
TC[shapeless.::[String,shapeless.::[String,shapeless.HNil]]] (complexity: 7) 
TC[shapeless.:+:[ComplexClass,shapeless.CNil]] (complexity: 6) 
=========================== 

的这里的问题是,我们正在寻找适合String :: String :: HNil一个TC实例(最低节点树中的),但我们有ComplexClass :+: CNil(三个步骤上)开放式搜索。编译器认为String :: String :: HNil重叠并支配ComplexClass :+: CNil,并且它因为看起来递归而退出。

这听起来很可笑,所以我们可以做一个实验,试图通过添加一些复杂的副产品部分,看会发生什么来说服自己。让我们只需添加一个构造函数:

case class Foo(i: Int) extends Trait2 

现在一切正常,我们在编译过程中得到这个消息:

Does not dominate: 
TC[shapeless.::[String,shapeless.::[String,shapeless.HNil]]] (complexity: 7) 
TC[shapeless.:+:[ComplexClass,shapeless.:+:[Foo,shapeless.CNil]]] (complexity: 9) 

所以ComplexClass hlist表示仍然重叠Trait2余积表示,但它不“T主宰它,因为Trait2表示(开隐含TC搜索,我们即将担心的主题)现在更加复杂。

检查者显然是过于偏执这里,其行为may change in the future,但现在我们还是坚持了下来。正如你所注意到的,最直接和最简单的解决方法是在其中粘贴一个Lazy以隐藏来自分歧检查器的所谓递归。

在这种情况下,特别是,虽然它看起来像只是把实例在TC同伴对象的作品,以及:

import shapeless._ 

sealed trait Trait1 
case class SimpleClass(a: String) extends Trait1 

sealed trait Trait2 
case class ComplexClass(a: String, b: String) extends Trait2 

trait TC[T] 

object TC { 
    //Instances for HList 
    implicit def hnilInstance: TC[HNil] = ??? 
    implicit def hconsInstance[H, T <: HList](implicit t: TC[T]): TC[H :: T] = ??? 

    //Instances for CoProduct 
    implicit def cnilInstance: TC[CNil] = ??? 
    implicit def cconsInstance[H, T <: Coproduct](implicit 
    h: TC[H], t: TC[T] 
): TC[H :+: T] = ??? 

    //Instances for Generic, relying on HNil & HCons 
    implicit def genericInstance[T, H](implicit 
    g: Generic.Aux[T, H], t: TC[H] 
): TC[T] = ??? 
} 

然后:

Does not dominate: 
TC[shapeless.::[String,shapeless.::[String,shapeless.HNil]]] (complexity: 7) 
TC[shapeless.:+:[ComplexClass,shapeless.CNil]] (complexity: 16) 

为什么移动的东西,周围像这提高了副产品的复杂性?我不知道。

+1

使这些错误的票证更加自我解释:https://issues.scala-lang.org/browse/SI-8467 –

+0

我没有看到这个问题,@SethTisue,但它听起来像只限于'-Xlog-implicits'的输出,并且只是当前消息的一个更具体的子集并不真正有助于这种奇怪的分歧情况。 –

+0

非常好的解释!尽管我们似乎仍然不明白为什么编译器会出现这种偏执狂,但至少现在的失败点已经非常清楚了。谢谢! – valenterry

相关问题