Reputation: 5449
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?
Upvotes: 0
Views: 437
Reputation: 4323
When you say new Class1 with Trait1
you are creating the equivalent of an anonymous class in Java. E.g. in Java you could say new Class1(){ /* add additional implementation details here*/ }
In terms of the order of inclusions when combining the this.name + this.super.name
strings, that's called the "diamond problem" and is solved by what Scala calls "type linearization:" https://www.safaribooksonline.com/blog/2013/05/30/traits-how-scala-tames-multiple-inheritance/
Upvotes: 0
Reputation: 29193
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 AComponent
and 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 for AComponent.apply(x)
. It is not (directly) sugar for new AComponent(x)
. AComponent.apply
is an automatically generated method in object AComponent
that looks like this:
object AComponent extends (String => AComponent) {
// Overrides the one in (^ is sugar for >)Function1[String, AComponent]
override def apply(id: String): AComponent = new AComponent(id)
}
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 CDB
the linearization order is:
Component <- AComponent <- CDA <- CDB
The thing that allows CDB
to call super.name
while overriding name
itself is abstract override
. It means that CDB
, at the same time, overrides what name
is, and also requires that someone else provide an implementation for super.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#name
calls CDA#name
calls AComponent#name
, and then CDA#name
prepends "ByADecorated:", and then CDB#name
prepends "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.
Upvotes: 3