通过隐式测试两个 Scala 无形 HList 类型的等价性

Test two scala shapeless HList types for equivalence via implicit

我有兴趣测试两个HList异构记录是否"equivalent";也就是说,它们具有相同的 key/val 对,但顺序不一定相同。是否有一个预定义的类型谓词可以执行下面代码片段中 EquivHLists 的操作?

// shapeless heterogeneous records with "equivalent" types.
// these should compile if given as the arguments to 'f' below.
val hrec1 = ("a" ->> 1) :: ("b" ->> 2) :: HNil
val hrec2 = ("b" ->> 2) :: ("a" ->> 1) :: HNil

// only compiles if two HList records contain same information
def f(hr1: H1 <: HList, hr2 : H2 <: HList)(implicit equiv: EquivHLists[H1, H2]) = {
  // biz logic
}
def f(hr1: H1 <: HList, hr2 : H2 <: HList)(implicit equiv: H1 =:= H2) = {
  // biz logic
}

我相信这应该做你想要的,你试过了吗?

我相信 Align[M,L] 类型类支持您想要的,它允许您重新排列一个 hlist 的元素以匹配另一个具有相同类型的顺序。

这是我认为可以满足您要求的函数。它会告诉您两个等效的 hlist 是否对每种类型具有相同的值。如果两个列表的类型不同,将无法编译。

import shapeless._
import ops.hlist._

def equiv[H <: HList, L <: HList]
  (h : H, l : L)(implicit align: Align[H, L]): Boolean = align(h) == l

scala> equiv(3 :: "hello" :: HNil, "hello" :: 3 :: HNil)
res11: Boolean = true

scala> equiv(4 :: "hello" :: HNil, "hello" :: 3 :: HNil)
res12: Boolean = false

scala> equiv(4 :: "hello" :: HNil, "hello" :: 3.0 :: HNil)
<console>:19: error: could not find implicit value for parameter align: shapeless.ops.hlist.Align[Int :: String :: shapeless.HNil,String :: Double :: shapeless.HNil]

edit :经过进一步实验,如果 hlists 具有多个相同类型的值,这将给出假阴性:

scala> equiv(3 :: "hello" :: 4 :: HNil, 4 :: "hello" :: 3 :: HNil)
res14: Boolean = false

这是因为 Align 的工作方式:它基本上只是遍历一个 hlist 并提取另一个具有相同类型的第一个元素。但是,如果您使用单例类型文字,那么这应该不是问题。

所以这个 确实 至少在键方面与上述记录一起工作:

scala> equiv(hrec1, hrec2)
res16: Boolean = true

//change one of the keys
scala> val hrec3 = ("c" ->> 2) :: ("a" ->> 1) :: HNil
hrec3: Int with shapeless.labelled.KeyTag[String("c"),Int] :: Int with shapeless.labelled.KeyTag[String("a"),Int] :: shapeless.HNil = 2 :: 1 :: HNil

scala> equiv(hrec1, hrec3)
<console>:27: error: could not find implicit value for parameter align ...

//change one of the values, it compiles but returns false
scala> val hrec4 = ("b" ->> 2) :: ("a" ->> 3) :: HNil
hrec4: Int with shapeless.labelled.KeyTag[String("b"),Int] :: Int with shapeless.labelled.KeyTag[String("a"),Int] :: shapeless.HNil = 2 :: 3 :: HNil

scala> equiv(hrec1, hrec4)
res18: Boolean = false