I'm interested in testing whether two HList heterogeneous records are "equivalent"; that is, they have the same key/val pairs, but not necessarily in the same order. Is there a predefined type predicate that does what EquivHLists
does in the code fragment below?
// 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
}
I believe the Align[M,L]
typeclass supports what you want, it lets you rearrange the elements of one hlist to match the order of another with the same types.
Here's a function that I think does what you want. It will tell you if two equivalent hlists have the same values for each type. If the two lists don't have the same types, it won't compile.
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 : after some further experimentation, this will give false negatives if the hlists have more than one value of the same type:
scala> equiv(3 :: "hello" :: 4 :: HNil, 4 :: "hello" :: 3 :: HNil)
res14: Boolean = false
This is because of the way Align
works: it basically just iterates over one hlist and pulls out the first element of the other with the same type. But if you're using singleton-typed literals then this shouldn't be an issue.
So this does work with the above records at least in regards to the keys:
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