Reputation: 8820
I've recently created an immutable class supporting operations like +, -, etc. that returns a new instance of that class when it is changed.
I wanted to make a subclass of that class to add a bit of state and functionality but now I'm running into a problem in that all the original class's methods return instances of itself rather than the subclass.
Based on my current limited knowledge of Scala I can come up with this:
class Foo(val bar:Int) {
def copy(newBar:Int) = new Foo(newBar)
def + (other:Foo):This = copy(this.bar + other.bar)
}
class Subclass(barbar:Int) extends Foo(barbar) {
override def copy(newBar:Int) = new Subclass(newBar)
override def + (other:Subclass) = super.+(other).asInstanceOf[Subclass]
}
The problem here is quite obvious - all operations of the superclass that return a new instance of have to be re-defined in the subclass with a cast.
At first "this.type" seemed promising but "this.type" only includes "this" and not any other object of the same type.
Is there a standard pattern for making immutable classes easy to subclass? Something like:
class Foo(val bar:Int) {
def copy(newBar:Int):SameType = new Foo(newBar)
def + (other:Foo) = copy(this.bar + other.bar)
}
class Subclass(barbar:Int) extends Foo(barbar) {
override def copy(newBar:Int):SameType = new Subclass(newBar)
override def + (other:Subclass) = super.+(other).asInstanceOf[Subclass]
}
This particular approach would require the compiler to require that all subclasses implement a copy() method that returns the same type as that subclass, which would be perfectly fine with me. However, I don't think any such thing exists in Scala at this time.
Some work-arounds that come to mind are:
I'm sure this has been discussed many times already and I apologize for asking again. I did Google for a duplicate question without success, so my search terms must have been poorly constructed.
Thanks in advance,
Dobes
Upvotes: 14
Views: 800
Reputation: 60006
You could use an implementation trait, like the collection classes do, which is parametrized by the concrete type. E.g., something like:
trait FooLike[+A] {
protected def bar: Int
protected def copy(newBar: Int): A
def +(other: Foo): A = copy(bar + other.bar)
}
class Foo(val bar: Int) extends FooLike[Foo] {
protected def copy(newBar: Int): Foo = new Foo(newBar)
}
class Subclass(barbar: Int) extends Foo(barbar) with FooLike[Subclass] {
protected def copy(newBar: Int): Subclass = new Subclass(newBar)
}
Upvotes: 9