自动将案例类转换为无形的可扩展记录? [英] Automatically convert a case class to an extensible record in shapeless?

查看:29
本文介绍了自动将案例类转换为无形的可扩展记录?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果我有这两个案例类:

If I have these two case classes:

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

和一个实例:

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

有没有办法在无形中自动将person转换成可扩展的记录?

Is there a way in shapeless to automatically convert person to an extensible record?

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

I am interested in both shallow and deep conversions.

浅拷贝类似于:

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

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

In the deep conversion, the nested case class also becomes a record:

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

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

I am also interested in converting records back to case classes.

推荐答案

假设我们有以下设置:

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 直接支持浅表:

Shapeless's LabelledGeneric supports the shallow case directly:

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

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

Or if you want a generic helper method:

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 提供的类型类和其他工具没有方便的方法来做到这一点,但是您可以从 这个问题(现在是一个测试用例 在 Shapeless 中)做你想做的事.首先是类型类本身:

The deep case is trickier, and as far as I know there's no convenient way to do this with the type classes and other tools provided by Shapeless, but you can adapt my code from this question (which is now a test case in Shapeless) to do what you want. First for the type class itself:

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

  def fromRec(out: Out): L
}

然后是一个低优先级实例,用于记录头本身没有 LabelledGeneric 实例的情况:

And then a low-priority instance for the case where the head of the record doesn't itself have a LabelledGeneric instance:

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

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

And then the rest of the companion object:

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 特征和对象必须一起定义才能伴随.)

(Note that the DeepRec trait and object must be defined together to be companioned.)

这很乱,但它有效:

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 不支持部分应用的类型参数列表,我们必须将操作分解为两部分(其中之一将明确指定其类型,而用于另一个将被推断).

The to / from syntax for the conversion back to the case class is necessary because any given record could correspond to a very large number of potential case classes, so we need to be able to specify the target type, and since Scala doesn't support partially-applied type parameter lists, we have to break up the operation into two parts (one of which will have its types specified explicitly, while the type parameters for the other will be inferred).

这篇关于自动将案例类转换为无形的可扩展记录?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆