Shapeless HList 类型检查
Shapeless HList type checking
我正在使用 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
。我可以对类型参数做些什么来使这个方法只接受具有相同类型的两侧吗?
不幸的是,基础 HList
特征是未参数化的,因此在您的方法调用中 H
只是解析为 Hlist
(这确实是任何 [=15= 的超类型) ] 与具体元素类型无关)。
要解决这个问题,我们必须稍微更改定义,转而依赖通用类型约束:
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)
现在我们仍然可以 "cheat" 并将 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)
不幸的是,我不认为这很容易解决(虽然确实如此,但我没有快速的解决方案)。
我可以提供更严格的变体,不能用显式类型参数欺骗。
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 但无法以正确的递归编译结束。
其他答案没有真正解决的一件事是,这完全是一个类型推断问题,可以通过简单地将参数列表一分为二来解决:
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
来打破这一点,但恐怕这是您自己的事了。
我正在使用 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
。我可以对类型参数做些什么来使这个方法只接受具有相同类型的两侧吗?
不幸的是,基础 HList
特征是未参数化的,因此在您的方法调用中 H
只是解析为 Hlist
(这确实是任何 [=15= 的超类型) ] 与具体元素类型无关)。
要解决这个问题,我们必须稍微更改定义,转而依赖通用类型约束:
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)
现在我们仍然可以 "cheat" 并将 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)
不幸的是,我不认为这很容易解决(虽然确实如此,但我没有快速的解决方案)。
我可以提供更严格的变体,不能用显式类型参数欺骗。
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 但无法以正确的递归编译结束。
其他答案没有真正解决的一件事是,这完全是一个类型推断问题,可以通过简单地将参数列表一分为二来解决:
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
来打破这一点,但恐怕这是您自己的事了。