【问题标题】:Automatically convert a case class to an extensible record in shapeless?自动将案例类转换为无形中的可扩展记录?
【发布时间】:2015-11-22 18:50:48
【问题描述】:

如果我有这两个案例类:

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

还有一个实例:

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

有没有办法在 shapeless 中自动将person 转换为可扩展的记录?

我对浅层和深层转换都感兴趣。

浅拷贝是这样的:

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

在深度转换中,嵌套的case类也变成了一条记录:

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

我也有兴趣将记录转换回案例类。

【问题讨论】:

    标签: scala shapeless


    【解决方案1】:

    假设我们有以下设置:

    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 的LabelledGeneric 直接支持shallow case:

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

    或者如果你想要一个通用的辅助方法:

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

    你可以返回from:

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

    深层案例比较棘手,据我所知,使用 Shapeless 提供的类型类和其他工具没有方便的方法,但您可以从 this question 修改我的代码(现在是 @987654322 @在无形中)做你想做的事。首先是类型类本身:

    trait DeepRec[L] extends DepFn1[L] {
      type Out <: HList
    
      def fromRec(out: Out): L
    }
    

    然后是一个低优先级实例,用于记录头部本身没有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)
      }
    }
    

    然后是伴生对象的其余部分:

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

    (注意DeepRec trait 和对象必须一起定义才能被伴随。)

    这很乱,但它有效:

    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 语法用于转换回案例类是必要的,因为任何给定的记录都可能对应于非常大量的潜在案例类,因此我们需要能够指定目标类型,而且由于 Scala 不支持部分应用的类型参数列表,我们必须将操作分成两部分(其中一个将明确指定其类型,而另一部分的类型参数将被推断出来)。

    【讨论】:

      猜你喜欢
      • 2016-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-17
      • 2020-01-16
      相关资源
      最近更新 更多