我正在编写一个泛型类(DynamoDB的编解码器),用Shapeless派生代码。我有一个版本无需使用案例类的字段名称,完全基于类的字段顺序与DynamoDB响应中的属性顺序相匹配。它使用Generic
和通常deriveHNil
,deriveHCons
方法,例如,在此处描述:https://meta.plasm.us/posts/2015/11/08/type-classes-and-generic-derivation/。
现在我想要一个使用字段名称来查找相关DynamoDB属性的版本。我目前的想法是主要重用上一个(基于订单)版本的方法,另外使编译器通过LabelledGeneric
和shapeless.ops.record.Keys
提供字段名称。但是我仍然坚持如何正确使用Keys
功能。
这个想法如下:函数hconsDecoder
应该一次做2件事:解构HList
以在其头部+尾部运行decode
操作,并从中提取标签上述头脑。 LabelledGeneric
应在HList
上为字段提供标签,以便H
中的hconsDecoder
类型参数将成为记录中的条目,其中包含相关信息。但由于Keys
仅适用于HList
,因此我会创建一个单身H :: HNil
来运行Keys
。
以下是我所拥有的代码部分:
trait FieldDecoder[A] {
def decode(a: AttributeValue): Option[A]
}
trait RecordDecoder[A] {
def decode(s: Seq[Attribute]): Option[A]
}
object RecordDecoderInstances {
implicit val hnilDecoder = new RecordDecoder[HNil] {
override def decode(s: Seq[Attribute]): Option[HNil] = {
Some(HNil)
}
}
object toName extends Poly1 {
implicit def keyToName[A] = at[Symbol with A](_.name)
}
implicit def hconsDecoder[H: FieldDecoder, T <: HList: RecordDecoder](
implicit kk: Keys[H :: HNil]#Out,
m: Mapper[toName.type, Keys[H :: HNil]#Out]) =
new RecordDecoder[H :: T] {
override def decode(s: Seq[Attribute]): Option[H :: T] = {
val attrName = (kk map toName).head.asInstanceOf[String] // compile error here
for {
h <- implicitly[FieldDecoder[H]]
.decode(s.filter(_.name == attrName).head.value)
t <- implicitly[RecordDecoder[T]]
.decode(s.filterNot(_.name == attrName))
} yield h :: t
}
}
}
鉴于此代码,编译器错误如下:could not find implicit value for parameter c: shapeless.ops.hlist.IsHCons[m.Out]
。我尝试过相同的不同版本,总是遇到implicit not found
错误的一些变化。最重要的是,Keys
由于某种原因无法与H :: HNil
构造一起使用。
这是我对Shapeless的第一次认真尝试,我不知道我是否正确行事。我非常感谢关于这个特殊错误的反馈以及我的方法。
答案 0 :(得分:1)
之前我遇到了同样的问题,但没有找到一种简单的方法来递归地将Keys
HList
与Generic
HList
对齐。我希望有人会发布更好的解决方案。
一个简单的解决方案是在递归处理之前将输入序列与键对齐。为清楚起见,我已将seq处理与通用HList
表示的处理分开。
case class AttributeValue(value: String)
case class Attribute(name: String, value: AttributeValue)
trait FieldDecoder[T] {
def decode(a: AttributeValue): Option[T]
}
HList
解码器:
trait HListDecoder[A <: HList] {
def decode(s: Seq[Attribute]): Option[A]
}
object HListDecoder {
implicit val hnilDecoder = new HListDecoder[HNil] {
override def decode(s: Seq[Attribute]): Option[HNil] = {
Some(HNil)
}
}
implicit def hconsDecoder[H, T <: HList, LR <: HList](
implicit
fieldDecoder: FieldDecoder[H],
tailDecoder: HListDecoder[T]) =
new HListDecoder[H :: T] {
override def decode(s: Seq[Attribute]): Option[H :: T] = {
for {
h <- fieldDecoder.decode(s.head.value)
t <- tailDecoder.decode(s.tail)
} yield h :: t
}
}
}
案例类解码器:
trait RecordDecoder[A] {
def decode(s: Seq[Attribute]): Option[A]
}
object RecordDecoder {
object toName extends Poly1 {
implicit def keyToName[A] = at[Symbol with A](_.name)
}
def sortByKeys(s: Seq[Attribute], keys: Seq[String]): Seq[Attribute] =
keys.flatMap(key => s.filter(_.name == key))
implicit def recordDecoder[A, R <: HList, LR <: HList, K <: HList, KL <: HList](
implicit
gen: Generic.Aux[A, R],
lgen: LabelledGeneric.Aux[A, LR],
kk: Keys.Aux[LR, K],
m: Mapper.Aux[toName.type, K, KL],
toSeq: ToTraversable.Aux[KL, Seq, String],
genDecoder: HListDecoder[R]): RecordDecoder[A] =
new RecordDecoder[A] {
def decode(s: Seq[Attribute]) = {
val keys = kk.apply.map(toName).to[Seq]
val attrs = sortByKeys(s, keys)
genDecoder.decode(attrs).map(gen.from _)
}
}
def apply[A](s: Seq[Attribute])(implicit decoder: RecordDecoder[A]) =
decoder.decode(s)
}
测试用例:
implicit val stringDecoder = new FieldDecoder[String] {
override def decode(a: AttributeValue): Option[String] = Some(a.value)
}
implicit val intDecoder = new FieldDecoder[Int] {
override def decode(a: AttributeValue): Option[Int] = Some(a.value.toInt)
}
val attrs = Seq(
Attribute("a", new AttributeValue("a")),
Attribute("c", new AttributeValue("2")),
Attribute("b", new AttributeValue("b")))
case class Test(b: String, a: String, c: Int)
println(RecordDecoder[Test](attrs))
// Some(Test(b,a,2))
答案 1 :(得分:1)
我正在清除Github的灵感,并在Frameless项目中找到了一些。似乎使用Witness
和LabelledGeneric
可以直接访问字段名称。我提出了以下有效的版本:
trait Decoder[A] {
def decode(s: Seq[Attribute]): Option[A]
}
object Decoder {
implicit val hnilDecoder = new Decoder[HNil] {
override def decode(s: Seq[Attribute]): Option[HNil] = {
Some(HNil)
}
}
implicit def keyedHconsDecoder[K <: Symbol, H, T <: HList](
implicit key: Witness.Aux[K],
head: FieldDecoder[H],
tail: Decoder[T]
): Decoder[FieldType[K, H] :: T] =
new Decoder[FieldType[K, H] :: T] {
def decode(s: Seq[Attribute]) = {
val fieldName = key.value.name
for {
head <- head.decode(s, fieldName)
tail <- tail.decode(s)
} yield labelled.field[K](head) :: tail
}
}
implicit def caseClassDecoder[A, R <: HList](
implicit gen: LabelledGeneric.Aux[A, R],
reprDecoder: Lazy[Decoder[R]],
ct: ClassTag[A]): Decoder[A] =
new Decoder[A] {
override def decode(s: Seq[Attribute]): Option[A] = {
println(s"record decode case class ${ct.runtimeClass.getSimpleName}")
reprDecoder.value.decode(s).map(gen.from)
}
}
def apply[A](s: Seq[Attribute])(implicit decoder: Decoder[A],
ct: ClassTag[A]): Option[A] = {
println(s"start decoding for ${ct.runtimeClass}")
decoder.decode(s)
}
}
(为简洁省略FieldDecoder
)
还需要将HCons解码器(keyedHconsDecoder
)中的返回类型从Decoder[H :: T]
调整为Decoder[[FieldType[K, H] :: T]
,因为我们在这里处理LabelledGeneric
。< / p>