Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: how to refer to the type of an extending class in a superclass?

Tags:

types

scala

Is there a way to define a type T in Parent such that T will always become the actual type of the extending class (in this case Child)?

In Parent, I want to enforce/declare T to be always the extending type, as if I would have written type T="type_of_the_extending_class" in each actual extending class without actually writing down the lines type T=Child1 in Child1 etc.

So Child1's method only should accept Child1 objects as parameter and Child2's method should only accept Child2 objects. Is there a simpler way to enforce this? Is there a way without writing type T=ChildX in each ChildX class ? Is there a way without this boilerplate?

I've been searching for a solution in Scala books but did not find any.

abstract class Parent{
  type T<:Parent
  def method(t:T) 
}

class Child1 extends Parent{
  type T=Child1
  override def method(t:T)=t.child1method
  def child1method=println("child1's method")
}

class Child2 extends Parent{
  type T=Child2
  override def method(t:T)=t.child2method
  def child2method=println("child2's method")
}
like image 762
jhegedus Avatar asked Feb 13 '23 20:02

jhegedus


1 Answers

The standard solution to this problem is F-bounded polymorphism (which isn't Scala-specific—you'll find it used in Java, etc.):

trait Parent[T <: Parent[T]] {
  def method(t: T)
}

class Child1 extends Parent[Child1] {
  def method(t: Child1) = println("child1's method")
}

class Child2 extends Parent[Child2] {
  def method(t: Child2) = println("child1's method")
}

As a side note, there's been some grumbling about F-bounded polymorphism in the Scala community—Kris Nuttycombe for example says it's "tricky to get right and causes typing clutter in the codebase", and I've personally found that I use it less and less often after several years of writing Scala. When your program architecture leads you to need this kind of inheritance, though, it's exactly the right tool for the job.

The problem with this.type (mentioned in the comments) is that it won't allow you to do most of the things you'd reasonably want to do—it's too specific:

scala> abstract class Parent {
     |   def method(t: this.type)
     | }
defined class Parent


scala> class Child1 extends Parent {
     |   def method(t: this.type) = println("child1's method")
     | }
defined class Child1

scala> val a = new Child1
a: Child1 = Child1@19517e9a

scala> val b = new Child1
b: Child1 = Child1@5737e545

scala> a.method(b)
<console>:12: error: type mismatch;
 found   : b.type (with underlying type Child1)
 required: a.type
              a.method(b)
                       ^

The only argument we can pass to a.method is a itself.

like image 113
Travis Brown Avatar answered Mar 23 '23 00:03

Travis Brown