型パラメータの変位
型パラメータには + や - の変位アノテーションを付けることで変位指定ができる。型パラメータの変位には以下の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)}
変位指定のまとめ
- ミュータブルなコンテナは不変にすべき
- イミュータブルなコンテナは共変にすべき
- 変換処理の入力は反変に、出力は共変にすべき