2015-08-11 6 views
6

Ho un sacco di case classes simili che significano cose diverse ma hanno lo stesso elenco di argomenti.Trasforma una classe di case in un'altra quando l'elenco degli argomenti è lo stesso

object User { 
    case class Create(userName:String, firstName: String, lastName: String) 
    case class Created(userName:String, firstName: String, lastName: String) 
} 

object Group { 
    case class Create(groupName:String, members: Int) 
    case class Created(groupName:String, members: Int) 
} 

Dato questo tipo di messa a punto, ero stanco di metodi di scrittura che prendono un argomento di tipo Crea e restituisce un argomento di tipo Creato. Ho un sacco di casi di test che fanno esattamente questo genere di cose.

Potrei scrivere una funzione per convertire una classe di caso nell'altra. Questa funzione converte User.Create in User.Created

def userCreated(create: User.Create) = User.Create.unapply(create).map((User.Created.apply _).tupled).getOrElse(sys.error(s"User creation failed: $create")) 

ho dovuto scrivere un altro tale funzione per il gruppo. Quello che mi piacerebbe davvero avere è una funzione generica che prende i due tipi delle classi case e un oggetto di una classe case e converte nell'altra. Qualcosa di simile,

def transform[A,B](a: A):B 

Inoltre, questa funzione non dovrebbe vanificare lo scopo di ridurre il boilerplate. Non esitare a suggerire una firma diversa per la funzione, se è più facile da usare.

+0

Ti aspetti qualcosa di simile a 'convert (a) .to [B]'? –

+0

È importante che tutte queste classi abbinate abbiano il nome creato/creato o il tuo requisito sia puramente accoppiato e che l'unica funzione trasformi la prima classe di una qualsiasi di queste coppie nel secondo? – itsbruce

risposta

5

Per la cronaca, ecco la sintassi più semplice typesafe Sono riuscito a implementare:

implicit class Convert[A, RA](value: A)(implicit ga: Generic.Aux[A, RA]) { 
    def convertTo[B, RB](gb: Generic.Aux[B, RB])(implicit ev: RA =:= RB) = 
    gb.from(ga.to(value)) 
} 

e può essere utilizzato in questo modo:

case class Create(userName: String, firstName: String, lastName: String) 
case class Created(userName: String, firstName: String, lastName: String) 

val created = Create("foo", "bar", "baz").convertTo(Generic[Created]) 

O la stessa cosa con LabelledGeneric per ottenere una migliore sicurezza del tipo:

implicit class Convert[A, RA](value: A)(implicit ga: LabelledGeneric.Aux[A, RA]) { 
    def convertTo[B, RB](gb: LabelledGeneric.Aux[B, RB])(implicit ev: RA =:= RB) = 
    gb.from(ga.to(value)) 
} 

val created = Create("foo", "bar", "baz").convertTo(LabelledGeneric[Created])) 
10

Informale per il salvataggio!

È possibile utilizzare Generic di Shapeless per creare rappresentazioni generiche di classi di casi, che possono quindi essere utilizzate per realizzare ciò che si sta tentando di fare. Utilizzando LabelledGeneric possiamo applicare entrambi i tipi e i nomi dei parametri.

import shapeless._ 

case class Create(userName: String, firstName: String, lastName: String) 
case class Created(userName: String, firstName: String, lastName: String) 
case class SortOfCreated(screenName: String, firstName: String, lastName: String) 

val c = Create("username", "firstname", "lastname") 

val createGen = LabelledGeneric[Create] 
val createdGen = LabelledGeneric[Created] 
val sortOfCreatedGen = LabelledGeneric[SortOfCreated] 

val created: Created = createdGen.from(createGen.to(c)) 

sortOfCreatedGen.from(createGen.to(c)) // fails to compile 
+0

Bella soluzione! Tuttavia, vi è una possibile trappola: dato che 'Generic' funziona convertendo gli oggetti in' HList', non garantisce che i corrispondenti nomi dei parametri degli oggetti di origine e di destinazione corrispondano. Pertanto, se si hanno parametri diversi con lo stesso tipo o un diverso ordine params, si otterrà un risultato errato della conversione in runtime. C'è [un sacco di utilities java] (http://stackoverflow.com/a/1432956/1349366) che eseguono la mappatura per riflessione che risolverà questo problema. – Aivean

+0

@Aivean Questo è un punto solido. Dipende se ti preoccupi dei nomi dei parametri o solo dei tipi - questo è un caso in cui le classi di valore sarebbero frizioni. – Ryan

+2

@Aivean Questo può essere risolto direttamente usando 'LabelledGeneric', che richiederebbe la corrispondenza esatta dei nomi dei parametri delle classi del caso e dei tipi e il loro ordine. – Kolmar