如何从无形记录中提取标签以进行泛型类派生

时间:2016-08-22 10:23:18

标签: scala shapeless

我正在编写一个泛型类(DynamoDB的编解码器),用Shapeless派生代码。我有一个版本无需使用案例类的字段名称,完全基于类的字段顺序与DynamoDB响应中的属性顺序相匹配。它使用Generic和通常deriveHNilderiveHCons方法,例如,在此处描述:https://meta.plasm.us/posts/2015/11/08/type-classes-and-generic-derivation/

现在我想要一个使用字段名称来查找相关DynamoDB属性的版本。我目前的想法是主要重用上一个(基于订单)版本的方法,另外使编译器通过LabelledGenericshapeless.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的第一次认真尝试,我不知道我是否正确行事。我非常感谢关于这个特殊错误的反馈以及我的方法。

2 个答案:

答案 0 :(得分:1)

之前我遇到了同样的问题,但没有找到一种简单的方法来递归地将Keys HListGeneric 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项目中找到了一些。似乎使用WitnessLabelledGeneric可以直接访问字段名称。我提出了以下有效的版本:

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>