简体   繁体   中英

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.

Even if two classes share the same properties, their encoding may be different. The description for each field would contain some(4) values. 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

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:

::[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.

I have used this solution to combine TypeClasses for tuples:

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 ?


A Similar problem is solved in scodec . If you check a demo here: 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.

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. 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:

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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