2015-11-22 7 views
6

Se ho queste classi a due casi:Converti automaticamente una classe di case in un record estensibile in shapeless?

case class Address(street : String, zip : Int) 
case class Person(name : String, address : Address) 

e un'istanza:

val person = Person("Jane", Address("street address", 12345)) 

C'è un modo in informe per convertire automaticamente person a un record estendibile?

Sono interessato sia a conversioni superficiali che profonde.

La copia superficiale sarebbe qualcosa di simile:

'name ->> "Jane" :: 'address ->> Address("street address", 12345) :: HNil 

Nella conversione profonda, la classe caso annidata diventa anche un record:

'name ->> "Jane" :: 'address ->> ('street ->> "street address" :: 'zip ->> 12345 :: HNil) :: HNil 

Sono interessato anche a convertire i record di nuovo a caso classi.

risposta

8

Supponiamo che abbiamo la seguente configurazione:

import shapeless._, shapeless.labelled.{ FieldType, field } 

case class Address(street: String, zip: Int) 
case class Person(name: String, address: Address) 

val person = Person("Jane", Address("street address", 12345)) 

type ShallowPersonRec = 
    FieldType[Witness.`'name`.T, String] :: 
    FieldType[Witness.`'address`.T, Address] :: HNil 

type DeepPersonRec = 
    FieldType[Witness.`'name`.T, String] :: 
    FieldType[ 
    Witness.`'address`.T, 
    FieldType[Witness.`'street`.T, String] :: 
    FieldType[Witness.`'zip`.T, Int] :: HNil 
    ] :: HNil 

informe di LabelledGeneric supporta il caso superficiale direttamente:

val shallow: ShallowPersonRec = LabelledGeneric[Person].to(person) 

O se volete un metodo di supporto generico:

def shallowRec[A](a: A)(implicit gen: LabelledGeneric[A]): gen.Repr = gen.to(a) 

val shallow: ShallowPersonRec = shallowRec(person) 

E puoi tornare indietro con from:

scala> val originalPerson = LabelledGeneric[Person].from(shallow) 
originalPerson: Person = Person(Jane,Address(street address,12345)) 

Il caso profondo è più complicato, e per quanto ne so non c'è modo conveniente per fare questo con le classi di tipo e altri strumenti forniti da informe, ma è possibile adattare il mio codice da this question (che ora è un test case in Shapeless) per fare ciò che vuoi. Prima per la classe tipo stesso:

trait DeepRec[L] extends DepFn1[L] { 
    type Out <: HList 

    def fromRec(out: Out): L 
} 

E poi un'istanza bassa priorità per il caso in cui la testa del record non si ha un esempio LabelledGeneric:

trait LowPriorityDeepRec { 
    type Aux[L, Out0] = DeepRec[L] { type Out = Out0 } 

    implicit def hconsDeepRec0[H, T <: HList](implicit 
    tdr: Lazy[DeepRec[T]] 
): Aux[H :: T, H :: tdr.value.Out] = new DeepRec[H :: T] { 
    type Out = H :: tdr.value.Out  
    def apply(in: H :: T): H :: tdr.value.Out = in.head :: tdr.value(in.tail) 
    def fromRec(out: H :: tdr.value.Out): H :: T = 
     out.head :: tdr.value.fromRec(out.tail) 
    } 
} 

E poi il resto dell'oggetto compagna:

object DeepRec extends LowPriorityDeepRec { 
    def toRec[A, Repr <: HList](a: A)(implicit 
    gen: LabelledGeneric.Aux[A, Repr], 
    rdr: DeepRec[Repr] 
): rdr.Out = rdr(gen.to(a)) 

    class ToCcPartiallyApplied[A, Repr](val gen: LabelledGeneric.Aux[A, Repr]) { 
    type Repr = gen.Repr  
    def from[Out0, Out1](out: Out0)(implicit 
     rdr: Aux[Repr, Out1], 
     eqv: Out0 =:= Out1 
    ): A = gen.from(rdr.fromRec(eqv(out))) 
    } 

    def to[A](implicit 
    gen: LabelledGeneric[A] 
): ToCcPartiallyApplied[A, gen.Repr] = 
    new ToCcPartiallyApplied[A, gen.Repr](gen) 

    implicit val hnilDeepRec: Aux[HNil, HNil] = new DeepRec[HNil] { 
    type Out = HNil  
    def apply(in: HNil): HNil = in 
    def fromRec(out: HNil): HNil = out 
    } 

    implicit def hconsDeepRec1[K <: Symbol, V, Repr <: HList, T <: HList](implicit 
    gen: LabelledGeneric.Aux[V, Repr], 
    hdr: Lazy[DeepRec[Repr]], 
    tdr: Lazy[DeepRec[T]] 
): Aux[FieldType[K, V] :: T, FieldType[K, hdr.value.Out] :: tdr.value.Out] = 
    new DeepRec[FieldType[K, V] :: T] { 
     type Out = FieldType[K, hdr.value.Out] :: tdr.value.Out 
     def apply(
     in: FieldType[K, V] :: T 
    ): FieldType[K, hdr.value.Out] :: tdr.value.Out = 
     field[K](hdr.value(gen.to(in.head))) :: tdr.value(in.tail) 
     def fromRec(
     out: FieldType[K, hdr.value.Out] :: tdr.value.Out 
    ): FieldType[K, V] :: T = 
     field[K](gen.from(hdr.value.fromRec(out.head))) :: 
      tdr.value.fromRec(out.tail) 
    } 
} 

(. si noti che il DeepRec tratto e oggetto devono essere definiti insieme da companioned)

0.123.516,41 mila

Questo è disordinato, ma funziona:

scala> val deep: DeepPersonRec = DeepRec.toRec(person) 
deep: DeepPersonRec = Jane :: (street address :: 12345 :: HNil) :: HNil 

scala> val originalPerson = DeepRec.to[Person].from(deep) 
originalPerson: Person = Person(Jane,Address(street address,12345)) 

La sintassi to/from per la riconversione alla classe caso è necessario perché qualsiasi record potrebbe corrispondere ad un gran numero di potenziali classi case, così dobbiamo essere in grado di specificare il tipo di destinazione, e poiché Scala non supporta gli elenchi di parametri di tipo parzialmente applicati, dobbiamo suddividere l'operazione in due parti (una delle quali avrà i suoi tipi specificati esplicitamente, mentre i parametri di tipo per l'altro sarà dedotto).

Problemi correlati