読者です 読者をやめる 読者になる 読者になる

型パラメータの変位指定

Scala

型パラメータの変位

型パラメータには + や - の変位アノテーションを付けることで変位指定ができる。型パラメータの変位には以下の3つがある。

  • 不変(nonvariant) : [A]
  • 共変(covariant) : [+A]
  • 反変(contravariant) : [-A]

不変

不変とした場合、funcの引数には型パラメータで指定したものと同じものしか受け付けない。

object Sample001 {

  class Nonvariant[A]
  def func(arg:Nonvariant[Number]) {println(arg)}
  
  def main(args: Array[String]) {
    func(new Nonvariant[Any])      // エラー
    func(new Nonvariant[Number])   // OK
    func(new Nonvariant[Integer])  // エラー
  }
}

共変

共変とした場合、funcの引数には型パラメータのサブクラスを渡せる。この場合、Integer は Number のサブクラスであり、Covariant[Integer] も Covariant[Number] のサブクラスとして扱える。

object Sample002 {
  class Covariant[+A]
  def func(arg:Covariant[Number]) {println(arg)}
  
  def main(args: Array[String]) {
    func(new Covariant[Any])      // エラー
    func(new Covariant[Number])   // OK
    func(new Covariant[Integer])  // OK
  }
}

反変

反変とした場合、funcの引数には型パラメータのスーパークラスを渡せる。この場合、Any は Number のスーパークラスであり、Contravariant[Any] は Contravariant[Number]のサブクラスとして扱われるようになる。親子関係が反転する。

object Sample003 {
  class Contravariant[-A]
  def func(arg:Contravariant[Number]) {println(arg)}
  
  def main(args: Array[String]) {
    func(new Contravariant[Any])     // OK
    func(new Contravariant[Number])  // OK
    func(new Contravariant[Integer]) // エラー
  }
}

不変の例

不変はミュータブルなコンテナに利用できる。

object Sample {
  class Holder[A](var data:A)
  def intVal(h:Holder[Number]) = h.data.intValue
  
  def main(args: Array[String]) {
    val p1 = new Holder[Number](0)
    p1.data = 100   // Number のサブタイプであるIntegerを格納可能
    println(intVal(p1))  // 100
    
    p1.data = 3.14  // Number のサブタイプであるDoubleを格納可能
    println(intVal(p1))  // 3
  }
}

Holder[Number] にて Number型の何らかのサブタイプを格納できるミュータブルなコンテナとして利用できる。

class Holder[A](val data:A) として以下のようにイミュータブルとすることもできる。

object Sample {
  class Holder[A](val data:A)
  def intVal(h:Holder[Number]) = h.data.intValue
  
  def main(args: Array[String]) {
    val p1 = new Holder[Number](100)
    println(intVal(p1))  // 100
    
    val p2 = new Holder[Number](3.14)
    println(intVal(p2))  // 3
    
  }
}

共変の例

イミュータブルなコンテナは共変として以下のように利用できる。

object Sample {
  class Holder[+A](val data:A) // (var data:A)とするとコンパイルエラー
  def intVal(h:Holder[Number]) = h.data.intValue
  
  def main(args: Array[String]) {
    val p1 = new Holder[Integer](100)
    println(intVal(p1))
    
    val p2 = new Holder[Double](3.14)
    println(intVal(p1))
  }
}

Integer用のコンテナHolder[Integer]と、Double 用のコンテナ Holder[Double] にて intVal という関数を共有して利用できるようになる。

反変の例

コンソールやファイルなどに与えられたオブジェクトの内容を書き出す Writer というトレイトを考える。

  trait Writer[-A] { def write(x:A) }

コンソールへ出力するトレイトの実装を以下のように定義する。引数は何が来ても良いように Any とする。

  val writer = new Writer[Any] { def write(x:Any) = println(x.toString) }

これらを踏まえて、アルバムのタイトルを出力するtitleという関数を作成して呼び出してみる。ソースは以下。

object Sample {
  trait Writer[-A] { def write(x:A) }
  val writer = new Writer[Any] { def write(x:Any) = println(x.toString) }
  
  def title(w:Writer[String]) {w.write("OK Computer")}

  def main(args: Array[String]) {
    title(writer)
  }
}

タイトルの出力には String を指定しているため、以下はエラーとなる。

  def title(w:Writer[String]) {w.write(100)} // エラー


反変ではなく不変とした場合、title 関数の引数は Writer[Any] とする必要があり、String を強要できなくなる。

  trait Writer[A] { def write(x:A) }
  ・・・
  def title(w:Writer[Any]) {w.write(100)}

変位指定のまとめ

  • ミュータブルなコンテナは不変にすべき
  • イミュータブルなコンテナは共変にすべき
  • 変換処理の入力は反変に、出力は共変にすべき