将Hlist [F [A]]转换为F [B],其中案例类B与A对齐类型

时间:2017-06-06 16:15:09

标签: scala typeclass shapeless fixed-width scodec

注意:我正在学习无形,所以如果我遗漏任何细节,请要求澄清。

背景:

我正在构建一个固定长度格式的编码/解码解决方案,同时练习无形。我们的想法是每个^(?!.*\.result.med$).*\.med$都有自己的编码器/解码器,定义为与其属性对齐的case class

即使两个类共享相同的属性,它们的编码也可能不同。每个字段的描述将包含一些(4)值。但这在问题上并不重要。

问题:

完整代码可在此处找到:https://github.com/atais/Fixed-Length/blob/03b395947a6b00e548ea1e76a9660e471f136565/src/main/scala/test/Main.scala

我声明了一个示例案例类及其编码器:

HList

所以case class Employee(name: String, number: Int, manager: Boolean) object Employee { implicit val employeeEncoder = FLEncoder.fixed((s: String) => s) :: FLEncoder.fixed((s: String) => s) :: FLEncoder.fixed((s: Int) => s.toString) :: FLEncoder.fixed((s: Boolean) => s.toString) :: HNil } 的类型很漂亮:

employeeEncoder

现在,编码器::[FLEncoder[String], ::[FLEncoder[String], ::[FLEncoder[Int], ::[FLEncoder[Boolean], HNil]]]] 正在寻找implicitly,我希望这可能是上述实现。

我已经使用此解决方案将TypeClasses组合为元组:

但我得到了:

FLEncoder[Employee]

如果我单独声明这些编码器,它们工作正常

Error:(69, 17) could not find implicit value for parameter enc: test.FLEncoder[test.Employee]
  println(encode(example))

问题:

基本上,如何使用implicit val a = fixed((s: String) => s) implicit val b = fixed((s: Int) => s.toString) implicit val c = fixed((s: Boolean) => s.toString) ,以便知道此Shapeless是对齐Hlist的编码器类型?

在scodec 中解决了类似的问题。如果您在此处查看演示: https://github.com/atais/Fixed-Length/blob/03b395947a6b00e548ea1e76a9660e471f136565/src/main/scala/test/Fixed.scala

你可以做这样的转变:

case class

但我不知道如何在我的案例中使用TransformSyntax.as

1 个答案:

答案 0 :(得分:2)

您需要的是告诉编译器您的FLEncoder列表可以转换为FLEncoder列表。由于我们处于类型级别,因此可以使用类型类来完成:

trait ListOfEncoder[L <: HList] {
  type Inside <: HList
  def merge(l: L): FLEncoder[Inside]
}

object ListOfEncoder {
  type Aux[L <: HList, I <: HList] = ListOfEncoder[L] { type Inside = I }

  implicit val hnil: Aux[HNil, HNil] = new ListOfEncoder[HNil] {
    type Inside = HNil
    def merge(l: HNil) = FLEncoder.fixed(_ => "")
  }

  implicit def hcons[H, T <: HList](implicit T: ListOfEncoder[T]): Aux[FLEncoder[H] :: T, H :: T.Inside] = new ListOfEncoder[FLEncoder[H] :: T] {
    type Inside = H :: T.Inside
    def merge(l: FLEncoder[H] :: T): FLEncoder[H :: T.Inside] =
      FLEncoder.fixed((ht: H :: T.Inside) => l.head.encode(ht.head) + T.merge(l.tail).encode(ht.tail))
  }
}

现在,在你的`FLEncoder对象中,添加这个隐式类:

implicit class EncoderAsGeneric[L <: HList, I <: HList](l: L)(implicit L: ListOfEncoder.Aux[L, I]) {
  def as[E](implicit gen: Generic.Aux[E, I]) = 
    FLEncoder.fixed((e: E) => L.merge(l).encode(gen.to(e))
}

这将允许您定义

implicit val employeeEncoder = (fixed((s: String) => s) ::
  fixed((s: String) => s) ::
  fixed((s: Int) => s.toString) ::
  fixed((s: Boolean) => s.toString) ::
    HNil).as[Employee]

现在,所有隐含都应该在Main范围内。

顺便说一句,由于您使用HList定义了FLEncoder,因此不再需要ProductTypeClassCompanion,因为这仅适用于基本情况的推断。