【问题标题】:Transform one case class into another when the argument list is the same当参数列表相同时,将一个案例类别转换为另一个案例类别
【发布时间】:2015-08-11 18:34:30
【问题描述】:

我有很多类似的案例类,它们的含义不同,但具有相同的参数列表。

object User {
  case class Create(userName:String, firstName: String, lastName: String)
  case class Created(userName:String, firstName: String, lastName: String)
}

object Group {
  case class Create(groupName:String, members: Int)
  case class Created(groupName:String, members: Int)
}

鉴于这种设置,我厌倦了编写接受 Create 类型参数并返回 Created 类型参数的方法。我有大量的测试用例可以做这种事情。

我可以编写一个函数来将一个案例类转换为另一个案例类。该函数将User.Create转换为User.Created

def userCreated(create: User.Create) = User.Create.unapply(create).map((User.Created.apply _).tupled).getOrElse(sys.error(s"User creation failed: $create"))

我不得不为 Group 编写另一个这样的函数。 我真正想要的是一个通用函数,它采用两种类型的案例类和一个案例类的对象并转换为另一种。类似的,

def transform[A,B](a: A):B

此外,此功能不应违背减少样板的目的。如果更易于使用,请随时为该函数建议不同的签名。

【问题讨论】:

  • 你期待类似convert(a).to[B]的东西吗?
  • 重要的是所有这些配对类都具有创建/创建的名称,还是您的要求纯粹是它们可以配对并且一个函数会将任何此类对的第一个类转换为第二个?

标签: scala boilerplate


【解决方案1】:

无形可救!

您可以使用 Shapeless 的 Generic 创建案例类的通用表示,然后可以使用它来完成您想要做的事情。使用LabelledGeneric,我们可以强制类型和参数名称。

import shapeless._

case class Create(userName: String, firstName: String, lastName: String)
case class Created(userName: String, firstName: String, lastName: String)
case class SortOfCreated(screenName: String, firstName: String, lastName: String)

val c = Create("username", "firstname", "lastname")

val createGen = LabelledGeneric[Create]
val createdGen = LabelledGeneric[Created]
val sortOfCreatedGen = LabelledGeneric[SortOfCreated]

val created: Created = createdGen.from(createGen.to(c))

sortOfCreatedGen.from(createGen.to(c)) // fails to compile

【讨论】:

  • 不错的解决方案!然而,有一个可能的陷阱:Generic 通过将对象转换为HList 来工作,它不能确保源对象和目标对象的相应参数名称匹配。因此,如果您有相同类型的不同参数或不同的参数顺序,您将在运行时得到错误的转换结果。 is plenty of java utils 通过反射执行映射将解决此问题。
  • @Aivean 说的很对。取决于您关心参数名称还是只关心类型——在这种情况下,值类会很重要。
  • @Aivean 这可以通过使用LabelledGeneric 直接修复,这需要案例类参数名称和类型及其顺序的精确对应。
  • 我想知道是否可以像在 OP 中定义一个通用函数:def transform[A : Generic,B : Generic](a: A):B。我找不到隐式参数来向编译器证明案例类模式相等。 implicit ev: Generic[A]#Repr =:= Generic[B]#Repr 编译失败。
  • @Kolmar 我尝试定义像transform 这样的函数,但也没有运气。
【解决方案2】:

为了记录,这是我设法实现的最简单的类型安全语法:

implicit class Convert[A, RA](value: A)(implicit ga: Generic.Aux[A, RA]) {
  def convertTo[B, RB](gb: Generic.Aux[B, RB])(implicit ev: RA =:= RB) =
    gb.from(ga.to(value))
}

而且可以这样使用:

case class Create(userName: String, firstName: String, lastName: String)
case class Created(userName: String, firstName: String, lastName: String)

val created = Create("foo", "bar", "baz").convertTo(Generic[Created])

或与LabelledGeneric 相同以实现更好的类型安全:

implicit class Convert[A, RA](value: A)(implicit ga: LabelledGeneric.Aux[A, RA]) {
  def convertTo[B, RB](gb: LabelledGeneric.Aux[B, RB])(implicit ev: RA =:= RB) =
    gb.from(ga.to(value))
}

val created = Create("foo", "bar", "baz").convertTo(LabelledGeneric[Created]))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-07
    • 2016-10-09
    • 1970-01-01
    • 2013-01-23
    • 2021-09-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多