How to represent a partial update on case classe in Scala ?

case-class, scala, shapeless

Solution

Here's a relatively boilerplate-free approach using Shapeless. First we define some polymorphic versions of the relevant functions on `Option`:

import shapeless._

object orElser extends Poly1 {
  implicit def default[A] = at[Option[A]] {
    oa => (o: Option[A]) => oa orElse o
  }
}

object getOrElser extends Poly1 {
  implicit def default[A] = at[Option[A]] {
    oa => (a: A) => oa getOrElse a
  }
}

We'll represent an update as an `HList` where every element is an `Option`, and we can write an `append` method that allows us to append two updates:

import UnaryTCConstraint._

def append[U <: HList: *->*[Option]#λ, F <: HList](u: U, v: U)(implicit
  mapper: MapperAux[orElser.type, U, F],
  zipper: ZipApplyAux[F, U, U]
): U = v.map(orElser).zipApply(u)

And finally we can write our `update` method itself:

def update[T, L <: HList, F <: HList, U <: HList](t: T, u: U)(implicit
  iso: Iso[T, L],
  mapped: MappedAux[L, Option, U],
  mapper: MapperAux[getOrElser.type, U, F],
  zipper: ZipApplyAux[F, L, L]
) = iso from u.map(getOrElser).zipApply(iso to t)

Now we need just a little bit of boilerplate (in the future this won't be necessary, thanks to inference-driving macros):

implicit def pcIso =
  Iso.hlist(PropositionContent.apply _, PropositionContent.unapply _)

We'll also define an alias for this specific update type (which isn't strictly necessary, but will make the following examples a little more concise):

type PCUpdate = Option[String] :: Option[String] :: HNil

And finally:

scala> val pc = PropositionContent("some title", "some content")
pc: PropositionContent = PropositionContent(some title,some content)

scala> val u1: PCUpdate = Some("another title") :: None :: HNil
u1: PCUpdate = Some(another title) :: None :: HNil

scala> val u2: PCUpdate = Some("newest title") :: Some("new content") :: HNil
u2: PCUpdate = Some(newest title) :: Some(new content) :: HNil

scala> append(u1, u2)
res0: PCUpdate = Some(newest title) :: Some(new content) :: HNil

scala> update(pc, append(u1, u2))
res1: PropositionContent = PropositionContent(newest title,new content)

Which is what we wanted.

Problem

I have the following case class : ``` case class PropositionContent(title:String,content:String) ``` And I would like to represent a partial modification of it as Data. One way would be to create the case class : ``` case class PartialPropositionContent(title:Option[String],content:Option[String) ``` and then some methods : ``` object PropositionContent { def append( pc : PropositionContent , ppc : PartialPropositionContent) = PropositionContent ( ppc.title.getOrElse(pc.title) , ppc.content.getOrElse(pc.content) ) def append( ppc : PartialPropositionContent , ppc2 : PartialPropositionContent ): PartialPropositionContent = {...} } ``` But it's a bit boilerplaty ! I think a `case class PropositionContent[M[_]](title:M[String],content:M[String])` will not really solve the stuff, and I don't know how to use Shapeless to solve the stuff. So do you have an idea ?

Original source