将 List[OF[C]] 转换为 F[B],其中 case class B 的类型与 A 对齐
Transforming a Hlist[F[A]] into F[B] where case class B has aligned types with A
注:本人正在学习无形,如有遗漏请指正
背景:
我正在构建一个 encoding/decoding 固定长度格式的解决方案,同时练习 Shapeless。这个想法是每个 case class
都有自己的 encoder/decoder 定义为与其属性对齐的 HList
。
即使两个 classes 共享相同的属性,它们的编码也可能不同。每个字段的描述将包含一些 (4) 个值。但这在问题中并不重要。
问题:
我声明一个示例案例class及其编码器:
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]
,我希望它可以是上面的实现。
我已经使用这个解决方案来组合元组的类型类:
- Composing typeclasses for tuples in Scala
- https://www.scala-exercises.org/shapeless/auto_typeclass_derivation
但我得到:
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
列表可以转换为 FLEncoder
列表。由于我们处于类型级别,因此可以使用 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))
}
}
现在,在您的 `FLEncoder 对象中,添加此隐式 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))
}
这将允许您定义
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
,因为这仅用于从基本情况进行推断。
注:本人正在学习无形,如有遗漏请指正
背景:
我正在构建一个 encoding/decoding 固定长度格式的解决方案,同时练习 Shapeless。这个想法是每个 case class
都有自己的 encoder/decoder 定义为与其属性对齐的 HList
。
即使两个 classes 共享相同的属性,它们的编码也可能不同。每个字段的描述将包含一些 (4) 个值。但这在问题中并不重要。
问题:
我声明一个示例案例class及其编码器:
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]
,我希望它可以是上面的实现。
我已经使用这个解决方案来组合元组的类型类:
- Composing typeclasses for tuples in Scala
- https://www.scala-exercises.org/shapeless/auto_typeclass_derivation
但我得到:
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
列表可以转换为 FLEncoder
列表。由于我们处于类型级别,因此可以使用 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))
}
}
现在,在您的 `FLEncoder 对象中,添加此隐式 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))
}
这将允许您定义
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
,因为这仅用于从基本情况进行推断。