Тестирование двух бесформенных типов HList в scala на эквивалентность через неявные

Мне интересно проверить, являются ли две гетерогенные записи HList "эквивалентными"; то есть они имеют одинаковые пары ключ / вал, но не обязательно в том же порядке. Есть предопределенный предикат типа, который делает то, что 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
}

2 ответа

Решение

Я верю Align[M,L] Класс typeclass поддерживает то, что вы хотите, он позволяет вам переставлять элементы одного списка в соответствии с порядком другого с теми же типами.

Вот функция, которая, я думаю, делает то, что вы хотите. Он скажет вам, если два эквивалентных списка имеют одинаковые значения для каждого типа. Если два списка не имеют одинаковые типы, он не скомпилируется.

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 работает: он просто перебирает один список и вытаскивает первый элемент другого с тем же типом. Но если вы используете литералы с одинарной типизацией, это не должно быть проблемой.

Так что это работает с вышеуказанными записями, по крайней мере, в отношении ключей:

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
def f(hr1: H1 <: HList, hr2 : H2 <: HList)(implicit equiv: H1 =:= H2) = {
  // biz logic
}

Я считаю, что это должно делать то, что вы хотите, вы пробовали это?

Другие вопросы по тегам