2015-11-22 4 views
6

Jeśli mam tych dwóch przypadków klasy:Automatycznie przekonwertować klasę spraw do rekordu rozszerzalnego w trybie bezkształtnym?

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

i instancję:

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

Czy istnieje sposób, w bezkształtne automatycznie konwertować person do rozszerzalny rekordu?

Interesują mnie konwersje płytkie i głębokie.

Płytkie kopia będzie coś takiego:

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

w głębokiej konwersji, zagnieżdżone klasy sprawa staje się również zapis:

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

Jestem również zainteresowany konwersji rekordy z powrotem do przypadku klasy.

Odpowiedz

8

Załóżmy, że mamy następującą konfigurację:

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 

Shapeless na LabelledGeneric wspiera płytkie sprawę bezpośrednio:

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

Albo jeśli chcesz rodzajowe metody pomocnika:

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

val shallow: ShallowPersonRec = shallowRec(person) 

I możesz wrócić z from:

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

Głębokie sprawa jest bardziej skomplikowana, io ile wiem, nie ma wygodny sposób to zrobić z klas typu i innych narzędzi dostarczonych przez bezkształtne, ale można dostosować mój kod z this question (który jest teraz test case w Shapeless), aby robić to, co chcesz. Po pierwsze dla samego typu klasy:

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

    def fromRec(out: Out): L 
} 

A potem wystąpienie o niskim priorytecie dla przypadku, gdy szef protokołu sam nie posiada LabelledGeneric instancję:

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) 
    } 
} 

a następnie resztę obiektu towarzyszącego:

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) 
    } 
} 

(. Zauważ, że DeepRec cecha i obiekt musi być zdefiniowana razem być companioned)

To jest brudny, ale to działa:

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)) 

to/from składnia do konwersji z powrotem do klasy przypadku jest konieczne, ponieważ dana płyta mogła odpowiadać na bardzo dużą liczbę potencjalnych klas przypadków, tak musimy mieć możliwość określenia typu celu, a ponieważ Scala nie obsługuje częściowo zastosowanych list parametrów, musimy podzielić operację na dwie części (z których jedna będzie miała wyraźnie określone jej typy, podczas gdy parametry typu dla drugiego zostanie wywnioskowane).

Powiązane problemy