Skip to content

Instantly share code, notes, and snippets.

@SystemFw
Last active December 23, 2021 12:34
Show Gist options
  • Save SystemFw/16198c96cc8a3b2e5d42d0a2c2e21ff5 to your computer and use it in GitHub Desktop.
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
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)
}
// 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