【问题标题】:Generically morph a class into another using Shapeless + LabelledGenerics使用 Shapeless + LabelledGenerics 将一个类泛型为另一个类
【发布时间】:2017-10-06 18:49:51
【问题描述】:

我知道使用 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))

但它并没有真正起作用

【问题讨论】:

    标签: scala shapeless type-level-computation labelled-generic


    【解决方案1】:

    有两个限制会阻止类型推断按照您在示例中想要的方式工作(两者都与无形 btw 无关):

    1. 在当前scalac 中明确指定类型参数是全部或全部。但是您只想指定B,其余的要推断。 Currying 是解决这个问题的一种方法。所以你的尝试是在正确的轨道上,但没有考虑到 2。

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

    所以从 1. 和 2. 得出,你必须 curry 两次

    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 有一个替代解决方案。- 使用 dummy argument 指定类型参数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)
        }
      }
    }
    

    【讨论】:

      【解决方案2】:
      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)
      

      【讨论】:

        【解决方案3】:

        根据@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)
        

        我们也可以让它像这样处理re-orderings of fields

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-10-31
          • 1970-01-01
          • 2014-08-02
          • 2011-08-09
          • 2021-12-25
          • 2014-12-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多