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

时间:2017-03-25 12:39:46

标签: scala pattern-matching traits case-class union-types

我有trait这样:

trait Identifiable {

  def id: Option[Long]

}

然后还有一些其他case class es扩展了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

任何帮助将不胜感激。 感谢。

2 个答案:

答案 0 :(得分:2)

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

this answer的启发,我修改了提供的使用无形镜片的例子:

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]

现在,我们可以免费修改任何类型id的{​​{1}}:

Identifable[T]

收率:

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)

关于在@Kolmar提供的链接中汇总这个答案的各个部分有一个很好的解释,所以首先要阅读透镜如何适用于另一个答案(非常相似)的详细信息,然后来回到这个参考最小的工作示例。

另请参阅@Jasper-M answer here了解更多完成相同的方法。

答案 1 :(得分:2)

联盟类型不是正确的道路。考虑:

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

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

下一步:

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

当您使用EntityB说EntityA时,您说“x是同时属于EntityA和EntityB的类型”。不存在这样的类型,当然也不存在具有复制方法的类型。

不幸的是,我认为你不能像普通的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)来执行此操作。看到这个答案非常类似于你想要做的事情:

Case to case inheritence in Scala