Skip to content

Instantly share code, notes, and snippets.

@crakjie
Created October 23, 2015 07:44
Show Gist options
  • Save crakjie/51ab7435f5b0ac3e669e to your computer and use it in GitHub Desktop.
Save crakjie/51ab7435f5b0ac3e669e to your computer and use it in GitHub Desktop.
package com.misterbell.shed.models.travellers
import shapeless.ops.record.Merger
import shapeless._
object OpenBaseCopyDemo extends App {
import openCopySyntax._
import mergeSyntax._
// Open family of case classes ...
// Parm T is used to return T and not just Base.
trait Base[T <: Base[T]] extends OpenFamily[T] {
val i: Int
val b: Option[Boolean]
case class BaseFields(i: Int, b: Option[Boolean])
def baseFields = BaseFields(i, b)
def baseCopy(base: BaseFields): T //Here it's return T
}
case class Foo(i: Int, b: Option[Boolean]) extends Base[Foo] {
def baseCopy(base: BaseFields) = this merge base
}
case class Bar(i: Int, s: String, b: Option[Boolean]) extends Base[Bar] {
def baseCopy(base: BaseFields) = this merge base
}
case class Baz(i: Int, b: Option[Boolean], d: Double) extends Base[Baz] {
def baseCopy(base: BaseFields) = this merge base
}
case class Quux(c: Char, i: Int, b: Option[Boolean]) extends Base[Quux] {
def baseCopy(base: BaseFields) = this merge base
}
// case class copy style functional update through the common super-type ...
val b1: Foo = Foo(23, Some(true))
val x1 : Foo = b1.copy(i = 13)
assert(b1.copy(i = 13) == Foo(13, Some(true)))
val b2: Bar = Bar(23, "foo", Some(false))
assert(b2.copy(i = 13, b = Some(true)) == Bar(13, "foo", Some(true)))
val b3: Baz = Baz(23, Some(false), 2.3)
assert(b3.copy(i = 13) == Baz(13, Some(false), 2.3))
val b4: Quux = Quux('*', 23, Some(false))
assert(b4.copy(b = Some(true), i = 13) == Quux('*', 13, Some(true)))
/* Because it's return T this method should compile */
/* And it's do when b is Boolean */
/* But's it's dosen't compile when the Boolean is contain in Option*/
/*
OpenCopy.scala:53: could not find implicit value for parameter update: com.misterbell.shed.models.travellers.UpdateRepr[t.BaseFields,shapeless.::[shapeless.labelled.FieldType[shapeless.tag.@@[Symbol,String("b")],Some[Boolean]],shapeless.HNil]]
[error] error after rewriting to openCopySyntax.apply[T](t).copy.applyDynamicNamed("apply")(scala.Tuple2("b", Some(true)))
[error] possible cause: maybe a wrong Dynamic method signature?
[error] t.copy(b = Some(true))
*/
def a[T <: Base[T]](t : T): T = {
t.copy(b = Some(true))
}
}
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 OpenFamily[T] {
type BaseFields
def baseFields: BaseFields
def baseCopy(base: BaseFields): 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))
}
}
// Implementation in terms of LabelledGeneric ...
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