トレイトの継承の線形化

昨日と同じように、菱形継承問題を考える。

scala> trait TraitA {
     |     def greet(): Unit
     | }
defined trait TraitA

scala> trait TraitB extends TraitA {
     |     override def greet(): Unit = println("Good morning!")
     | }
defined trait TraitB

scala> trait TraitC extends TraitA {
     |     override def greet(): Unit = println("Good evening!")
     | }
defined trait TraitC

昨日と違うのは、TraitB と TraitC で greet メソッドを実装するときに override キーワードをつけているところだ。この場合、つぎのように、単純に2つを継承したクラスを作ってもエラーにならない。

scala> class ClassA extends TraitB with TraitC
defined class ClassA

ここで、ClassA の greet メソッドを呼び出すと何を出力するか。試してみよう。

scala> (new ClassA).greet()
Good evening!

Good evening! と表示された。ということは TraitC の greet メソッドが呼ばれたってことだ。

つぎに、TraitB と TraitC の継承順を入れ替えた ClassB を考えよう。

scala> class ClassB extends TraitC with TraitB
defined class ClassB

scala> (new ClassB).greet()
Good morning!

今度は TraitB の greet メソッドが呼び出されている。

Scala では、こういう形で複数のトレイトを継承(ミックスイン)した場合、あとからミックスインしたトレイトが優先される。この機能をトレイトの線形化と呼ぶ。

さて、ここからがちょっとよくわからない。メソッドの中で super を使うことで親トレイトのメソッドを呼び出すことができる。定義はこんなふうだ。

scala> trait TraitA {
     |     def greet(): Unit = println("Hello!")
     | }
defined trait TraitA

scala> trait TraitB extends TraitA {
     |     override def greet(): Unit = {
     |         super.greet()
     |         println("My name is Terebi-chan.")
     |     }
     | }
defined trait TraitB

scala> trait TraitC extends TraitA {
     |     override def greet(): Unit = {
     |         super.greet()
     |         println("I like niconico.")
     |     }
     | }
defined trait TraitC

scala> class ClassA extends TraitB with TraitC
defined class ClassA

scala> class ClassB extends TraitC with TraitB
defined class ClassB

ClassA の greet メソッドを呼んでみよう。

scala> (new ClassA).greet()
Hello!
My name is Terebi-chan.
I like niconico.

あとからミックスインされた TraitC の greet メソッドが呼ばれるはずだから I like niconico. と出力されるのはわかる。super を使って親トレイトの greet メソッドも呼んでいるんだから Hello! と出力されるのもわかる。だけどなんで My name is Terebi-chan. と出力されるんだ?これは TraitB のメソッドの出力のはずだろう?

TraitB と TraitC の間には親子関係がないはずなのに、まるで TraitB が TraitC の親トレイトであるかのようになっている。線形化ってそういうものなのか?

ClassC では出力の順番が変わる。

scala> (new ClassB).greet()
Hello!
I like niconico.
My name is Terebi-chan.

線形化の順番が違うので出力の順番も違うってことだろう。とは思うけど、なんとなく納得がいかない。