2016-09-28 2 views
2

Angenommen, ich habe zwei Datensätze. Eine könnte die LabelledGeneric Darstellung einer Fallklasse sein; während der andere einen Programmierer bereitgestellte Datensatz sein könnten, die für Menschen lesbaren Bereich Etiketten liefert:In Shapeless, zwei Datensätze vorausgesetzt, wie benötige ich, dass beide Datensätze die gleichen Schlüssel haben und sich ihnen anschließen?

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

Gibt es eine Möglichkeit zu

  1. verlangt, dass die markierte generische Darstellung von Book und der Satzart von labels besitzen die gleichen Schlüssel (oder mindestens die Schlüssel von label sind eine Teilmenge der Schlüssel von Book) und
  2. "verbinden" oder zip sie zusammen durch Schlüssel, so dass Sie einen Datensatz mit den gleichen Schlüsseln wie der linke herausbekommen Argument, wobei die Werte ein Paar sind (lhs value e, Option [rhs-Wert]) oder so ähnlich?

Ich denke, das mit einer Kombination machbar sein könnte für jede Seite die Keys Zeuge zu extrahieren, dann Align verwenden. (Ich würde gerne sehen, dass dies den out-of-the-box-formlosen Operationen hinzugefügt wird.) Dies ermöglicht es uns, den Feldern einer Klasse "Metadaten" zuzuordnen (anstatt zum Beispiel Annotationen zu verwenden).

Antwort

2

Ich denke, das funktioniert, aber ich würde gerne Kommentare hören:

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) 
    } 
    } 
} 

Beispiel Nutzung:

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)) 

druckt

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

Wenn wir nicht alle zulassen möchten Schlüssel in labels erscheinen dann gibt es ein bisschen mehr Arbeit zu tun. Aber es könnte andere Wege geben, damit umzugehen.

+0

es kommt mir vor, die Verwendung von 'Remover', um gebrauchte Schlüssel aus' R' zu eliminieren, ist wahrscheinlich nicht notwendig. Ich habe es von einigen anderen Ops abgeholt. Ich frage mich, ob es eine Art Leistungszeitgewinn bei der Kompilierung gibt? Ich werde wahrscheinlich versuchen, es in einer Bearbeitung zu entfernen. – tksfz

+0

Jetzt ist mir klar, dass die Verwendung von 'Remover' zur Eliminierung verbrauchter Schlüssel aus' R' nützlich ist, um den "Outer Join" zu implementieren: Sobald wir 'R = HNil' haben, können wir einfach den Rest von' L' anhängen – tksfz

+0

Ihre Herangehensweise ist großartig! –

Verwandte Themen