Inheritance of immutable return types when extending traits in multiple levels

StackOverflow https://stackoverflow.com/questions/21593579

  •  07-10-2022
  •  | 
  •  

Frage

I have the following trait hierarchy. TraitA is the root trait, and given that I want my data structures to be immutable, the function commonUpdateFunction() has a generic return type. I am not sure if this is the best way. I have two other traits that extend it, adding two other functions. Some classes extend one, some classes extend the other, but some classes need to extend both.

However, I am now running into a problem where because of that generic type thing I am getting illegal inheritance, when in reality I am only doing it to get the right type when updating the data structure to a new one.

Furthermore it seems I cannot pass TraitA as a parameter because of this generic type.

trait TraitA[T <: TraitA[T]]
{
   self : T =>

   def commonUpdateFunction() : T
}

trait TraitB extends TraitA[TraitB]
{
   def someFunctionB() : Integer = { /// some code }
}

trait TraitC extends TraitA[TraitC]
{
   def someFunctionC() : Unit = { /// some code } 
}

class ClassB extends TraitB 
{
   def commonUpdateFunction() : ClassB = { /// some code } 
}

class ClassC extends TraitC
{
   def commonUpdateFunction() : ClassC = { /// some code }
} 

class ClassA extends TraitB with TraitC  //**this causes illegal inheritance**
{
   def commonUpdateFunction() : ClassA = { /// some code }
} 

What is the right way to achieve this inheritance on 2 traits while at the same time having immutable updating of data structures with the right types?

War es hilfreich?

Lösung

Type parameters aren't your problem here, the problem is that ClassA tries to mix in three copies of commonFunction(), which differ only by return type:

class ClassA extends TraitB with TraitC {
  def commonFunction() : ClassA = { /// some code }
  def commonFunction() : ClassB = { /// some code } 
  def commonFunction() : ClassC = { /// some code } 
} 

Although the JVM does allow overloading on return type, it isn't permitted at compile time - there's just too much chance for confusion (especially if type inference is involved).

The solution is often to use f-bounded polymorphism (as you did with commonUpdateFunction()), but it's impossible to show how to do that here given that all your commonFunction() definitions are concrete.

It would help a lot to see more "real" code!


UPDATE: Based on new information from the comments.

Instead of a type parameter you might find life easier with an abstract type member. Using Repr (for "Repr"esentation) is a common enough convention and used in the collections lib; make sure that this abstract type member has a bound!

Stick the other common attributes in here as well:

trait Employee {
  type Repr <: Employee

  def name : String
  def id   : Int

  def withName(name: String) : Repr
  def withId(id: Int) : Repr
}

sub-traits follow a similar pattern. There's no need to re-declare other abstract members that keep their signature. You can also introduce other members as you refine the types here.

trait ManagingEmployee extends Employee {
  type Repr <: ManagingEmployee
  def numberOfReports: Int
  def withNumberOfReports(x: Int) : Repr
}

trait SkilledEmployee extends Employee {
  type Repr <: SkilledEmployee 
  def skill: String
}

Now make the leaf nodes of our type tree concrete. case classes work well here, though there will sadly be some duplication (macros could possibly help, but that's a different question).

Note how name and id are made concrete by the class params, the Repr type is made explicit via the = sign, and the abstract methods have to be explicitly re-defined in each leaf class:

case class HrManager(
  name            : String,
  id              : Int,
  numberOfReports : Int
) extends ManagingEmployee {
  type Repr = HrManager
  def withName(name: String) = this.copy(name = name)
  def withId(id: Int) = this.copy(id = id)
  def withNumberOfReports(x: Int) = this.copy(numberOfReports = id)
}

case class Technician(name: String, id: Int) extends SkilledEmployee {
  type Repr = Technician
  def withName(name: String) = this.copy(name = name)
  def withId(id: Int) = this.copy(id = id)
  val skill = "programming"
} 

case class TechnicalManager(
  name            : String,
  id              : Int,
  numberOfReports : Int
) extends SkilledEmployee with ManagingEmployee {
  type Repr = TechnicalManager 
  def withName(name: String) = this.copy(name = name)
  def withId(id: Int) = this.copy(id = id)
  def withNumberOfReports(x: Int) = this.copy(numberOfReports = id)
  val skill = "software architecture"
} 
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top