Skip to content

Instantly share code, notes, and snippets.

@milessabin
Last active August 16, 2016 16:25
Show Gist options
  • Save milessabin/b16e0765e1acdc90fd29 to your computer and use it in GitHub Desktop.
Save milessabin/b16e0765e1acdc90fd29 to your computer and use it in GitHub Desktop.
Functional update of common fields of an open family of case classes via a case-class-like copy through the common super type ...
import shapeless._
/**
* Functional update of common fields of an *open* family of case classes
* via a case-class-like copy through the common super type ...
*
* This is a follow up to my earlier post showin how to do functional
* update for a sealed family of case classes,
*
* https://gist.github.com/milessabin/a212d946aef33811fee1
*
* It makes use of the generic case class merge I posted here,
*
* https://gist.github.com/milessabin/c3c9fbc57b1d913c2ee4
*/
object OpenBaseCopyDemo extends App {
import openCopySyntax._
import mergeSyntax._
// Open family of case classes ...
trait Base extends OpenFamily[Base] {
val i: Int
val b: Boolean
case class BaseFields(i: Int, b: Boolean)
def baseFields = BaseFields(i, b)
def baseCopy(base: BaseFields): Base
}
case class Foo(i: Int, b: Boolean) extends Base {
def baseCopy(base: BaseFields) = this merge base
}
case class Bar(i: Int, s: String, b: Boolean) extends Base {
def baseCopy(base: BaseFields) = this merge base
}
case class Baz(i: Int, b: Boolean, d: Double) extends Base {
def baseCopy(base: BaseFields) = this merge base
}
case class Quux(c: Char, i: Int, b: Boolean) extends Base {
def baseCopy(base: BaseFields) = this merge base
}
// case class copy style functional update through the common super-type ...
val b1: Base = Foo(23, true)
assert(b1.copy(i = 13) == Foo(13, true))
val b2: Base = Bar(23, "foo", false)
assert(b2.copy(i = 13, b = true) == Bar(13, "foo", true))
val b3: Base = Baz(23, false, 2.3)
assert(b3.copy(i = 13) == Baz(13, false, 2.3))
val b4: Base = Quux('*', 23, false)
assert(b4.copy(b = true, i = 13) == Quux('*', 13, true))
}
trait OpenFamily[T] {
type BaseFields
def baseFields: BaseFields
def baseCopy(base: BaseFields): T
}
object openCopySyntax {
class CopySyntax[T, BaseFields0](t: OpenFamily[T] { type BaseFields = BaseFields0 }) {
object copy extends RecordArgs {
def applyRecord[R <: HList](r: R)(implicit update: UpdateRepr[BaseFields0, R]): T =
t.baseCopy(update(t.baseFields, r))
}
}
implicit def apply[T](t: OpenFamily[T]): CopySyntax[T, t.BaseFields] = new CopySyntax(t)
}
trait UpdateRepr[T, R <: HList] {
def apply(t: T, r: R): T
}
object UpdateRepr {
import ops.record._
implicit def mergeUpdateRepr[T <: HList, R <: HList]
(implicit merger: Merger.Aux[T, R, T]): UpdateRepr[T, R] =
new UpdateRepr[T, R] {
def apply(t: T, r: R): T = merger(t, r)
}
implicit def cnilUpdateRepr[R <: HList]: UpdateRepr[CNil, R] =
new UpdateRepr[CNil, R] {
def apply(t: CNil, r: R): CNil = t
}
implicit def cconsUpdateRepr[H, T <: Coproduct, R <: HList]
(implicit
uh: Lazy[UpdateRepr[H, R]],
ut: Lazy[UpdateRepr[T, R]]
): UpdateRepr[H :+: T, R] =
new UpdateRepr[H :+: T, R] {
def apply(t: H :+: T, r: R): H :+: T = t match {
case Inl(h) => Inl(uh.value(h, r))
case Inr(t) => Inr(ut.value(t, r))
}
}
implicit def genProdUpdateRepr[T, R <: HList, Repr <: HList]
(implicit
prod: HasProductGeneric[T],
gen: LabelledGeneric.Aux[T, Repr],
update: Lazy[UpdateRepr[Repr, R]]
): UpdateRepr[T, R] =
new UpdateRepr[T, R] {
def apply(t: T, r: R): T = gen.from(update.value(gen.to(t), r))
}
implicit def genCoprodUpdateRepr[T, R <: HList, Repr <: Coproduct]
(implicit
coprod: HasCoproductGeneric[T],
gen: Generic.Aux[T, Repr],
update: Lazy[UpdateRepr[Repr, R]]
): UpdateRepr[T, R] =
new UpdateRepr[T, R] {
def apply(t: T, r: R): T = gen.from(update.value(gen.to(t), r))
}
}
// Case class merge ...
object mergeSyntax {
implicit class MergeSyntax[T](t: T) {
def merge[U](u: U)(implicit merge: CaseClassMerge[T, U]): T = merge(t, u)
}
}
trait CaseClassMerge[T, U] {
def apply(t: T, u: U): T
}
object CaseClassMerge {
import ops.record.Merger
def apply[T, U](implicit merge: CaseClassMerge[T, U]): CaseClassMerge[T, U] = merge
implicit def mkCCMerge[T, U, RT <: HList, RU <: HList]
(implicit
tgen: LabelledGeneric.Aux[T, RT],
ugen: LabelledGeneric.Aux[U, RU],
merger: Merger.Aux[RT, RU, RT]
): CaseClassMerge[T, U] =
new CaseClassMerge[T, U] {
def apply(t: T, u: U): T =
tgen.from(merger(tgen.to(t), ugen.to(u)))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment