Scala: Object inherits on runtime

431 views Asked by At

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?

2

There are 2 answers

0
nairbv On

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/

0
HTNW On

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.