使用Shapeless+LabelledGenerics将一个类泛型变形为另一个类



我知道使用Shapeless我可以做这样的事情:

import shapeless._, syntax.singleton._, record._
case class Foo(x: Int, y: String)
case class RichFoo(x: Int, y: String, z: Double)
def makeRich(foo: Foo): RichFoo = {
val x = ('z ->> 0.9)
val repr = LabelledGeneric[Foo].to(foo) + x
LabelledGeneric[RichFoo].from(repr)
}
val a = Foo(1, "hello")
val b = makeRich(a)

现在我想写一个通用的方法来做到这一点:

trait Morph[A, B, AR, BR] {
def apply(a: A)(f: AR => BR): B
}
object Morph {
implicit def genericMorph[A, B, AR, BR](implicit genA: LabelledGeneric.Aux[A, AR], genB: LabelledGeneric.Aux[B, BR]): Morph[A, B, AR, BR] =
new Morph[A, B, AR, BR] {
override def apply(a: A)(f: AR => BR) = genB.from(f(genA.to(a)))
}
implicit class Syntax[A](a: A) {
def morph[AR, BR, B](f: AR => BR)(implicit morph: Morph[A, B, AR, BR]): B =
morph(a)(f)
}
}

但是,现在的用法是不可逆的?

val a = Foo(1, "hello")
a.morph[???, ???, RichFoo](_ + ('z ->> 0.9))

设计此API的更好方法是什么?

我试过这样的东西:

implicit class Syntax[A](a: A) {
def morphTo[B] = new {
def using[AR <: HList, BR <: HList](f: AR => BR)(implicit morph: Morph[A, B, AR, BR]): B =
morph(a)(f)
}
}
a.morphTo[RichFoo].using(_ :+ ('z ->> 0.9))

但它并不能真正起作用

有两个限制阻止类型推理以您想要的方式在示例中工作(两者都与shapeless btw无关):

  1. 在当前scalac中,显式指定类型参数要么全部要么全部不指定。但您希望仅指定B,其余部分则有待推断Currying是这个问题的一个解决方案。所以你的尝试是正确的,但没有占2。

  2. 方法参数的类型推断从左到右,每次一个参数列表。但是你想根据morph的类型来推断f的类型,因为它是隐式的。这里的解决方案是…再次加密

所以从1开始。和2。因此,您必须咖喱两次

implicit class Syntax[A](a: A) {
def morphTo[B] = new {
def by[AR <: HList, BR <: HList](implicit morph: Morph[A, B, AR, BR]) = new {
def using(f: AR => BR): B = morph(a)(f)
}
}
}
a.morphTo[RichFoo].by.using(_ :+ ('z ->> 0.9))

对于1,有一个替代解决方案。-使用伪参数指定类型参数B:

trait To[-A]
object To {
private val instance = new To[Any] { }
def apply[A]: To[A] = instance
}
implicit class Syntax[A](a: A) {
def morph[B, AR <: HList, BR <: HList](to: To[B])(
implicit morph: Morph[A, B, AR, BR]
) = new {
def using(f: AR => BR): B = morph(a)(f)
}
}
a morph To[RichFoo] using (_ :+ ('z ->> 0.9))

有关如何在Dotty:中解决这些问题的未来参考

  1. 您已经可以部分指定类型参数:a.morph[B = RichFoo]
  2. 目前正在进行更一致的隐式参数语法方面的工作:lampepfl/dotty#1260

编辑:通常将依赖于其他类型的类型定义为类型成员是个好主意:

trait Morph[A, B] {
type AR
type BR
def apply(a: A)(f: AR => BR): B
}
object Morph {
type Aux[A, B, AR0, BR0] = Morph[A, B] {
type AR = AR0
type BR = BR0
}
implicit def genericMorph[A, B, AR0, BR0](
implicit genA: LabelledGeneric.Aux[A, AR0], genB: LabelledGeneric.Aux[B, BR0]
): Aux[A, B, AR0, BR0] = new Morph[A, B] {
type AR = AR0
type BR = BR0
def apply(a: A)(f: AR => BR) = genB.from(f(genA.to(a)))
}
implicit class Syntax[A](a: A) {
def morphTo[B](implicit morph: Morph[A, B]) = new {
def using(f: morph.AR => morph.BR) = morph(a)(f)
}
}
}
import shapeless._, syntax.singleton._, record._
case class Foo(x: Int, y: String)
case class RichFoo(x: Int, y: String, z: Double)
class Morph[A, B, AR](a: A, genA: LabelledGeneric.Aux[A, AR]) {
def apply[BR](f: AR => BR)(implicit genB: LabelledGeneric.Aux[B, BR]) = genB.from(f(genA.to(a)))
}
implicit class Syntax[A, AR](val a: A)(implicit genA: LabelledGeneric.Aux[A, AR]) {
def morph[B]: Morph[A, B, AR] = new Morph(a, genA)
}
val a = Foo(1, "hello")
a.morph[RichFoo](_ + ('z ->> 0.9)) // => RichFoo(1,hello,0.9)

根据@g.krastev的回答,我采用了这种DSL方法:

import shapeless._, syntax.singleton._, record._, ops.hlist._
case class Morph[A, AR](a: A)(implicit reprA: LabelledGeneric.Aux[A, AR]) {
def to[B] = new {
def apply[BR](f: AR => BR)(implicit reprB: LabelledGeneric.Aux[B, BR]): B =
reprB.from(f(reprA.to(a)))
}
}

然后我们可以像这样使用它:

val a = Foo(1, "hello")
val b = Morph(a).to[RichFoo](_ + ('z ->> 0.9)) // => RichFoo(1,hello,0.9)

我们还可以让它处理这样的字段的重新排序:

case class Morph[A, AR](a: A)(implicit reprA: LabelledGeneric.Aux[A, AR]) {
def to[B] = new {       
def apply[BR <: HList, BR2 <: HList](f: AR => BR2)(implicit reprB: LabelledGeneric.Aux[B, BR], align: Align[BR2, BR]): B =
reprB.from(align(f(reprA.to(a))))
}
}

相关内容

  • 没有找到相关文章

最新更新