Behaviour of super in chained Scala traits

2020-03-24 06:36发布

问题:

Why does x.func below return "B extends B extends B"? How to arrange this code so that it returns "B extends A extends Base"?

trait Base {
  def name = "Base"
  def func = name
}

trait A extends Base {
  override def name = "A"
  override def func = name + " extends " + super.func
}

trait B extends Base {
  override def name = "B"
  override def func = name + " extends " + super.func
}

val x = new Base with A with B
println(x.func)

Update: One arrangement could be as follows. It now has identical definitions of func1 in A and B. It does not work if I try to move it to the Derived class. Any ideas how to remove the repetition of func1?

trait Base {
  def name = "Base"
  def func1(s: String) = s
}

trait Derived extends Base {
  def func = func1(name)
}

trait A extends Derived {
  override def func1(s: String) = s + " extends " + super.func1(super.name)
  override def name = "A"
}

trait B extends Derived {
  override def func1(s: String) = s + " extends " + super.func1(super.name)
  override def name = "B"
}

val x = new Base with A with B
println(x.func)

回答1:

I think the inheritance order may actually be the one your are seeking. If you replace the " extends " with one that shows which method of which trait is called:

trait Base {
  def name = "Base"
  def func = "Base." + name
}

trait A extends Base {
  override def name = "A"
  override def func = name + " A.extends " + super.func
}

trait B extends Base {
  override def name = "B"
  override def func = name + " B.extends " + super.func
}

val x = new Base with A with B
println(x.func)
// B B.extends B A.extends Base.B

It's just that name is always "B". In other words:

trait Base { def func = "Base" } 
trait A extends Base { override def func = "A extends " + super.func }
trait B extends Base { override def func = "B extends " + super.func }
val x = new Base with A with B
println(x.func)
// B extends A extends Base

which is what you want...

The full linearization of your example is:

Object, B, A, Base, ScalaObject, AnyRef, Any

(see http://ofps.oreilly.com/titles/9780596155957/ScalaObjectSystem.html#Linearization for a practical explanation on how to figure out the linearization)

Edit to answer comment: why is the name always returns "B"? That's because the def name method is overridden by the trait B to return "B". That's the whole point of inheritance, to be able to use in superclasses behavior that is refined in subclasses:

trait Legs { 
  def legs: Int 
  def printLegs() { println("I have " + legs + " legs") }
}

class Dog extends Legs { def legs = 4 }
class Chicken extends Legs { def legs = 2 }

new Dog printLegs
// I have 4 legs
new Chicken printLegs
// I have 2 legs

legs in trait Legs is not a separate one than legs in Dog depending on if you refer to it in Legs or Dog... Similarly, your def name will always return "B" if your object is a B.

It looks like you want to use name as a private method:

trait Base { 
  private def _name() = "Base"
  def func = _name
} 
trait A extends Base { 
  private def _name() = "A"
  override def func = _name + " extends " + super.func
}
trait B extends Base { 
  private def _name() = "B"
  override def func = _name + " extends " + super.func
}
val x = new Base with A with B
println(x.func)
// B extends A extends Base

I find that if there isn't a clear object model, using trait and inheritance quickly gets complicated. I assume you sanitized/simplified the example to use generic names like A, B, Base, func so that you get to the core of the issue, but on the other hand it doesn't give me any insights on what changes you could do to make it work for you. As you asked I've arrange the code so that it prints "B extends A extends Base". I'm sure there are a bunch of other constraints that aren't in the question why it won't work for you.



标签: scala traits