【问题标题】:Shapeless HList type checking无形 HList 类型检查
【发布时间】:2015-07-02 07:05:32
【问题描述】:

我正在使用 Shapeless 并且有以下方法来计算两个 HList 之间的差异:

  def diff[H <: HList](lst1: H, lst2:H):List[String] = (lst1, lst2) match {
    case (HNil, HNil)                 => List()
    case (h1::t1, h2::t2) if h1 != h2 => s"$h1 -> $h2" :: diff(t1, t2)
    case (h1::t1, h2::t2)             => diff(t1, t2)
    case _                            => throw new RuntimeException("something went very wrong")
  }

由于该方法的两个参数都采用H,因此我希望不同类型的 HList 不会在此处编译。例如:

diff("a" :: HNil, 1 :: 2 :: HNil)

不应该编译,但可以编译,并且会产生运行时错误:java.lang.RuntimeException: something went very wrong。我可以对类型参数做些什么来使这个方法只接受具有相同类型的两个边吗?

【问题讨论】:

  • 您似乎没有处理lst1 或lst2 中只有一个为空的情况,这可能很好地解释了您的错误。
  • 我理解错误,但我想要的是编译错误,而不是运行时错误
  • 哦,我明白你想要达到的目标。不幸的是,基本的HList 特征是未参数化的,因此在您的方法调用中H 只是解析为Hlist(这确实是任何Hlist 的超类型,而与具体元素类型无关)。看我的回答。

标签: scala shapeless type-level-computation


【解决方案1】:

其他答案没有真正解决的一件事是,这完全是一个类型推断问题,只需将参数列表一分为二即可解决:

def diff[H <: HList](lst1: H)(lst2: H): List[String] = (lst1, lst2) match {
  case (HNil, HNil)                 => List()
  case (h1::t1, h2::t2) if h1 != h2 => s"$h1 -> $h2" :: diff(t1)(t2)
  case (h1::t1, h2::t2)             => diff(t1)(t2)
  case _                            => throw new RuntimeException("bad!")
}

这给了我们想要的东西:

scala> diff("a" :: HNil)(1 :: 2 :: HNil)
<console>:15: error: type mismatch;
 found   : shapeless.::[Int,shapeless.::[Int,shapeless.HNil]]
 required: shapeless.::[String,shapeless.HNil]
       diff("a" :: HNil)(1 :: 2 :: HNil)
                           ^

这是可行的(即不会编译不当然后在运行时崩溃),因为 Scala 的方法类型推断是基于每个参数列表工作的。如果lst1 和lst2 在同一个参数列表中,H 将被推断为它们的最小上限,这通常不是您想要的。

如果您将lst1 和lst2 放在单独的参数列表中,那么编译器将在看到lst1 后立即决定H 是什么。如果lst2 没有相同的类型,它就会爆炸(这是我们的目标)。

您仍然可以通过将 H 显式设置为 HList 来解决此问题,但恐怕这是您自己的事。

【讨论】:

  • 是的,这是一个类型推断问题。是的,引入第二个参数列表是克服这个问题的一种方法。这实际上正是我的解决方案所利用的(在这种情况下,第二个参数列表包含隐含的证据,而不是 lst2)。您的解决方案稍微简单一些,而我的解决方案使用起来更自然(我猜)。
【解决方案2】:

不幸的是,基本的 HList 特征是未参数化的,因此在您的方法调用中 H 只是解析为 Hlist (这确实是任何 Hlist 的超类型,而与具体元素类型无关)。 为了解决这个问题,我们必须稍微改变一下定义,转而依赖广义的类型约束:

def diff[H1 <: HList, H2 <: HList](lst1: H1, lst2: H2)(implicit e: H1 =:= H2): List[String] = (lst1, lst2) match {
  case (HNil, HNil)                 => List()
  case (h1::t1, h2::t2) if h1 != h2 => s"$h1 -> $h2" :: diff(t1, t2)
  case (h1::t1, h2::t2)             => diff(t1, t2)
  case _                            => throw new RuntimeException("something went very wrong")
}

让我们检查一下:

scala> diff("a" :: HNil, 1 :: 2 :: HNil)
<console>:12: error: Cannot prove that shapeless.::[String,shapeless.HNil] =:= shapeless.::[Int,shapeless.::[Int,shapele
              diff("a" :: HNil, 1 :: 2 :: HNil)
                  ^

scala> diff("a" :: HNil, "b" :: HNil)
res5: List[String] = List(a -> b)

scala> diff("a" :: 1 :: HNil, "b" :: 2 :: HNil)
res6: List[String] = List(a -> b, 1 -> 2)

现在我们仍然可以“作弊”并将 H1 和 H2 显式设置为 HList,我们又回到了原点。

scala> diff[HList, HList]("a" :: HNil, 1 :: 2 :: HNil)
java.lang.RuntimeException: something went very wrong
  at .diff(<console>:15)
  at .diff(<console>:13)

不幸的是,我不认为这很容易解决(虽然确实如此,但我没有快速的解决方案)。

【讨论】:

【解决方案3】:

我可以提供一些更严格的变体,它不会被显式类型参数所欺骗。

object diff {
    class Differ[T <: HList](val diff: (T, T) => List[String])

    def apply[T <: HList](l1: T, l2: T)(implicit differ: Differ[T]): List[String] = differ.diff(l1, l2)

    implicit object NilDiff extends Differ[HNil]((_, _) => Nil)

    implicit def ConsDiff[H, T <: HList : Differ] = new Differ[H :: T]({
      case (h1 :: t1, h2 :: t2) if h1 != h2 => s"$h1 -> $h2" :: diff(t1, t2)
      case (h1 :: t1, h2 :: t2) => diff(t1, t2)
    })
  }

它肯定比上面一个复杂得多,我尝试使用Polymorphic function,但无法以编译正确的递归结束。

【讨论】:

    猜你喜欢
    • 2021-02-18
    • 2014-06-03
    • 2017-02-10
    • 1970-01-01
    • 2016-07-30
    • 2020-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多