使用 Shapeless 的方法需要比必要更多的证据

More evidence needed than necessary for method using Shapeless

我昨天和几位同事一起探索 Shapeless,我们决定编写一个玩具方法来将一个添加到案例 class 的第一个参数,当该参数是 Int 时:

def addOneToCaseClass[C, H <: HList, E, T <: HList]
    (c: C)
    (implicit gen: Generic.Aux[C, H],
              h:   IsHCons.Aux[H, E, T],
              ev:  E =:= Int,
              ev2: (Int :: T) =:= H
    ): C = {

  val hList = gen.to(c)

  val elem = hList.head
  val tail = hList.tail

  val newElem = elem + 1

  gen.from(newElem :: tail)
}

在我看来 ev2 参数是多余的 - 当然可以推断出 E :: T =:= Int :: T,但编译器无法做到这一点。

有什么特别的原因吗?

您的直觉是合理的,但不幸的是 Scala 编译器不够聪明,无法从 hev 派生出 ev2。问题是 h 只确定 H 分解为 E :: T,它没有确定相反的情况,即 ET 合并等于 H.

我能想出的最简洁的表述与你的原文相似,但少了一个证人,

def addOneToCaseClass[C, R <: HList, T <: HList](c: C)
  (implicit
    gen: Generic.Aux[C, R],
    h: IsHCons.Aux[R, Int, T],
    ev: (Int :: T) =:= R) = {
  val hList = gen.to(c)
  val elem = hList.head
  val tail = hList.tail
  gen.from(elem+1 :: tail)
}

这里我们可以通过使用 h 证明 R 分解为 Int :: T 来消除 E =:= Int 的证明。然而,我们仍然需要证明 Int :: T 等于 R 以使用更新后的元素返回 gen