简体   繁体   English

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

[英]Transforming a Hlist[F[A]] into F[B] where case class B has aligned types with A

Note: I am learning shapeless, so please ask for clarification if I miss any details. 注意:我正在学习无形,所以如果我错过任何细节,请要求澄清。

Background: 背景:

I am building an encoding/decoding solution for fixed-length format while practising Shapeless. 我正在构建一个固定长度格式的编码/解码解决方案,同时练习无形。 The idea is that each case class would have its own encoder/decoder defined as an HList aligned with its properties. 这个想法是每个case class都有自己的编码器/解码器定义为与其属性对齐的HList

Even if two classes share the same properties, their encoding may be different. 即使两个类共享相同的属性,它们的编码也可能不同。 The description for each field would contain some(4) values. 每个字段的描述将包含一些(4)值。 But this is not important in the problem. 但这在问题上并不重要。

Problem: 问题:

The full code is available here: https://github.com/atais/Fixed-Length/blob/03b395947a6b00e548ea1e76a9660e471f136565/src/main/scala/test/Main.scala 完整代码可在此处获取: https//github.com/atais/Fixed-Length/blob/03b395947a6b00e548ea1e76a9660e471f136565/src/main/scala/test/Main.scala

I declare a sample case class and its encoder: 我声明了一个示例案例类及其编码器:

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
}

so the type of my employeeEncoder is a beautiful: 所以我的employeeEncoder的类型很漂亮:

::[FLEncoder[String], ::[FLEncoder[String], ::[FLEncoder[Int], ::[FLEncoder[Boolean], HNil]]]]

Now, encoder implicitly is looking for an FLEncoder[Employee] , which I hope could be the above implementation. 现在,编码器implicitly正在寻找一个FLEncoder[Employee] ,我希望它可以是上面的实现。

I have used this solution to combine TypeClasses for tuples: 我使用此解决方案将TypeClasses组合为元组:

But I am getting: 但我得到:

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

If I declare those encoders separately, they are working fine 如果我单独声明这些编码器,它们工作正常

implicit val a = fixed((s: String) => s)
implicit val b = fixed((s: Int) => s.toString)
implicit val c = fixed((s: Boolean) => s.toString)

Question: 题:

So basically, how to use Shapeless so it would know that this Hlist is an encoder type for an aligned case class ? 所以基本上,如何使用Shapeless所以它会知道这个Hlist是一个对齐的case class的编码器类型?


A Similar problem is solved in scodec . 在scodec中解决了类似的问题 If you check a demo here: https://github.com/atais/Fixed-Length/blob/03b395947a6b00e548ea1e76a9660e471f136565/src/main/scala/test/Fixed.scala 如果您在此处查看演示: https//github.com/atais/Fixed-Length/blob/03b395947a6b00e548ea1e76a9660e471f136565/src/main/scala/test/Fixed.scala

you are able to do such transformation: 你能够做这样的转变:

case class Person(name: String, age: Int)

val pc: Codec[shapeless.::[String, shapeless.::[Int, HNil]]] = (("name" | fixed(10, '_')) :: ("age" | fixed(6, '0').narrow[Int](strToInt, _.toString)))

val personCodec: Codec[Person] = pc.as[Person]

But I do not know how could I use TransformSyntax.as in my case. 但我不知道在我的情况下如何使用TransformSyntax.as

What you need is to give a way to tell the compiler that your list of FLEncoder can be converted to a FLEncoder of list. 你需要的是给予的方式告诉你的列表中的编译器FLEncoder可以被转换成一个FLEncoder名单。 Since we are on the type level, this can be done with a typeclass: 由于我们处于类型级别,因此可以使用类型类来完成:

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))
  }
}

Now, in your `FLEncoder object, add this implicit class: 现在,在你的`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))
}

This will allow you to define 这将允许您定义

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

And now, all the implicits should be in scope for your Main as it is. 而现在,所有的暗示应该在你的Main范围内。

By the way, since you define your FLEncoder with HList , there is no need for ProductTypeClassCompanion anymore, since this is only for inference from base cases. 顺便说一下,由于您使用HList定义了HList ,因此不再需要ProductTypeClassCompanion ,因为这仅适用于基本情况的推断。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM