Class Linearization in Scala

Scala has a wealth of language constructs that support modern object-oriented programming. Class Linearization stands in the heart of its object model. Section 12.6 in Artima's Programming Scala is an invaluable resource. Section 7.5 in Oreilly's Programming Scala also provides a succint explaination, though its full content is not freely available online. In this article, I will try to explain what Class Linearization is and how to make it speak for our design rather than have it get in the way.

Let's start with Multiple Inheritance. Whether MI is bad is an unsettled debate(for example: http://programmers.stackexchange.com/questions/218458/is-there-any-real-reason-multiple-inheritance-is-hated). Some languages like Java opt for single inheritance and interfaces to provide some functionalities of MI(http://en.wikipedia.org/wiki/Multiple_inheritance). E.g.

class ButtonClass extends WidgetClass 
  implements ColorInterface, EffectInterface

An obvious shortcoming of this approach is that since no implementations are allowed in interfaces, the deriving class has to implement everything which is against code reuse. Java offers abstract class that could have implementation, but at the end of day it's still a class hence is restricted to be extended only.

The desire of having interfaces that contain implementation leads us to trait(another solution is mixin http://stackoverflow.com/questions/925609/mixins-vs-traits). Class Linearization is really the magic behind Scala's object model that weaves classes and traits altogether. A little bit pedantic description:

...inheritance hierarchy forms a directed, acyclic graph (see [ScalaSpec2009]). The term linearization refers to the algorithm used to "flatten" this graph for the purposes of resolving method lookup priorities, constructor invocation order, binding of super, etc.
~ Programming Scala

In short, Class Linearization flattens types in a DAG to a linear hierarchy. I'll try to visualize what it means. The syntax of trait and class roughly looks like this:

trait T [extends (C | T) (with T)*] [body]
class C [extends (C | T) (with T)*] [body]

class linearization

  • The construct/initialize order follows the linearized hierarchy from top to bottom. E.g. in the figure, C1, T2, C2, T3, T1, then CA.

  • The mixed class in which trait/class A mixes is everything from the top util A in the linearized hierarchy. E.g. T3 mixes in "C1 >: T2 >: C2".

  • When trait A extends class/trait B, it implies that the mixed class in which we mix A must be a subclass of B. To put it another way, B must appear somewhere above A in the linearized hierarchy. Therefore, trying the example of CB would give the following error.

    scala> class CB extends C2 with T1 with T3 with T4
    <console>:14: error: illegal inheritance; superclass C2
    is not a subclass of the superclass C3
    of the mixin trait T4
    
  • In addition, if we see trait A { self: B => ...}:

    trait B { val bar = "B" }
    trait A { self: B =>
    val foo = self.bar
    }
    

    It implies the concrete class must be a subclass of B. That is, B must appear anywhere in the linearized hierarchy. We should be careful about object construction/initialization. Becase if B appears below A in the linearized hierarchy, A's initialization that reads uninitialized B's feilds would get nothing.

// init/construct order: B -> A -> $anon$
scala> val c = new B with A
c: B with A = $anon$1@2efe33c7
scala> c.foo
res0: String = B
scala> c.bar
res1: String = B    
// init/construct order: A -> B -> $anon$
scala> val d = new A with B
c: A with B = $anon$1@9904c66
scala> d.foo
res2: String = null  // *nothing*
scala> d.bar
res3: String = B
  • Lastly, Scala also gives us abstract override. If I abstract override T1's "bar" in "trait T2 extends T1", the mixed class in which T2 mixes must have overriden this method too and I'll need it in my version of "bar".
    trait T1 {
    def foo: String = "T1 "
    def bar: String
    }
    trait T2 extends T1 {
    // 'super.foo' can be safely accessed since it already has a "default" definition in T1(which could be overriden by the mixed class).
      override def foo = "T2 " + super.foo
    // 'super.bar' has not defined from T2's perspective. Adding "abstract" to make sure the mixed class must have defined it. abstract override def bar = "T2 " + super.bar } class C1 extends T1 { override def foo = "C1 " + super.foo def bar = "C1 " }
    class linearization
    // T1 >: C1 >: T2 >: $anon$
    scala> val c1 = new C1 with T2
    c1: C1 with T2 = $anon$1@6e39dcd1
    scala> c1.foo
    res0: String = "T2 C1 T1 "
    scala> c1.bar
    res1: String = "T2 C1 "
    

That's what I undertand so far about Scala's Class Linearization. Please take a pinch of salt and feel free to point out any incorrect information.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值