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 ?
Just ported Travis's code (which I don't understand) to Shapeless 2.1:
import shapeless._
import shapeless.ops.hlist._
object orElser extends Poly1 {
implicit def default[A]: Case[Option[A]] { type Result = Option[A] => Option[A] } = at[Option[A]] {
oa => (o: Option[A]) => oa orElse o
}
}
object getOrElser extends Poly1 {
implicit def default[A]: Case[Option[A]] { type Result = A => A } = at[Option[A]] {
oa => (a: A) => oa getOrElse a
}
}
import UnaryTCConstraint._
def append[U <: HList: *->*[Option]#λ, F <: HList](u: U, v: U)
(implicit mapper: Mapper.Aux[orElser.type, U, F],
zipper: ZipApply.Aux[F, U, U]): U =
v map orElser zipApply u
def update[T, L <: HList, F <: HList, U <: HList](t: T, u: U)
(implicit gen: Generic.Aux[T, L],
mapped: Mapped.Aux[L, Option, U],
mapper: Mapper.Aux[getOrElser.type, U, F],
zipper: ZipApply.Aux[F, L, L]) =
gen from (u map getOrElser zipApply (gen to t))
API is intact.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With