简体   繁体   English

为什么scalac在这里出现“分歧的隐式扩展”错误?

[英]Why does scalac rise a “diverging implicit expansion” error here?

In the following code I try to derive typeclass instances with shapeless. 在下面的代码中,我尝试使用shapeless派生类型类实例。 However, in the case of a more complex case class (which is converted to a more complex HList), the compiler gives me a "diverging implicit expansion" even though it does not seem to resolve the same kind of implicit type twice. 但是,在更复杂的案例类(转换为更复杂的HList)的情况下,编译器给我一个“分歧的隐式扩展”,即使它似乎没有两次解析相同类型的隐式类型。 Maybe I am missing some other rule of the compiler? 也许我错过了编译器的其他一些规则?

(Fiddle: https://scalafiddle.io/sf/WEpnAXN/0 ) (小提琴: 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
}

When trying to resolve the[TC[Trait1]] the compiler should do something like that: 在尝试解析the[TC[Trait1]] ,编译器应该执行以下操作:

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

which seems to work. 这似乎工作。 However, with the 2-field case class, the compiler fails to do something like this - so I wonder: why do I have to use Lazy here already to make it work? 但是,对于2字段的case类,编译器无法做到这样的事情 - 所以我想知道:为什么我必须在这里使用Lazy才能使它工作?

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

I have created some fiddle so you can execute the code there directy. 我已经创建了一些小提琴,因此您可以直接执行代码。

A couple of years ago when I was working through some issues like this I found that the easiest way to figure out what the divergence checker was doing was just to throw some println s into the compiler and publish it locally. 几年前,当我处理这样的问题时,我发现找出分歧检查器所做的最简单的方法就是将一些println抛入编译器并在本地发布。 In 2.12 the relevant code is the dominates method here , where we can replace the last line with something like this: 在2.12中,相关代码是这里dominates方法,我们可以用这样的方式替换最后一行:

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
})

Then we can sbt publishLocal scalac and try to compile your code: 然后我们可以sbt publishLocal scalac并尝试编译你的代码:

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

The problem here is that we're looking for a TC instance for String :: String :: HNil (the lowest node in your tree), but we have an open search for ComplexClass :+: CNil (three steps up). 这里的问题是我们正在为String :: String :: HNil (树中最低的节点)寻找一个TC实例,但我们对ComplexClass :+: CNil进行了开放式搜索ComplexClass :+: CNil (三步)。 The compiler thinks that String :: String :: HNil both overlaps and dominates ComplexClass :+: CNil , and it bails out because that looks recursive. 编译器认为String :: String :: HNil重叠并支配ComplexClass :+: CNil ,并且它会因为看起来递归而退出。

This sounds ridiculous, so we can do an experiment to try to convince ourselves by adding some complexity to the coproduct part and seeing what happens. 这听起来很荒谬,所以我们可以做一个实验,试图通过在副产品部分增加一些复杂性并看看会发生什么来说服自己。 Let's just add a constructor: 我们只需添加一个构造函数:

case class Foo(i: Int) extends Trait2

Now everything works fine, and we get this message during compilation: 现在一切正常,我们在编译期间收到此消息:

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

So the ComplexClass hlist representation still overlaps the Trait2 coproduct representation, but it doesn't dominate it, since the Trait2 representation (the subject of the open implicit TC search we're worried about) is now more complex. 所以ComplexClass hlist表示仍然重叠 Trait2余积表示,但并不支配它,因为Trait2表示(开放隐含的主题TC的搜索,我们即将担心)现在更加复杂。

The checker is clearly being much too paranoid here, and its behavior may change in the future , but for now we're stuck with it. 检查员显然在这里过于偏执,其行为可能在未来发生变化 ,但现在我们仍然坚持下去。 As you note, the most straightforward and fool-proof workaround is to stick a Lazy in there to hide the supposed recursion from the divergence checker. 正如您所注意到的,最直接和最简单的解决方法是将Lazy放在那里以隐藏来自分歧检查器的假定递归。

In this case specifically, though, it looks like just putting the instances in the TC companion object works as well: 但在具体情况下,看起来只是将实例放在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] = ???
}

And then: 然后:

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

Why does moving stuff around like this raise the complexity of the coproduct? 为什么像这样移动东西会增加副产品的复杂性? I have no idea. 我不知道。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM