2013-04-27 20 views
10

Ho la seguente classe di caso:Come rappresentare un aggiornamento parziale su case class in Scala?

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

e vorrei rappresentare una parziale modifica di esso come dati.

Un modo potrebbe essere quello di creare la classe caso:

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

e poi alcuni metodi:

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

} 

Ma è un po 'boilerplaty!

Penso che uno case class PropositionContent[M[_]](title:M[String],content:M[String]) non risolverà davvero la roba, e non so come usare Shapeless per risolvere il problema.

Quindi hai un'idea?

+0

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

+0

E i tipi di alias? – sschaef

+0

intendi 'type CompleteProposistionContent = PropositionContent [Id]'? – jwinandy

risposta

6

Ecco un approccio relativamente senza caldaia che utilizza Shapeless. In primo luogo definiamo alcune versioni polimorfe delle funzioni rilevanti sul 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 
    } 
} 

ti rappresentano un aggiornamento come un HList dove ogni elemento è un Option, e possiamo scrivere un metodo append che ci permette di aggiungere due aggiornamenti :

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) 

E finalmente possiamo scrivere il nostro metodo update stesso:

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) 

Ora abbiamo bisogno solo un po 'di boilerplate (in the future questo non sarà necessario, grazie a inference-driving macros):

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

Faremo anche definire un alias per questo specifico tipo di aggiornamento (che non è strettamente necessario, ma renderà i seguenti esempi un po 'più conciso):

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

E infine:

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) 

Quale è w cappello che volevamo

2

Proprio porting del codice di Travis (che non capisco) per informi 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 è intatto.

+1

_ (che non capisco) _ => sembra essere il caso con un sacco di codice informe :) – eirirlar

Problemi correlati