在下面的代码中,我尝试使用shapeless派生类型类实例。但是,在更复杂的案例类(转换为更复杂的HList)的情况下,编译器给我一个“分歧的隐式扩展”,即使它似乎没有两次解析相同类型的隐式类型。也许我错过了编译器的其他一些规则?
(小提琴: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]
我已经创建了一些小提琴,因此您可以直接执行代码。
答案 0 :(得分:6)
几年前,当我通过some issues like this工作时,我发现找出分歧检查器所做的最简单的方法就是将一些0
扔进编译器并发布它本地。在2.12中,相关代码是int
方法here,我们可以用以下内容替换最后一行:
println
然后我们可以dominates
scalac并尝试编译您的代码:
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
(树中最低的节点)寻找Dominates:
TC[shapeless.::[String,shapeless.::[String,shapeless.HNil]]] (complexity: 7)
TC[shapeless.:+:[ComplexClass,shapeless.CNil]] (complexity: 6)
===========================
个实例,但我们对TC
进行了公开搜索(三个步骤)向上)。编译器认为String :: String :: HNil
重叠并支配ComplexClass :+: CNil
,并且因为看起来是递归的而导致失败。
这听起来很荒谬,所以我们可以做一个实验,试图通过在副产品部分增加一些复杂性并看看会发生什么来说服自己。我们只需添加一个构造函数:
String :: String :: HNil
现在一切正常,我们在编译期间收到此消息:
ComplexClass :+: CNil
因此case class Foo(i: Int) extends Trait2
hlist表示仍然重叠 Does not dominate:
TC[shapeless.::[String,shapeless.::[String,shapeless.HNil]]] (complexity: 7)
TC[shapeless.:+:[ComplexClass,shapeless.:+:[Foo,shapeless.CNil]]] (complexity: 9)
联合产品表示,但它不支配它,因为ComplexClass
表示(主题为我们担心的开放隐式Trait2
搜索现在变得更加复杂。
检查员在这里显然太偏执了,它的行为may change in the future,但是现在我们一直坚持下去。正如您所指出的,最直接和最简单的解决方法是在其中粘贴Trait2
以隐藏来自分歧检查器的假定递归。
在这种情况下,具体而言,看起来只是将实例放在TC
伴随对象中也是如此:
Lazy
然后:
TC
为什么像这样移动东西会增加副产品的复杂性?我不知道。