我有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
任何帮助将不胜感激。 感谢。
答案 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)来执行此操作。看到这个答案非常类似于你想要做的事情: