CLOVER🍀

That was when it all began.

superキーワードで、呼び出す継承元クラスまたはMix-inしたトレイトを指定する

ちょっとしたことから、Scalaのscala.collection.MapLikeのソースを見ていたのですが、こんな記述があるのを見つけました。

// Source from Scala 2.9.2 ...
package scala.collection

import generic._
import mutable.{ Builder, MapBuilder }
import annotation.{migration, bridge}
import parallel.ParMap

〜省略〜

trait MapLike[A, +B, +This <: MapLike[A, B, This] with Map[A, B]]
  extends PartialFunction[A, B]
     with IterableLike[(A, B), This]
     with GenMapLike[A, B, This]
     with Subtractable[A, This]
     with Parallelizable[(A, B), ParMap[A, B]]
{

〜省略〜

  override /*PartialFunction*/
  def toString = super[IterableLike].toString

}

問題は、これ。

  override /*PartialFunction*/
  def toString = super[IterableLike].toString

なんか、superにトレイト名をパラメータとして与えてますね。これって、Mix-inしたトレイトのうち、どれを呼び出すか指定できるってこと?

ちょっと気になったので、試してみることにしました。

まずは、こんなソースを用意。

// TraitTest.scala
trait TraitA {
  def action(): Unit = println("This is TraitA!!")
}

trait TraitB {
  def action(): Unit = println("This is TraitB!!")
}

class Sample extends TraitA with TraitB {
  override def action(): Unit =
    super.action()
}

val s = new Sample
s.action()

同じactionというメソッドを実装したトレイトを2つMix-inしたクラスですね。

実行すると、こうなります。

$ scala TraitTest.scala
This is TraitB!!

同じメソッドがあった場合、1番右(最後)にMix-inされたトレイトが呼び出されることになるので、Mix-inの順番をこう変えると

class Sample extends TraitB with TraitA {

結果はこうなります。

$ scala TraitTest.scala
This is TraitA!!

ここまでは、まあ普通ですね。

では、TraitAを最後にMix-inしたままactionメソッドの実装をちょっと変えてみます。

class Sample extends TraitB with TraitA {
  override def action(): Unit =
    super[TraitB].action()
}

結果は、こう。

$ scala TraitTest.scala
This is TraitB!!

TraitAが最後にMix-inされているにも関わらず、TraitBの実装を呼び出すことができてしまいました…。

今度は、これにクラスの継承を加えてみましょう。とりあえず、superキーワードは指定なしに戻してみます。

class Parent {
  def action(): Unit = println("This is Parent!!")
}

class Sample extends Parent with TraitB with TraitA {
  override def action(): Unit =
    super.action()
}

実行。

$ scala TraitTest.scala
This is TraitA!!

まあ、そうですよね…。

では、こう変更。

class Sample extends Parent with TraitB with TraitA {
  override def action(): Unit =
    super[Parent].action()
}

実行。

$ scala TraitTest.scala
This is Parent!!

…トレイトを飛び越えて、継承したクラスのメソッドを呼べてしまいました。

これ、全然知らなかったです。MapLikeのソースを見ていたのは全然違う調べ物をしていたからなのですが、意外なことがわかってしまいました。

ちなみに、指定できるのは直接継承しているクラスまたはMix-inしているトレイトだけのようで、そのクラスやトレイトのさらに上位クラスなどは指定できないようです。

trait SuperTrait {
  def action(): Unit = println("This is SuperTrait!!")
}

trait TraitA extends SuperTrait {
  override def action(): Unit = println("This is TraitA!!")
}

class Sample extends Parent with TraitB with TraitA {
  override def action(): Unit =
    super[SuperTrait].action()
}

みたいなことをすると…

$ scala TraitTest.scala 
/xxxxx/TraitTest.scala:26: error: SuperTrait does not name a parent class of class Sample
    super[SuperTrait].action()
    ^
one error found

そんな名前のクラス、親クラスにいないんだけど、と怒られてしまいます。この例は、トレイトの上位トレイトをつけたものですが、クラスでやっても同じでした。

まあ、実際にはそれほど使う機会はないような気もしますが、知識の片隅に入れておきましょう。

なお、後で調べてみると、すでにまとめておられる方がいました。
Scalaトレイト メモ(Hishidama's Scala trait Memo)
こちらのサイトは、Javaの調べ物でかなりお世話になっています。さすがですね♪