2014-04-21 40 views
7

zakładając, że masz zajęcia takim wypadku następującymBezpieczne kopiowanie pola między klasami przypadku różnych typów

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

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

I instancji klas z następujących zmiennych

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

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

Czy istnieje sposób, aby użyć .copy metoda (lub w inny sposób), aby zastosować zmienne z Test2 do Test1, podobnie jak

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

Jeśli nie jest to możliwe w czystej scali, jak to by było zrobione w Shapeless 1/2 (obecny kod jest w Shapeless 1, jednak planujemy uaktualnić do Shapeless 2 w pewnym momencie)

+0

Czy chcesz go automatycznie wymyślić nazwy i dopasować je? – NightRa

+0

Tak, dlatego są one "klasami przypadków", a nie "krotkami" – mdedetrich

Odpowiedz

13

In bezkształtne 2.0.0 można to zrobić z jak tak,

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) 

Zauważ, że to sprawia, że ​​założenia o kolejności pól w każdej z klas przypadków zamiast wykorzystanie informacji etykietę pola. Może to być podatne na błędy, gdy masz wiele pól tego samego typu: typy mogą być wyrównane, ale ukryta semantyka może zostać zmieniona.

Możemy to naprawić, używając bezkształtnej wersji LabelledGeneric. LabelledGeneric odwzorowuje wartości klasy case na bezkształtne, rozszerzalne rekordy, które, podobnie jak przechwytywanie typów wartości pól, kodują również nazwy pól w typie za pomocą pojedynczego typu odpowiedniej Scala Symbol. Z odrobiną dodatkowej infrastruktury (które będę dodając do bezkształtnej 2.1.0 wkrótce) pozwala nam na mapie między klasami przypadków bezpiecznie przy minimalnym 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

Dzięki, to jest to, czego potrzebowałem – mdedetrich

+0

cześć, czy istnieje również sposób na konwersję z Test1 na Test2 podczas usuwania środkowego elementu? tj: Test1 ("pierwszy", 1, 3) powinien być Test2 ("pierwszy, 3) –

+0

Dzięki, miła odpowiedź, Co to jest" FieldType "? – Michaelzh