【问题标题】:Transforming a Hlist[F[A]] into F[B] where case class B has aligned types with A将 List[OF[C]] 转换为 F[B],其中案例类 B 与 A 对齐类型
【发布时间】:2017-06-23 20:54:00
【问题描述】:

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

背景:

在练习 Shapeless 时,我正在为固定长度格式构建编码/解码解决方案。这个想法是每个case class 都有自己的编码器/解码器,定义为与其属性对齐的HList

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

问题:

完整代码在这里:https://github.com/atais/Fixed-Length/blob/03b395947a6b00e548ea1e76a9660e471f136565/src/main/scala/test/Main.scala

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

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 正在寻找FLEncoder[Employee],我希望它可以是上述实现。

我已使用此解决方案为元组组合 TypeClass:

但我得到了:

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 是对齐case class 的编码器类型?


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

你可以做这样的转变:

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]

但我不知道在我的情况下如何使用TransformSyntax.as

【问题讨论】:

  • 你能给我们FLEncoder[T]的特征,以便更好地了解你想要实现的目标吗?同时,请查看stackoverflow.com/questions/44358332/…,其中给出了完整的示例(其中一部分在我的解决方案中)。
  • 编码器在Main.scala的GitHub上封闭

标签: scala typeclass shapeless fixed-width scodec


【解决方案1】:

您需要提供一种方法来告诉编译器您的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,因为这仅用于从基本情况进行推断。

【讨论】:

  • 我添加了你的对象并导入了隐式,但我仍然得到同样的错误。你能分叉我的Main.scala 并给出一个可行的解决方案吗?
  • 您仍然错过的是通用转换(从您的HListEmployee)。我会 PR 你并将更新的答案放在这里。
  • 你知道为什么编译器不知道我生成的 Hlist 不等于 Shapeless 将创建的吗?为什么它没有发现它是一个隐含的值?
  • shapeless 永远不会产生 F[A] :: F[B] :: HNil,但会产生 F[A :: B :: HNil],当您考虑它时会完全不同。
猜你喜欢
  • 2022-11-07
  • 1970-01-01
  • 1970-01-01
  • 2019-10-23
  • 1970-01-01
  • 2011-12-03
  • 1970-01-01
  • 2011-10-06
  • 2020-12-25
相关资源
最近更新 更多