Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: How can I make my immutable classes easier to subclass?

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:

  1. Use delegation - but of course I'd still be re-implementing all the methods as delegate calls
  2. Use implicit types to add operations instead of subclassing
  3. Use a mutable data structure. This is probably the simplest and quickest solution, but I'd lose the benefits of using immutable data structures (which I'm still hoping to learn more about).

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

like image 976
Dobes Vandermeer Avatar asked Aug 29 '11 08:08

Dobes Vandermeer


People also ask

Can immutable classes be subclassed?

If you want to enforce immutability, you cannot have subclasses. See for example java. lang. String, which is a final class for this reason: To prevent people from subclassing String to make it mutable.

Can you inherit from an immutable class?

If the immutable class can be inherited, the subclass can change the methods to modify the instances of the class, so an immutable class cannot allow this.

What is subclass in Scala?

Subclass. A class that inherits behaviors from another class, called superclass. In Scala, a class can only up have up to one superclass. Superclass. A class whose methods are inherited by one or more classes, called subclasses.


1 Answers

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)
}
like image 109
Jean-Philippe Pellet Avatar answered Sep 18 '22 17:09

Jean-Philippe Pellet