Last active
December 23, 2021 12:34
-
-
Save SystemFw/16198c96cc8a3b2e5d42d0a2c2e21ff5 to your computer and use it in GitHub Desktop.
Update fields of a case class with a Diff generically, with support for nesting
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object Ex { | |
import Lib._ | |
case class Person(name: String, age: Int) | |
case class PersonOpt(name: Option[String], age: Option[Int]) | |
def mergePerson(p: Person, po: PersonOpt) = | |
Person(po.name.getOrElse(p.name), po.age.getOrElse(p.age)) | |
case class Contact(person: Person, phone: String) | |
case class ContactOpt(person: PersonOpt, phone: Option[String]) | |
def mergeContact(c: Contact, co: ContactOpt) = | |
Contact(mergePerson(c.person, co.person), co.phone.getOrElse(c.phone)) | |
val p = Person("Paul", 42) | |
val optP = PersonOpt(None, 34.some) | |
val r = p.optMerge(optP) | |
// scala> Ex.r | |
// res3: Ex.Person = Person(Paul,34) | |
val rr = Contact(p, "old").optMerge(ContactOpt(optP, "new".some)) | |
// scala> Ex.rr | |
// res1: Ex.Contact = Contact(Person(Paul,34),new) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// from this question https://gitter.im/scala/scala?at=5fd8ed148deebc230c7e9694 | |
object Lib { | |
import shapeless._ | |
import shapeless.labelled._ | |
@annotation.implicitNotFound( | |
"Make sure that ${Opt} has the same field names as ${V}, and Options of the field types, recursively" | |
) | |
trait OptMerge[V, Opt] { | |
def apply(v: V, o: Opt): V | |
} | |
trait LowPrio { | |
implicit def baseCase[V]: OptMerge[V, Option[V]] = | |
(a, oa) => oa.getOrElse(a) | |
} | |
object OptMerge extends LowPrio { | |
def apply[A, B](implicit ev: OptMerge[A, B]): OptMerge[A, B] = ev | |
implicit def generic[Opt, V, OptRepr <: HList, VRepr <: HList]( | |
implicit | |
vGen: LabelledGeneric.Aux[V, VRepr], | |
optGen: LabelledGeneric.Aux[Opt, OptRepr], | |
mergeOpt: Lazy[OptMerge[VRepr, OptRepr]] | |
): OptMerge[V, Opt] = { (v, opt) => | |
vGen.from(mergeOpt.value.apply(vGen.to(v), optGen.to(opt))) | |
} | |
implicit def hnil: OptMerge[HNil, HNil] = (_, _) => HNil | |
implicit def fields[K <: Symbol, V1, V2, T1 <: HList, T2 <: HList]( | |
implicit | |
merge: Lazy[OptMerge[V1, V2]], | |
next: OptMerge[T1, T2] | |
): OptMerge[FieldType[K, V1] :: T1, FieldType[K, V2] :: T2] = { (v1, v2) => | |
field[K](merge.value.apply(v1.head, v2.head)) :: next(v1.tail, v2.tail) | |
} | |
} | |
implicit class OptMerger[Opt, V](v: V) { | |
def optMerge(o: Opt)(implicit optMerger: OptMerge[V, Opt]): V = | |
optMerger(v, o) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment