2011年4月10日日曜日

Scalaにおける菱形継承問題を調べてみた。

昨日、エンジニアな皆さんたちと京都の二条城にお花見に行ってきた。


その後の飲み会の席で、こんな話題がでた。

「Scalaって菱形継承をどう解決してんの?」

Scalaを勉強中とはいえ、まだまだ勉強が足りない僕は、その質問にきちんと答えることができなかったので、ちょっと調べてみた。

■菱形継承問題とは




引用元:wikipedia 菱形継承問題 


菱形継承問題とは、多重継承を認めているプログラム言語において、クラスが上記の図のような継承関係にあり、BとCがAのメソッドをそれぞれ異なる形でオーバーライドしていた場合、DのクラスはBかC、いずれのメソッドを継承するのか、という問題である。
JavaやC#などの言語では、そもそも多重継承を認めておらず、インターフェイスも最終的に具象化すべきクラス(上記図でいうとD)にて具体的な実装がなされるため、菱形継承は問題にならない。

■Scalaのトレイトではどうなる?

ScalaもJavaやC#と同様、言語仕様としてクラスの多重継承を認めていない。その一方でインターフェイスという概念も存在せず、代わりにトレイトという考え方が存在する。

トレイトとは、基本的にはインターフェイスと同様の考え方であるのだが、それ自体に具体的な実装を施すことができる。
(ちなみにインターフェイスでいうinplementsはトレイトではmixinと呼ぶ)

トレイトはインターフェイスと同様、複数のトレイトをクラスにmixinできるため、JavaやC#では起こり得なかった菱形継承問題が発生することになる。

■実際に動かしてみた

ではScalaで菱形継承を実装するとどうなるのか、実験してみよう。

package diamondProblem

abstract class Animal {
  def howl()
}

trait DogTrait extends Animal {
  override def howl() = println("bow wow")
}

trait CatTrait extends Animal {
  override def howl() = println("mew")
}

class MyPet extends DogTrait with CatTrait {
}

object Driver  {
  def main(args: Array[String]) {
    var pet = new MyPet
    pet.howl()
  }
}

前述した菱形継承の図と対応させると、AのクラスがAnimalクラスに該当する。これはhowl()メソッドを定義した抽象クラスである。
B、Cに当たるのは、Animalクラスを継承したトレイト、DogTraitとCatTrait。それぞれhowl()メソッドに具体的な実装を施している。
最後に、DogTraitとCatTraitを多重mixinしたMyPetクラス。
DriverでMyPetクラスのインスタンスを生成し、howl()メソッドを呼び出した。

ビルドしてみたところ、正常終了。実行したら以下の結果が出た。

% scala diamondProblem.Driver
mew

CatTraitのメソッドを継承するようだ。
続いて、コードをこのように変えてみた。

package diamondProblem

abstract class Animal {
  def howl()
}

trait DogTrait extends Animal {
  override def howl() = println("bow wow")
}

trait CatTrait extends Animal {
  override def howl() = println("mew")
}

class MyPet extends CatTrait with DogTrait {
}

object Driver  {
  def main(args: Array[String]) {
    var pet = new MyPet
    pet.howl()
  }
}


"class MyPet extends DogTrait with CatTrait"のExtends 〇〇 with △△を入れ替え、"class MyPet extends CatTrait with DogTrait"としてみる。

実行すると…。

% scala diamondProblem.Driver
bow wow

結果が変わった!

Scalaで菱形継承を実装した場合、Extends 〇〇 with △△ で定義された継承関係のうち、右側に位置するトレイトから優先して継承される(右側にあるトレイトが左側のトレイトを上書きする)ようだ。

ちなみに、菱形継承でなく、単純に同名のメソッドを有するトレイトを多重mixinした場合はこうなった。

package diamondProblem

trait DogTrait {
  def howl() = println("bow wow")
}

trait CatTrait {
  def howl() = println("mew")
}

class MyPet extends CatTrait with DogTrait {
}

object Driver  {
  def main(args: Array[String]) {
    var pet = new MyPet
    pet.howl()
  }
}

これをビルドすると…。

% scalac diamondProblem.scala 
diamondProblem.scala:11: error: overriding method howl in trait CatTrait of type ()Unit;
 method howl in trait DogTrait of type ()Unit needs `override' modifier
class MyPet extends CatTrait with DogTrait { 
      ^
one error found


howl()をオーバーライドしろ、というエラーになる。

2 件のコメント:

  1. "Programming in Scala" (いわゆる「コップ本」) だと 12.6 でこの話題をあつかっています。

    返信削除
  2. > Kato Kazuyoshi さん。

    コメントありがとうございます。
    コップ本は未読でした。読んでみたいと思います。

    返信削除