2016-06-23 8 views
2

Ich möchte eine Aktualisierungsinstanz einer Klasse in eine Basisinstanz zusammenführen und das Feld der Aktualisierungsinstanz über die Basisinstanz auswählen, wenn dieses Feld in der Basis "leer" ist Beispiel. Das Beispiel verschmilzt unter base und update:Verwenden Sie formlos, um zwei Instanzen derselben Fallklasse zusammenzuführen

case class Foo(a: Option[Int], b: List[Int], c: Option[Int]) 

val base = Foo(None, Nil, Some(0)) 
val update = Foo(Some(3), List(4), None) 

merge(base,update) == Foo(Some(3), List(4), Some(0)) 

ich so etwas wie dies versucht habe:

val g = Generic[Foo] 
val aHList = g.to(base) 
val bHList = g.to(update) 

aHList 
    .zipWithIndex 
    .map({ 
    case (l: List, i: Int) => l ++ bHList(i) 
    case (o: Option, i: Int) => if (!bHList(i).nonEmpty) { 
     updateHList(i) 
    } else { 
     o 
    } 
    case (_, i: Int) => updateHList(i) 
    }) 

Aber es stellt sich heraus, die generische .to Methode nicht ausgegeben ein HList pro sagen, aber ein Repr. Irgendeine Idee, wie ich mein Ziel erreichen könnte?

Vielen Dank!

Antwort

5

Für bestimmte Aufgaben wie diese ist es meist einfacher, eine Typklasse zu erstellen, als mit einem map mit Poly zu experimentieren.

Wir können ein Update auf einige T wie folgt darstellen:

trait Update[T] { 
    def apply(base: T, update: T): T 
} 

Jetzt müssen wir einige Beispiele definieren. Wie aktualisiert man eine List und Option und einige Instanzen, um eine Instanz Update[Foo] ableiten zu können.

import shapeless._ 

object Update extends Update0 { 
    def apply[A](implicit update: Lazy[Update[A]]): Update[A] = update.value 

    implicit def optionUpdate[A]: Update[Option[A]] = 
    new Update[Option[A]] { 
     def apply(base: Option[A], update: Option[A]): Option[A] = update orElse base 
    } 

    implicit def listUpdate[A]: Update[List[A]] = 
    new Update[List[A]] { 
     def apply(base: List[A], update: List[A]): List[A] = base ++ update 
    } 

    implicit def hnilUpdate: Update[HNil] = 
    new Update[HNil] { 
     def apply(base: HNil, update: HNil): HNil = HNil 
    } 

    implicit def hconsUpdate[H, T <: HList](
    implicit updateH: Update[H], updateT: Lazy[Update[T]] 
): Update[H :: T] = 
    new Update[H :: T] { 
     def apply(base: H :: T, update: H :: T): H :: T = 
     updateH(base.head, update.head) :: updateT.value(base.tail, update.tail) 
    } 
} 

trait Update0 { 
    implicit def genericUpdate[A, G <: HList](
    implicit gen: Generic.Aux[A, G], updateG: Lazy[Update[G]] 
): Update[A] = 
    new Update[A] { 
     def apply(base: A, update: A): A = 
     gen.from(updateG.value(gen.to(base), gen.to(update))) 
    } 
} 

Wir einige Syntax hinzufügen, kann es ein wenig leichter zu machen:

implicit class UpdateOps[A](val base: A) extends AnyVal { 
    def update(change: A)(implicit update: Lazy[Update[A]]): A = 
    update.value(base, change) 
} 

Jetzt können wir tun:

case class Foo(a: Option[Int], b: List[Int], c: Option[Int]) 

val base = Foo(None, Nil, Some(0)) 
val update = Foo(Some(3), List(4), None) 

base update update // Foo(Some(3),List(4),Some(0)) 

Wir für cats.SemigroupK eine Instanz definieren könnte oder scalaz.Plus so können wir die Option und List Instanc weglassen es, während gewinnt zB Update[Vector[Int]]:

import cats.SemigroupK 
import cats.implicits._ 

implicit def semigroupKUpdate[F[_], A](implicit F: SemigroupK[F]): Update[F[A]] = 
    new Update[F[A]] { 
    def apply(base: F[A], update: F[A]): F[A] = F.combineK(update, base) 
    } 
+0

Schöne Lösung, thx! Warum setzen Sie 'genericUpdate' in eine andere Eigenschaft? Warum nicht im Update? –

+0

Wie es ist, sollte es in "Update" in Ordnung sein. Wenn Sie jedoch beispielsweise ein benutzerdefiniertes 'Update [(A, B)]' erstellen möchten, muss das 'genericUpdate' in einem' Merkmal' stehen, da Sie andernfalls mehrdeutige Implikate erhalten würden. –

+0

Wie beim Setup habe ich einen Kompilierfehler, es kann nicht das 'Update [Foo]' implizit finden. Ich habe das 'Update'-Merkmal und -Objekt in einer Datei. Und die 'UpdateOps [A]' in einem Paketobjekt. Ich importiere sowohl das Paketobjekt als auch den Inhalt des 'Update'-Objekts. Sieht so aus, als würde es das 'GenericUpdate' nicht finden. Auf einer anderen Anmerkung, was ist "G" für "genericUpdate"? –

2

können Sie verwenden Poly und map dieses Problem in sehr allgemeiner Art und Weise zu lösen. Ich denke, die Lösung ist ziemlich elegant.

Edit: herausgefunden, wie None und Nil Objekttypen in Tuples mit einem Poly eingebettet werden. Hinzugefügt und vereinfacht den Code.