Inheritance of immutable return types when extending traits in multiple levels

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

  •  07-10-2022
  •  | 
  •  

Question

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?

Était-ce utile?

La solution

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"
} 
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top