【问题标题】:Generically converting between two case classes of the same shape在相同形状的两个案例类之间进行一般转换
【发布时间】:2019-09-18 12:37:02
【问题描述】:

我有一堆案例类,它们在其他密封特征中具有相同形状的对应物(每个密封特征都用于 Akka Typed 行为中的详尽模式匹配),我想从一个版本转换为具有最少样板的下一个版本。

特征看起来像这样:

object RoutingCommands {
  sealed trait Command
  final case class ProtocolMsg(name: String, id: Int) extends Command
}

object ProtocolCommands {
  sealed trait Command
  final case class ProtocolMsg(name: String, id: Int) extends Command
}

我知道我可以像这样使用shapeless.Generic 进行转换:

val msg1 = ProtocolCommands.ProtocolMsg("foo", 1)
val msg2 = Generic[RoutingCommands.ProtocolMsg].from(
  Generic[ProtocolCommands.ProtocolMsg].to(msg1)
)

但是对于每次转换都必须这样做是更多样板,而不仅仅是 手工构建案例类。理想情况下,我想要一个转换器,它可以根据编译时提供的两种类型派生上述代码,例如val msg2 = convert(msg1)

作为迈出的一步,我试图将其分解为:

def convert[A,B](a: A): B = Generic[B].from(
  Generic[A].to(a)
)

但这会导致:

Error:(55, 44) could not find implicit value for parameter gen: shapeless.Generic[B]

从四处挖掘,看来我需要使用Generic.Aux 这导致我:

def convert[A, B, HL <: HList](a: A)(
  implicit
  genA: Generic.Aux[A, HL],
  genB: Generic.Aux[B, HL]
) = genB.from(genA.to(a))

当调用时:

val msg3 = convert(msg2)

结果:

Error:(61, 57) could not find implicit value for parameter genB: shapeless.Generic.Aux[B,HL]

这是可以理解的,因为没有定义返回类型。但是,我想出了如何提示B 是什么,以便可以隐式推导出genB

【问题讨论】:

    标签: scala shapeless case-class


    【解决方案1】:

    你可以使用“部分申请”

    def convert[A, HL <: HList](a: A)(
      implicit
      genA: Generic.Aux[A, HL]
    ) = new Helper(a, genA)
    
    class Helper[A, HL <: HList](a: A, genA: Generic.Aux[A, HL]) {
      def apply[B](implicit genB: Generic.Aux[B, HL]) = genB.from(genA.to(a))
    }
    
    val msg3 = convert(msg2).apply[ProtocolCommands.ProtocolMsg]
    

    (最好使用@Ben 的回答中的“部分应用程序”)

    或者创建一个类型类

    trait Convert[A, B] {
      def apply(a: A): B
    }
    
    object Convert {
      implicit def mkConvert[A, B, HL <: HList](implicit
        genA: Generic.Aux[A, HL],
        genB: Generic.Aux[B, HL]
      ): Convert[A, B] = a => genB.from(genA.to(a))
    }
    
    implicit class ConvertOps[A](a: A) {
      def convert[B](implicit cnv: Convert[A, B]): B = cnv(a)
    }
    
    val msg3 = msg2.convert[ProtocolCommands.ProtocolMsg]
    

    https://books.underscore.io/shapeless-guide/shapeless-guide.html#sec:ops:migration "6.3 案例研究:案例类迁移"

    【讨论】:

    • 喜欢类型类方法。谢谢!是时候阅读更多无形的文档以更好地了解这一切是如何工作的
    【解决方案2】:

    正如您所建议的,问题在于未指定结果类型且无法推断。您可以通过显式提供类型参数来解决此问题,如

    val msg3 = convert[ProtocolCommands.ProtocolMsg, RoutingCommands.ProtocolMsg, String :: Int :: HNil](msg2)
    

    但这显然违背了使用 Shapeless 的意义。编译器只需要显式指定返回类型,并且可以推断其他类型,但 Scala 不直接支持显式仅提供类型参数的子集。

    如上一个答案中所述,您可以使用“部分应用”模式使用部分应用来解决此限制。如果您使用在返回类型上参数化的类(这是您需要指定的)而不是输入类型,则此方法效果最佳:

    def convert[B] = new ConvertPartiallyApplied[B]
    
    class ConvertPartiallyApplied[B] {
      def apply[A, Repr](a: A)(implicit genA: Generic.Aux[A, Repr], genB: Generic.Aux[B, Repr]) = genB.from(genA.to(a))
    }
    

    然后可以简单地与

    一起使用
    convert[RoutingCommands.ProtocolMsg](msg2)
    

    【讨论】:

      猜你喜欢
      • 2018-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-08
      • 2019-08-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多