Scala copy case class with generic type

假如想象 提交于 2021-02-07 05:27:05

问题


I have two classes PixelObject, ImageRefObject and some more, but here are just these two classes to simplify things. They all are subclasses of a trait Object that contains an uid. I need universal method which will copy a case class instance with a given new uid. The reason I need it because my task is to create a class ObjectRepository which will save instance of any subclass of Object and return it with new uid. My attempt:

trait Object {
  val uid: Option[String]
}

trait UidBuilder[A <: Object] {
  def withUid(uid: String): A = {
    this match {
      case x: PixelObject => x.copy(uid = Some(uid))
      case x: ImageRefObject => x.copy(uid = Some(uid))
    }
  }
}

case class PixelObject(uid: Option[String], targetUrl: String) extends Object with UidBuilder[PixelObject]

case class ImageRefObject(uid: Option[String], targetUrl: String, imageUrl: String) extends Object with UidBuilder[ImageRefObject]

val pix = PixelObject(Some("oldUid"), "http://example.com")

val newPix = pix.withUid("newUid")

println(newPix.toString)

but I am getting the following error:

➜  ~  scala /tmp/1.scala
/tmp/1.scala:9: error: type mismatch;
 found   : this.PixelObject
 required: A
      case x: PixelObject => x.copy(uid = Some(uid))
                                   ^
/tmp/1.scala:10: error: type mismatch;
 found   : this.ImageRefObject
 required: A
      case x: ImageRefObject => x.copy(uid = Some(uid))
                                      ^
two errors found

回答1:


I would stick with the solution proposed by Seam. I have done the same a couple of months ago. For example:

trait Entity[E <: Entity[E]] {
  // self-typing to E to force withId to return this type
  self: E => def id: Option[Long]
  def withId(id: Long): E
}
case class Foo extends Entity[Foo] {
  def withId(id:Long) = this.copy(id = Some(id))
}

So, instead of defining an UuiBuilder with a match for all implementations of your trait, you define the method in your implementation itself. You probably don't want to modify UuiBuilder every time you add a new implementation.

In addition, I would also recommend you to use a self typing to enforce the return type of your withId() method.




回答2:


Surely a better solution would be to actually utilise the subtyping?

trait Object {
  val uid: Option[String]
  def withNewUID(newUid: String): Object
}



回答3:


Casting to A does the trick - probably due to the recursive definition of your case classes.

trait UidBuilder[A <: Object] {
  def withUid(uid: String): A = {
    this match {
      case x: PixelObject    => x.copy(uid = Some(uid)).asInstanceOf[A]
      case x: ImageRefObject => x.copy(uid = Some(uid)).asInstanceOf[A]
    }
  }
}

Maybe there's a more elegant solution (except - well implementing the withUid for each case class, which I think is not what you asked for), but this works. :) I think it's maybe not a straightforward idea doing this with the UidBuilder, but it's an interesting approach nonetheless.

To make sure you don't forget a case - and I take it all the needed case classes are in the same compilation unit anyway - make your Object a sealed abstract class and add another cast

this.asInstanceOf[Object]

If you leave out a case for one of your case classes then, you will get a warning.



来源:https://stackoverflow.com/questions/15441589/scala-copy-case-class-with-generic-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!