ブログ

日常の忘備録

# Scalaの型パラメータの非変、共変、反変

前回の記事を書きながら、「そういや型パラメータについて超ざっくり説明してんなぁ」と思ったので。


前回の記事の説明だと型パラメータがあたかも関数特有の機能のように聞こえるかもしれませんが、型パラメータはJavaジェネリクスのようにクラスなどでも使うことができます。というか、Scalaを使ったことがあれば型パラメータを用いて作られているデータ構造を使用しているはずです。ListやArrayなどのコレクションライブラリは全てそれです。型パラメータの使い方は基本的に関数の時と同様です。

class Example[T](a: T) {
  def toList(): List[T] = List(a)
}
val example = new Example[Int](3)
example.toList //=> List(3)

ExampleクラスのtoList関数はメンバーaをただListにつつんで返すだけです。


ここまでだったらクラスでも使えるよーと言えばいい話なのですが、Scalaの型パラメータには反変、共変という性質があり、前回それに全く触れなかったので、ここで。 まず非変、非変は型パラメータA,Bがあったとき、例えば

val: G[A] = G[B]

の代入式が、AとBが完全に等しい場合にのみ成り立つ性質です。 それに対して、共変は

val G[B] = G[A]

がAがBの親クラスであるときに限り成り立つ性質であり、反変は

val G[A] = G[B]

が、AがBの親クラスである場合にのみ成り立つ性質です。Scalaでは標準で非変になります。ですが、Javaでは共変なので、Javaを使ったことのある人は少し違和感を感じるかもしれません。実際自分もちゃんと調べるまで完全にScalaでも共変だと思ってました。いまから1つ例を提示します。AnimalクラスがCatクラスをサブクラスに持つと仮定します。そのときJavaでは

Animal[] animals = new Animal[1];
animals[0] = new Cat();

が成り立ちます。これはJavaが標準で共変であるため、CatスーパークラスであるAnimal型の配列にCat型の値を入れようとしてもちゃんと通ります。ですが、反対にScalaは標準では非変なので、

val animals: Array[Animal] = new Array[Cat](new Cat())

に対してtype mismatchでエラーを吐きます。Scalaでは標準では非変なのですが、もちろん共変や反変にすることもできます。共変にするには型パラメータを[+A]のように定義します。また、反変にするには型パラメータを[-A]のようにします。