Как определить тип HList, но на основе другого типа HList

Предположим, у меня есть класс case:

case class Foo(num: Int, str: String, bool: Boolean)

Теперь у меня также есть простая оболочка:

sealed trait Wrapper[T]
case class Wrapped[T](value: T) extends Wrapper[T]

(и некоторые другие реализации Wrapper, которые здесь не важны)

Я могу использовать Generic[Foo] для получения Aux, представляющего этот класс case: val genFoo = Generic[Foo] (в моем реальном коде я использую LabelledGeneric, чтобы не потерять имена полей)

Это дает мне тип, представляющий определение списка HList:

Generic.Aux[Foo, Int :: String :: Boolean :: HNil]

(при использовании с LabelledGeneric определение намного сложнее, но по сути то же самое)

Теперь я хочу создать определение типа для HList, которое вместо необработанных типов включает в себя обернутые типы. Пример:

type WrappedHlist = Wrapper[Int] :: Wrapper[String] :: Wrapper[Boolean] :: HNil

Затем я могу использовать это определение типа, например, для создания кодировщика/декодера Circe (я предоставил необходимый кодировщик/декодер для типа Wrapper).

Вся необходимая информация существует во время компиляции, так как определение второго HList легко определяется из первого. Прямо сейчас я могу добиться этого, написав определение вручную или повторив определение case-класса Foo с избыточной версией, где все определено как Wrappers. Как я могу создать определение, которое не требует от меня повторения всего?


person fluffysheap    schedule 08.12.2020    source источник


Ответы (2)


Что ж, это выполнимо с небольшим количеством классов типов, типов, зависящих от пути, и шаблона Aux:

trait WrapperHelper[In] {
  type Out
  def wrap(i: In): Out
}
object WrapperHelper {
  type Aux[I, O] = WrapperHelper[I] { type Out = O }

  implicit val nilWH: WrapperHelper.Aux[HNil, HNil] = new WrapperHelper[HNil] {
    type Out = HNil
    def wrap(i: HNil): HNil = i
  }

  implicit def hconsWH[H, TI <: HList, TO <: HList](
    implicit
    tailWH: WrapperHelper.Aux[TI, TO]
  ): WrapperHelper.Aux[H :: TI, Wrapper[H] :: TO] = new WrapperHelper[H :: TI] {
    type Out = Wrapper[H] :: TO
    def wrap(i: H :: TI): Wrapper[H] :: TO = i match {
      case head :: tail => Wrapped(head) :: tailWH.wrap(tail)
    }
  }
}

def wrap[I](i: I)(implicit wh: WrapperHelper[I]): wh.Out = wh.wrap(i)

HNIl рассматривается как частный случай, когда In = Out. Для всего остального... вам все равно придется отображать тип рекурсивно. Хотя это и некрасиво, это должно делать то, что вы хотите:

@ wrap(Generic[Foo].to(Foo(1, "", true)))
res7: Wrapper[Int] :: Wrapper[String] :: Wrapper[Boolean] :: HNil = Wrapped(1) :: Wrapped("") :: Wrapped(true) :: HNil

Предполагая, что вы хотите предоставить некоторую пользовательскую логику кодирования/декодирования, вы должны изменить подпись

implicit def hconsWH[H, TI <: HList, TO <: HList](
  implicit
  tailWH: WrapperHelper.Aux[TI, TO]
): WrapperHelper.Aux[H :: TI, Wrapper[H] :: TO]

к чему-то вроде

implicit def hconsWH[H, TI <: HList, TO <: HList](
  implicit
  thatThingINeedToLiftAToWrapperA: Encoder[A], // whatever is needed to lift A => Wrapper[A]
  tailWH: WrapperHelper.Aux[TI, TO]
): WrapperHelper.Aux[H :: TI, Wrapper[H] :: TO]

Даже если вам не нужна реализация, все же полезно определить класс типов здесь, просто чтобы предоставить некоторые неявные доказательства, которые разрешат тип для вас.

person Mateusz Kubuszok    schedule 08.12.2020

HList Int :: String :: Boolean :: HNil можно легко преобразовать в HList Wrapper[Int] :: Wrapper[String] :: Wrapper[Boolean] :: HNil с помощью класса стандартного типа shapeless.ops.hlist.Mapped

implicitly[Mapped.Aux[Int :: String :: Boolean :: HNil, 
  Wrapper, 
  Wrapper[Int] :: Wrapper[String] :: Wrapper[Boolean] :: HNil]] // compiles
person Dmytro Mitin    schedule 13.12.2020