I have the following code in Scala:
trait Component {
def state : String
def name: String
}
case class AComponent( id : String) extends Component {
def state = name + ":" + id
def name = "A"
}
trait ComponentDecoratorA extends Component {
abstract override def name = "ByADecorated:" + super.name
}
trait ComponentDecoratorB extends Component {
abstract override def name = "ByBDecorated:" + super.name
}
object Run
{
def main (args : Array[String]) = {
val c = new AComponent ("42") // static decoration
with ComponentDecoratorA with ComponentDecoratorB
println( c.state)
}
The output is:
ByBDecorated:ByADecorated:A:42
I am new in Scala, but I know that we can inherit from the trait in object creation to limit the trait to the object. But as I have understood it correctly we are inheriting from ComponentDecoratorA and ComponentDecoratorB when creating the object. But why don't we get a conflict for the name method? And the output shows that the name methods of all three classes are called. How can this happen?
val c = new AComponent ("42") // static decoration
with ComponentDecoratorA with ComponentDecoratorB
Why do we need new although we are using a case class?
And how does it get the result ByBDecorated:ByADecorated:A:42?
For the first question, "why can we inherit here?", it's simply because you are allowed to. This creates an object that inherits from both
AComponentand the decorators, and if you compile this you'll find there's an anonymous class that was generated holding this object's class's code.Second, on why you need to use
new.AComponent(x)is syntax sugar forAComponent.apply(x). It is not (directly) sugar fornew AComponent(x).AComponent.applyis an automatically generated method inobject AComponentthat looks like this:Calling it will only ever give you back a plain old
AComponent, and it will not be possible to mix in traits because that is only possible when defining a new type (e.g.class Foo extends A with B) or when using a constructor (e.g.new AComponent("42") with ComponentDecoratorA with ComponentDecoratorB).Finally, the compiler performs something known as type linearization to "flatten" the hierarchy of traits and classes something inherits from into a sequence. For
AComponent with CDA with CDBthe linearization order is:The thing that allows
CDBto callsuper.namewhile overridingnameitself isabstract override. It means thatCDB, at the same time, overrides whatnameis, and also requires that someone else provide an implementation forsuper.name. This is useful in the stackable trait pattern.When a method is called, a right-to-left search is performed on that order to find what exactly should be called. So
CDB#namecallsCDA#namecallsAComponent#name, and thenCDA#nameprepends "ByADecorated:", and thenCDB#nameprepends "ByBDecorated:".Also, it is impossible (well, highly difficult and very dangerous) to mix in traits at runtime, as the question title implies. This is all done at compile time by generating an anonymous class.