尝试使用Shapeless递归地将case类转换为异类列表的奇怪行为

时间:2014-09-18 23:19:27

标签: scala scala-macros shapeless

我昨晚熬夜试图弄清楚这个没有形状的问题,如果我不把它从胸前拿走,我担心它会吃掉我的晚上,所以就这样了。

在这个最小化版本中,我只是定义了一个类型类,它将递归转换为heterogeneous列表中的案例类:

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
}

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

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)

然后(注意我已经清理了类型语法,因为这不是一个完全不可读的混乱):

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

到目前为止一切顺利。但那时:

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

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

<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]就好了,如果你直接提出它就会这样做。

在2.10.4和2.11.2以及2.0.0版本和主版本上都会发生这种情况。我很确定这必须是一个bug,但我不排除我做错了什么的可能性。以前有人见过这样的事吗?我的逻辑是否有问题或Generic我缺少某些限制?

好的,感谢您的聆听 - 也许现在我可以去看书或其他什么。

2 个答案:

答案 0 :(得分:8)

现在使用最近的无形-2.1.0-SNAPSHOT构建或多或少有效,并且此问题中的样本的近亲已添加为example

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

D而不是C发生这种情况的具体细节隐藏在Scala的类型搜索器的实现细节中,但粗略的近似,区别在于在C的分辨率期间,我们在B(较小)之前看到A(较大),因此分歧检查员很高兴我们的类型正在收敛;相反,在D的分辨率期间,我们在A(更大)之前看到B(较小),因此分歧检查器(保守地)保释。

无形2.1.0中的修复是最近增强的Lazy类型构造函数和关联的隐式宏基础结构。这允许更多用户控制分歧,并支持使用隐式解析来构造递归隐式值,这对于为递归类型自动派生类型类实例的能力至关重要。很多这方面的例子都可以在无形的代码库中找到,特别是返工类型类派生基础结构和Scrap Your Boilerplate实现,它不再需要专用的宏支持,而是完全根据Generic和{实现{1}}原语。在无形示例子项目中可以找到这些机制的各种应用。

答案 1 :(得分:7)

我采取了略微不同的方法。

trait CaseClassToHList[X] {
  type Out <: HList
}

trait LowerPriorityCaseClassToHList {
  implicit def caseClass[X](implicit gen: Generic[X]): CaseClassToHList[X] {
    type Out = generic.Repr
  } = null
}

object CaseClassToHList extends LowerPriorityCaseClassToHList {
  type Aux[X, R <: HList] = CaseClassToHList[X] { type Out = R }

  implicit def caseClassWithCaseClasses[X, R <: HList](
    implicit toHList: CaseClassToHList.Aux[X, R],
    nested: DeepHLister[R]): CaseClassToHList[X] {
    type Out = nested.Out
  } = null
}

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

object DeepHLister {

  implicit def hnil: DeepHLister[HNil] { type Out = HNil } = null

  implicit def caseClassAtHead[H, T <: HList](
    implicit head: CaseClassToHList[H],
    tail: DeepHLister[T]): DeepHLister[H :: T] {
    type Out = head.Out :: tail.Out
  } = null

  def apply[X <: HList](implicit d: DeepHLister[X]): d.type = null
}

使用以下代码进行测试:

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)

object Test {

  val z = DeepHLister[HNil]
  val typedZ: DeepHLister[HNil] {
    type Out = HNil
  } = z

  val a = DeepHLister[A :: HNil]
  val typedA: DeepHLister[A :: HNil] {
    type Out = (Int :: String :: HNil) :: HNil
  } = a

  val b = DeepHLister[B :: HNil]
  val typedB: DeepHLister[B :: HNil] {
    type Out = ((Int :: String :: HNil) :: (Int :: String :: HNil) :: HNil) :: HNil
  } = b

  val c = DeepHLister[C :: HNil]
  val typedC: DeepHLister[C :: HNil] {
    type Out = (((Int :: String :: HNil) :: (Int :: String :: HNil) :: HNil) :: (Int :: String :: HNil) :: HNil) :: HNil 
  } = c

  val d = DeepHLister[D :: HNil]
  val typedD: DeepHLister[D :: HNil] {
    type Out = ((Int :: String :: HNil) :: ((Int :: String :: HNil) :: (Int :: String :: HNil) :: HNil) :: HNil) :: HNil
  } = d
}