Reputation: 2970
Suppose I have two records. One might be the LabelledGeneric
representation of a case class; while the other could be a programmer-supplied record that supplies human-readable field labels:
case class Book(author: String, title: String, quantity: Int)
val labels = ('author ->> "Author") :: ('title ->> "Title") :: ('quantity ->> "Quantity") :: HNil
Is there a way to
Book
and the record type of labels
possess the same keys (or at least the keys of label
are a subset of the keys of Book
) andI think this might be doable with a combination of extracting the Keys
witness for each side, then using Align
. (I'd love to see this added to the out-of-the-box shapeless ops.) This allows us to associate "metadata" to the fields of a class (in lieu of using annotations for example).
Upvotes: 2
Views: 430
Reputation: 2970
I think this works but I'd love to hear comments:
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)
}
}
}
Example usage:
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))
prints out
(Foo,Id) :: (Bar,Name) :: (3,Number Of) :: HNil
If we want to allow not all keys to appear in labels
then there's a bit more work to do. But there might be other ways to handle that.
Upvotes: 2