在Shapeless中,给定两条记录,如何要求两条记录具有相同的密钥并加入它们?

时间:2016-09-28 20:47:10

标签: scala shapeless

假设我有两条记录。一个可能是案例类的LabelledGeneric表示;而另一个可能是程序员提供的记录,提供人类可读的字段标签:

case class Book(author: String, title: String, quantity: Int)
val labels = ('author ->> "Author") :: ('title ->> "Title") :: ('quantity ->> "Quantity") :: HNil

有没有办法

  1. 要求Book的标记通用表示和labels的记录类型具有相同的键(或者至少label的键是{{1}的键的子集1}})和
  2. "加入"或者用键将它们拼接在一起,这样你就可以得到一个与左手参数相同的键的记录,其值是一对(lhs值,选项[rhs值])或类似的东西?
  3. 我认为这可能是合并为每一方提取Book见证,然后使用Keys。 (我很乐意将这一点添加到开箱即用的无形操作中。)这使我们可以关联"元数据"到一个类的字段(例如代替使用注释)。

1 个答案:

答案 0 :(得分:2)

我认为这有效,但我很乐意听到评论:

trait ZipByKey[L <: HList, R <: HList] extends DepFn2[L, R] {
  type Out <: HList
}

object ZipByKey {

  type Aux[L <: HList, R <: HList, O <: HList] = ZipByKey[L, R] { type Out = O }

  implicit def hnilZip[R <: HList] = new ZipByKey[HNil, R] { type Out = HNil; override def apply(l: HNil, r: R) = HNil }

  implicit def hlistZip[K, V, T <: HList, R <: HList, RV, Remainder <: HList, TO <: HList]
  (implicit
    remover: Remover.Aux[R, K, (RV, Remainder)],
    recurse: ZipByKey.Aux[T, Remainder, TO]
  ) = new ZipByKey[FieldType[K, V] :: T, R] {
    type Out = FieldType[K, (V, RV)] :: TO

    def apply(l: FieldType[K, V] :: T, r: R): Out = {
      val (rv, remainder) = remover.apply(r)
      val newValue = (l.head, rv)
      labelled.field[K](newValue) :: recurse.apply(l.tail, remainder)
    }
  }
}

使用示例:

  case class Book(author: String, title: String, quantity: Int)
  val labels = ('author ->> "Author") :: ('title ->> "Title") :: ('quantity ->> "Number Of") :: HNil

  val generic = LabelledGeneric[Book]

  def zipByKey[T, G <: HList, R <: HList, O <: HList](t: T, r: R)
    (implicit generic: LabelledGeneric.Aux[T, G],
      zipByKey: ZipByKey.Aux[G, R, O]): O = {
    zipByKey.apply(generic.to(t), r)
  }

  println(zipByKey(Book("Hello", "Foo", 3), labels))

打印出来

(Foo,Id) :: (Bar,Name) :: (3,Number Of) :: HNil

如果我们想要不允许所有密钥都显示在labels中,那么还有一些工作要做。但可能还有其他方法可以解决这个问题。