Scala: implementing method with return type of concrete instance

Rui Gonçalves picture Rui Gonçalves · Feb 6, 2013 · Viewed 7.1k times · Source

I need a way to enforce a method in an abstract class to have a return type of the concrete class of the object it is called on. The most common example is a copy() method, and I'm currently using an approach based on abstract types:

abstract class A(id: Int) {
  type Self <: A
  def copy(newId: Int): Self
}

class B(id: Int, x: String) extends A(id) {
  type Self = B
  def copy(newId: Int) = new B(newId, x)
}

class C(id: Int, y: String, z: String) extends A(id) {
  type Self = C
  def copy(newId: Int) = new C(newId, y, z)
}

I already saw many approaches, including the ones in this great answer. However, none of them really forces a implementation to return its own type. For example, the following classes would be valid:

class D(id: Int, w: String) extends A(id) {
  type Self = A
  def copy(newId: Int) = new D(newId, w) // returns an A
}

class E(id: Int, v: String) extends A(id) {
  type Self = B
  def copy(newId: Int) = new B(newId, "")
}

The fact that I can do that causes that, if I am doing copies of objects of which the only information I have is that they are of a given subclass of A's:

// type error: Seq[A] is not a Seq[CA]!
def createCopies[CA <: A](seq: Seq[CA]): Seq[CA] = seq.map(_.copy(genNewId()))

Is there a better, type-safe way I can do that?

EDIT: If possible, I would like to keep the ability to create arbitrarily deep hierarchies of abstract classes. That is, in the previous example, I'm expecting to be able to create an abstract class A2 that extends A, and then proceed to create A2's concrete subclasses. However, if that simplifies the problem (as it's the case with abstract types), I do not need to further extend already concrete classes.

Answer

EECOLOR picture EECOLOR · Feb 16, 2013

The only solution I could think of was this one:

trait CanCopy[T <: CanCopy[T]] { self: T =>
  type Self >: self.type <: T
  def copy(newId: Int): Self
}

abstract class A(id: Int) { self:CanCopy[_] =>
  def copy(newId: Int): Self
}

The following would compile:

class B(id: Int, x: String) extends A(id) with CanCopy[B] {
  type Self = B
  def copy(newId: Int) = new B(newId, x)
}

class C(id: Int, y: String, z: String) extends A(id) with CanCopy[C] {
  type Self = C
  def copy(newId: Int) = new C(newId, y, z)
}

The following would not compile:

class D(id: Int, w: String) extends A(id) with CanCopy[D] {
  type Self = A
  def copy(newId: Int) = new D(newId, w) // returns an A
}

class E(id: Int, v: String) extends A(id) with CanCopy[E] {
  type Self = B
  def copy(newId: Int) = new B(newId, "")
}

Edit

I actually forgot to remove the copy method. This might be a bit more generic:

trait StrictSelf[T <: StrictSelf[T]] { self: T =>
  type Self >: self.type <: T
}

abstract class A(id: Int) { self:StrictSelf[_] =>
  def copy(newId:Int):Self
}