Scala - 模式匹配中的联合类型

Scala - union types in pattern matching

我有一个 trait 这样的:

trait Identifiable {

  def id: Option[Long]

}

然后还有一些其他的 case classes 扩展了 Identifiable 特性。 例如:

case class EntityA(id: Option[Long], name: String, created: Date) extends Identifiable

case class EntityB(id: Option[Long], price: Long, count: Int) extends Identifiable

假设我有一个 Seq[Identifiable] 并且我想为每个分配新的 id

最简单的方法似乎是:

val xs: Seq[Identifiable] = ...
xs.map {
  case x: EntityA => x.copy(id = Some(nextId))
  case x: EntityB => x.copy(id = Some(nextId))
}

好!但是有一个问题。 子类越多,要写的(重复)代码就越多。

我试图从联合类型获得帮助:

xs.map {
  case x: EntityA with EntityB => x.copy(id = Some(nextId))
}

xs.map {
  case x @ (_: EntityA | _: EntityB) => x.copy(id = Some(nextId))
}

但我收到一条错误消息:Cannot resolve symbol copy

如有任何帮助,我们将不胜感激。 谢谢。

基本上,我们在这里要做的是对实际类型的抽象。问题是 copy 仅根据案例 类 实施 OOTB,而 Identifiable 是一个特征,因此可能有也可能没有可用的 copy 方法编译时间,因此编译器为什么对你大喊大叫。

深受的启发,我修改了提供的使用无形透镜的示例:

import shapeless._

abstract class Identifiable[T](implicit l: MkFieldLens.Aux[T, Witness.`'id`.T, Option[Long]]){
  self: T =>
  final private val idLens = lens[T] >> 'id

  def id: Option[Long]
  def modifyId(): T = idLens.modify(self)(_ => Some(Random.nextLong()))
}

case class EntityA(id: Option[Long], name: String, create: Date) extends Identifiable[EntityA]
case class EntityB(id: Option[Long], price: Long, count: Int) extends Identifiable[EntityB]

现在,我们可以免费修改扩展 Identifable[T] 的任何类型的每个 id

val xs: Seq[Identifiable[_]] = Seq(EntityA(Some(1), "", new Date(2017, 1, 1)), EntityB(Some(2L), 100L, 1))
val res = xs.map(_.modifyId())
res.foreach(println)

产量:

EntityA(Some(-2485820339267038236),,Thu Feb 01 00:00:00 IST 3917)
EntityB(Some(2288888070116166731),100,1)

在上面由@Kolmar 提供的 link 中,关于组装这个答案的各个部分有很好的解释,所以首先去阅读关于镜头如何为另一个答案工作的细节(这是非常类似),然后返回到这里作为最小工作示例的参考。

另请参阅@Jasper-M 以了解更多实现相同目标的方法。

联合类型不是这里的正确路径。考虑:

xs.map {
  case x @ (_: EntityA | _: EntityB) => x.copy(id = Some(nextId))
}

当你说 EntityA | EntityB Scala 将尝试找到将这两种类型结合在一起的超类型。在这种情况下,它是 Identifiable,它没有复制方法,因此编译器无法解析它。

下一个:

xs.map {
  case x: EntityA with EntityB => x.copy(id = Some(nextId))
}

当您说 EntityA 和 EntityB 时,您说的是 "x is a type that is both an EntityA and EntityB at the same time"。不存在这样的类型,当然也不存在具有复制方法的类型。

不幸的是,我认为您不能像您希望在普通 Scala 中那样对复制方法进行一般性抽象。我认为你最好的选择是向你的特征添加一个复制方法并在你的每个子 类 中实现方法,就像这样,不幸的是这意味着一些样板:

trait Identifiable {
  def id: Option[Long]
  def copyWithNewId(newId: Option[Long]): Identifiable
}

case class EntityA(id: Option[Long], name: String) extends Identifiable {
  override def copyWithNewId(newId: Option[Long]) = this.copy(id = newId)
}

case class EntityB(id: Option[Long], count: Int) extends Identifiable {
  override def copyWithNewId(newId: Option[Long]) = this.copy(id = newId)
}

除了将复制调用移动到实体本身之外,这或多或少与您的工作模式匹配有关。

现在这只适用于普通的 Scala。您可以使用更高级的库,例如 Shapeless 或 Monocle 来执行此操作。请参阅此答案,它与您正在尝试执行的操作非常相似: