自动将 case class 转换为 shapeless 中的可扩展记录?
Automatically convert a case class to an extensible record in shapeless?
如果我有这两种情况classes:
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
在深度转换中,嵌套的大小写class也变成了一条记录:
'name ->> "Jane" :: 'address ->> ('street ->> "street address" :: 'zip ->> 12345 :: HNil) :: HNil
我也有兴趣将记录转换回大小写 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
直接支持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))
深层案例比较棘手,据我所知,使用类型 classes 和 Shapeless 提供的其他工具没有方便的方法来做到这一点,但您可以从 this question (which is now a test case 在 Shapeless 中)做你想做的事。首先是类型 class 本身:
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 和 object 必须一起定义才能伴随。)
这很乱,但它有效:
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))
用于转换回大小写 class 的 to
/ from
语法是必要的,因为任何给定的记录都可能对应大量潜在的大小写 classes,因此我们需要能够指定目标类型,并且由于 Scala 不支持部分应用的类型参数列表,我们必须将操作分为两部分(其中一部分将明确指定其类型,而另一个的类型参数将被推断出来)。
如果我有这两种情况classes:
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
在深度转换中,嵌套的大小写class也变成了一条记录:
'name ->> "Jane" :: 'address ->> ('street ->> "street address" :: 'zip ->> 12345 :: HNil) :: HNil
我也有兴趣将记录转换回大小写 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
直接支持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))
深层案例比较棘手,据我所知,使用类型 classes 和 Shapeless 提供的其他工具没有方便的方法来做到这一点,但您可以从 this question (which is now a test case 在 Shapeless 中)做你想做的事。首先是类型 class 本身:
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 和 object 必须一起定义才能伴随。)
这很乱,但它有效:
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))
用于转换回大小写 class 的 to
/ from
语法是必要的,因为任何给定的记录都可能对应大量潜在的大小写 classes,因此我们需要能够指定目标类型,并且由于 Scala 不支持部分应用的类型参数列表,我们必须将操作分为两部分(其中一部分将明确指定其类型,而另一个的类型参数将被推断出来)。