2011/06/05

サブクラスで返り値がそのサブクラスへと変化するメソッドの定義

Scalaは静的な型システムを持つ言語で、Javaだとcastですましてしまうところも、きちんと定義しなくてはならない。もちろん、castももちろんできるけれども、それは本当にJavaのクラスを使用するときなどにとどめておいたほうがいい。
その結果、少しに分かりにくいところを解説しておく。

次のようなNodeの定義を考える。
trait Node {
  def play(move: Move): Option[Node]
  def possibleMoves(m: Marker): Seq[Move]
  def isTerminal: Boolean
}

探索は、この3つのメッソドとあとは適切なscore関数があれば可能である。
そして、このNodeを実装したReversiNodeを定義することを考える。ReversiNodeは当然、この3つのメソッドに対する実装を与える必要がある。さらに、ReversiNodeにはNodeにはない、winnerメソッドなどが含まれる。
class ReversiNode extends Node {
  override def play(move: Move): Option[Node] = ...
  override def possibleMoves(m: Marker): Seq[Move] = ...
  override def isTerminal: Boolean = ...
  def winner: Marker = ...
}

ここで、問題となるのはplayの返り値の型である。このままでは、playから帰ってきたオブジェクトに対してwinnerメソッドなどReversiNode特有のメソッドを呼び出せない。
val n2 = n1.play(move)
n2.winner // Error!

Javaであれば、こういうときにはcastを使うことになるが、動作時にエラーとなる危険がある。
ReversiNode n2 = (ReversiNode) n1.play(move);
n2.winner();

Scalaでは、型パラメータを用いることでcastを使わずに安全なコードを作成することができる。
まず、Nodeの宣言を次のように修正する。
trait Node[Repr <: Node[Repr]] {
  def play(move: Move): Option[Repr]
  def possibleMoves(m: Marker): Seq[Move]
  def isTerminal: Boolean
}
この[Repr <: Node[Repr]]というのは、 1. Reprという型変数を用いる(playの返り値に用いている)、 2. ReprはNode[Repr]のサブクラスである、という意味である。 そして、実装クラスであるReversiNodeを次のようにする。
class ReversiNode extends Node[ReversiNode] {
  override def play(move: Move): Option[ReversiNode] = ...
  override def possibleMoves(m: Marker): Seq[Move] = ...
  override def isTerminal: Boolean = ...
  def winner: Marker = ...
}

これで、playの返り値がサブクラスのReversiNodeであることが保証された。

最後に、Nodeを使うクラスを作る場合は、Nodeが型パラメータを必要とするために、次のように用いる。
trait Player[N <: Node[N]] {
  def play(node: N, last: Move): Move
}

0 件のコメント:

コメントを投稿