2013-04-27 11 views
10

Mam następujące klasy obudowy:Jak reprezentować częściową aktualizację klassu przypadku w Scali?

case class PropositionContent(title:String,content:String) 

I chciałbym reprezentować częściową modyfikację nim jako dane.

Jednym ze sposobów byłoby utworzyć klasę case:

case class PartialPropositionContent(title:Option[String],content:Option[String) 

a następnie kilka sposobów:

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 = {...} 

} 

Ale to trochę boilerplaty!

Myślę, że case class PropositionContent[M[_]](title:M[String],content:M[String]) tak naprawdę nie rozwiąże problemu i nie wiem jak używać Shapeless do rozwiązywania problemów.

Masz pomysł?

+0

wskazówka: https://blog.stackmob.com/2012/02/an-introduction-to-lenses-in-scalaz/ –

+0

co z aliasami typów? – sschaef

+0

masz na myśli 'type CompleteProposistionContent = PropositionContent [Id]'? – jwinandy

Odpowiedz

6

Oto podejście względnie wolne od płyt przy użyciu metody bezkształtnej. Najpierw musimy zdefiniować kilka polimorficzne wersje odpowiednich funkcji na 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 
    } 
} 

Będziemy reprezentować aktualizacji jako HList gdzie każdy element jest Option i możemy napisać metodę append która pozwala nam dołączyć dwie aktualizacje :

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) 

I wreszcie możemy napisać nasz update samą metodę:

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) 

Teraz musimy tylko trochę boilerplate (w the future to nie będzie konieczne, dzięki inference-driving macros):

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

Będziemy również zdefiniować alias dla tego konkretnego typu aktualizacji (co nie jest bezwzględnie konieczne, ale sprawi, że poniższe przykłady trochę bardziej zwięzłe):

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

I wreszcie:

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) 

który jest w Kapelusz, który chcieliśmy.

2

Wystarczy przeniesiony kod Travisa (czego nie rozumiem) do bezkształtnej 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 jest nienaruszona.

+1

_ (czego nie rozumiem) _ => wydaje się być w przypadku wielu bezkształtnego kodu :) – eirirlar