2014-04-21 9 views
7

si Supponendo hanno classi case come la seguentein modo sicuro la copia campi tra classi case di diversi tipi

case class Test1(a:String,b:Int,c:Char) 

case class Test2(a:String,b:Int) 

E si istanziare le classi con le seguenti variabili

val test1 = Test1("first",2,'3') 

val test2 = Test2("1st",20) 

C'è un modo per utilizzare il Metodo .copy (o altro), per applicare le variabili all'interno di Test2 a Test1, qualcosa come

val test3 = test1.copy(test2) //Note this isn't valid scala code 
// Result should be ("1st",20,'3') 

Se questo non è possibile in scala pura, come si farebbe in Shapeless 1/2 (il codice corrente è in Shapeless 1, tuttavia stiamo pianificando l'aggiornamento a Shapeless 2 ad un certo punto nel tempo)

+0

vuoi che per capire automaticamente i nomi e abbinarli? – NightRa

+0

Sì, quindi perché sono 'case classes' e non' tuples' – mdedetrich

risposta

13

In 2.0.0 informe questo può essere fatto con questo modo,

scala> import shapeless._ 
import shapeless._ 

scala> case class Test1(a: String, b: Int, c: Char) 
defined class Test1 

scala> case class Test2(a: String, b: Int) 
defined class Test2 

scala> val test1 = Test1("first", 2, '3') 
test1: Test1 = Test1(first,2,3) 

scala> val test2 = Test2("1st", 20) 
test2: Test2 = Test2(1st,20) 

scala> val test1Gen = Generic[Test1] 
test1Gen: ... = [email protected] 

scala> val test2Gen = Generic[Test2] 
test2Gen: ... = [email protected] 

scala> val test3 = test1Gen.from(test2Gen.to(test2) :+ test1.c) 
test3: Test1 = Test1(1st,20,3) 

noti che questo fa presumere l'ordine dei campi in ciascuna delle classi case piuttosto che facendo uso delle informazioni dell'etichetta del campo. Questo potrebbe essere soggetto a errori in cui erano presenti più campi dello stesso tipo: i tipi potrebbero essere allineati, ma la semantica latente potrebbe essere alterata.

Possiamo risolvere il problema utilizzando LabelledGeneric di shapeless. LabelledGeneric esegue il mapping dei valori della classe del case a record estensibili informi che, oltre a catturare i tipi dei valori dei campi, codifica anche i nomi dei campi nel tipo tramite il tipo singleton del corrispondente Scala Symbol. Con un po 'di ulteriori infrastrutture (che sarò aggiungendo alla 2.1.0 informe breve) questo ci permette di mappare tra classi case in tutta sicurezza con il minimo boilerplate,

import shapeless._, record._, syntax.singleton._, ops.hlist.Remove 

/** 
* This will be in shapeless 2.1.0 ... 
* 
* Permute the elements of the supplied `HList` of type `L` into the same order 
* as the elements of the `HList` of type `M`. 
*/ 
trait Align[L <: HList, M <: HList] extends (L => M) { 
    def apply(l: L): M 
} 

object Align { 
    def apply[L <: HList, M <: HList] 
    (implicit alm: Align[L, M]): Align[L, M] = alm 

    implicit val hnilAlign: Align[HNil, HNil] = new Align[HNil, HNil] { 
    def apply(l: HNil): HNil = l 
    } 

    implicit def hlistAlign[L <: HList, MH, MT <: HList, R <: HList] 
    (implicit 
     select: Remove.Aux[L, MH, (MH, R)], 
     alignTail: Align[R, MT]): Align[L, MH :: MT] = new Align[L, MH :: MT] { 
    def apply(l: L): MH :: MT = { 
     val (h, t) = l.removeElem[MH] 
     h :: alignTail(t) 
    } 
    } 
} 

/** 
* This, or something like it, will be in shapeless 2.1.0 ... 
* 
* Utility trait intended for inferring a field type from a sample value and 
* unpacking it into its key and value types. 
*/ 
trait Field { 
    type K 
    type V 
    type F = FieldType[K, V] 
} 

object Field { 
    def apply[K0, V0](sample: FieldType[K0, V0]) = 
    new Field { type K = K0; type V = V0 } 
} 

object OldWineNewBottle { 
    case class From(s1: String, s2: String) 
    case class To(s2: String, i: Int, s1: String) 

    val from = From("foo", "bar") 

    val fromGen = LabelledGeneric[From] 
    val toGen = LabelledGeneric[To] 

    // Define the type of the i field by example 
    val iField = Field('i ->> 0) 

    val align = Align[iField.F :: fromGen.Repr, toGen.Repr] 

    // extend the fields of From with a field for 'i', permute into 
    // the correct order for To and create a new instance ... 
    val to = toGen.from(align('i ->> 23 :: fromGen.to(from))) 

    assert(to == To("bar", 23, "foo")) 
} 
+0

Grazie, questo è quello che mi serviva – mdedetrich

+0

ciao, c'è anche un modo per avere una conversione da Test1 a Test2 rimuovendo l'elemento centrale? es .: Test1 ("primo", 1, 3) dovrebbe essere Test2 ("primo, 3) –

+0

Grazie, bella risposta, qual è il' FieldType'? – Michaelzh

Problemi correlati