尝试使用 Shapeless 递归地将案例类转换为异构列表的奇怪行为 [英] Weird behavior trying to convert case classes to heterogeneous lists recursively with Shapeless

查看:32
本文介绍了尝试使用 Shapeless 递归地将案例类转换为异构列表的奇怪行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

昨晚我熬夜太晚试图找出这个无形的问题,我担心如果我不把它从我的胸膛中取出来,我担心它会影响我的晚上,所以就这样了.

I stayed up way too late last night trying to figure out this Shapeless issue and I'm afraid it's going to eat my evening if I don't get it off my chest, so here goes.

在这个最小化版本中,我只是定义了一个类型类,它将递归地将 case 类转换为 异构 列表:

In this minimized version I'm just defining a type class that will recursively convert case classes into heterogeneous lists:

import shapeless._

trait DeepHLister[R <: HList] extends DepFn1[R] { type Out <: HList }

trait LowPriorityDeepHLister {
  type Aux[R <: HList, Out0 <: HList] = DeepHLister[R] { type Out = Out0 }

  implicit def headNotCaseClassDeepHLister[H, T <: HList](implicit
    dht: DeepHLister[T]
  ): Aux[H :: T, H :: dht.Out] = new DeepHLister[H :: T] {
    type Out = H :: dht.Out
    def apply(r: H :: T) = r.head :: dht(r.tail)
  }
}

object DeepHLister extends LowPriorityDeepHLister {
  implicit object hnilDeepHLister extends DeepHLister[HNil] {
    type Out = HNil
    def apply(r: HNil) = HNil
  }

  implicit def headCaseClassDeepHLister[H, R <: HList, T <: HList](implicit
    gen: Generic.Aux[H, R],
    dhh: DeepHLister[R],
    dht: DeepHLister[T]
  ): Aux[H :: T, dhh.Out :: dht.Out] = new DeepHLister[H :: T] {
    type Out = dhh.Out :: dht.Out
    def apply(r: H :: T) = dhh(gen.to(r.head)) :: dht(r.tail)
  }

  def apply[R <: HList](implicit dh: DeepHLister[R]): Aux[R, dh.Out] = dh
}

让我们试试吧!首先我们需要一些案例类:

Let's try it out! First we need some case classes:

case class A(x: Int, y: String)
case class B(x: A, y: A)
case class C(b: B, a: A)
case class D(a: A, b: B)

然后(请注意,我已经清理了类型语法,以免造成完全无法阅读的混乱):

And then (note that I've cleaned up the type syntax for the sake of this not being a totally unreadable mess):

scala> DeepHLister[A :: HNil]
res0: DeepHLister[A :: HNil]{
  type Out = (Int :: String :: HNil) :: HNil
} = DeepHLister$$anon$2@634bf0bf

scala> DeepHLister[B :: HNil]
res1: DeepHLister[B :: HNil] {
  type Out = (
    (Int :: String :: HNil) :: (Int :: String :: HNil) :: HNil
  ) :: HNil
} = DeepHLister$$anon$2@69d6b3e1

scala> DeepHLister[C :: HNil]
res2: DeepHLister[C :: HNil] {
  type Out = (
    ((Int :: String :: HNil) :: (Int :: String :: HNil) :: HNil) ::
    (Int :: String :: HNil) ::
    HNil
  ) :: HNil
} = DeepHLister$$anon$2@4d062faa

到目前为止一切顺利.但随后:

So far so good. But then:

scala> DeepHLister[D :: HNil]
res3: DeepHLister[D :: HNil] {
  type Out = ((Int :: String :: HNil) :: B :: HNil) :: HNil
} = DeepHLister$$anon$2@5b2ab49a

B 没有被转换.如果我们打开 -Xlog-implicits 这是最后一条消息:

The B didn't get converted. If we turn on -Xlog-implicits this is the last message:

<console>:25: this.DeepHLister.headCaseClassDeepHLister is not a valid implicit value for DeepHLister[shapeless.::[B,shapeless.HNil]] because:
hasMatchingSymbol reported error: diverging implicit expansion for type DeepHLister[this.Repr]
starting with method headNotCaseClassDeepHLister in trait LowPriorityDeepHLister
              DeepHLister[D :: HNil]
                         ^

这对我来说没有意义——headCaseClassDeepHLister 应该能够生成 DeepHLister[B :: HNil] 就好了,如果你直接问它就可以了.

Which doesn't make sense to me—headCaseClassDeepHLister should be able to generate DeepHLister[B :: HNil] just fine, and it does if you ask it directly.

这发生在 2.10.4 和 2.11.2 上,以及 2.0.0 版本和主版本.我很确定这一定是一个错误,但我不排除我做错了什么的可能性.有没有人见过这样的事情?我的逻辑有问题还是我遗漏了对 Generic 的某些限制?

This happens on both 2.10.4 and 2.11.2, and with both the 2.0.0 release and master. I'm pretty sure this has to be a bug, but I'm not ruling out the possibility that I'm doing something wrong. Has anyone seen anything like this before? Is there something wrong with my logic or some restriction on Generic I'm missing?

好的,感谢您的聆听——也许现在我可以去看书什么的了.

Okay, thanks for listening—maybe now I can go read a book or something.

推荐答案

这现在或多或少像使用最近的 shapeless-2.1.0-SNAPSHOT 版本编写的那样工作,并且此问题中的示例的近亲已添加到那里作为示例.

This now works more or less as written using recent shapeless-2.1.0-SNAPSHOT builds, and a close relative of the sample in this question has been added there as an example.

原来的问题是 Generic 的每次扩展都会在 DeepHLister 类型类的隐式解析中引入一个新的 HList 类型实例,原则上,可以生成一个 HList 类型,该类型与以前在相同分辨率下看到的某些类型相关但更复杂.这种情况会使散度检查器跳闸并中止解析过程.

The problem with the original is that each expansion of a Generic introduces a new HList type into the implicit resolution of the DeepHLister type class instances and, in principle, could produce an HList type that is related to but more complex than some type seen previously during the same resolution. This condition trips the divergence checker and aborts the resolution process.

为什么 D 会发生这种情况而不是 C 会发生这种情况的确切细节潜伏在 Scala 类型检查器的实现细节中,但粗略地说,区别在于是在解析 C 的过程中,我们看到 B(较大)在 A(较小)之前,所以散度检查器很高兴我们的类型正在收敛;相反,在解析 D 的过程中,我们看到 A(较小)在 B(较大)之前,因此散度检查器(保守地)失效.

The exact details of why this happens for D but not for C is lurking in the details of the implementation of Scala's typechecker but, to a rough approximation, the differentiator is that during the resolution for C we see the B (larger) before the A (smaller) so the divergence checker is happy that our types are converging; conversely during the resolution for D we see the A (smaller) before the B (larger) so the divergence checker (conservatively) bails.

在 shapeless 2.1.0 中对此的修复是最近增强的 Lazy 类型构造函数和关联的隐式宏基础结构.这允许用户对发散进行更多控制,并支持使用隐式解析来构造递归隐式值,这对于自动派生递归类型的类型类实例的能力至关重要.在无形代码库中可以找到许多此类示例,特别是重新设计的类型类派生基础结构和 Scrap Your Boilerplate 实现,它们不再需要专门的宏支持,而是完全按照 GenericLazy 原语.这些机制的各种应用可以在无形示例子项目中找到.

The fix for this in shapeless 2.1.0 is the recently enhanced Lazy type constructor and associated implicit macro infrastructure. This allows much more user control over divergence and supports the use of implicit resolution to construct the recursive implicit values which are crucial to the ability to automatically derive type class instances for recursive types. Many examples of this can be found in the shapeless code base, in particular the reworked type class derivation infrastructure and Scrap Your Boilerplate implementation, which no longer require dedicated macro support, but are implemented entirely in terms of the Generic and Lazy primitives. Various applications of these mechanisms can be found in the shapeless examples sub-project.

这篇关于尝试使用 Shapeless 递归地将案例类转换为异构列表的奇怪行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆