あるプログラマの日記

プログラマのメモ、出来事、考えたこと、勉強とかの雑記

型パラメータの変位指定(variance)

型パラメータで型の横に共変(+), 反変(-)の変位アノテーションを付けて変位指定ができる。
変位指定アノテーションを付けない場合は非変(nonvariant)になる。
どのパラメータ型を渡せるかの3種類の規則を指定。

共変(covariant)

  • 型コンストラクタに共変の変位指定アノテーション[+T]を指定すると T とそのサブ型も扱えるようにできる。
scala> class Foo[+T](val a: T)
defined class Foo

scala> def bar(b: Foo[AnyVal]) = println(b.a)
bar: (b: Foo[AnyVal])Unit

scala> bar(new Foo[AnyVal](1))
1

scala> bar(new Foo[Int](1))
1

scala> bar(new Foo[Any](1))
<console>:10: error: type mismatch;
 found   : Foo[Any]
 required: Foo[AnyVal]
              bar(new Foo[Any](1))
                  ^
  • T型とTのサブ型は指定できるが、T のスーパー型は指定できない。
  • ScalaのListの定義は共変。List[Int] は Lint[Any] の引数を受け取るメソッドに指定できる。
  • イミュータブルなコレクションの型パラメータは共変が便利。
sealed abstract class List[+A] extends LinearSeq[A] 
	                                  with Product 
	                                  with GenericTraversableTemplate[A, List]
	                                  with LinearSeqOptimized[A, List[A]] {
..snip..

非変(nonvariant)

  • 型コンストラクタに変位指定アノテーションを何も指定しない場合は T 以外の型を指定できない。
  • T 型のみ指定できる。T のサブ型も T のスーパー型も指定できない。
scala> class Foo2[T](val a: T)
defined class Foo2

scala> def bar2(b: Foo2[AnyVal]) = println(b.a)
bar: (b: Foo2[AnyVal])Unit

scala> bar2(new Foo2[AnyVal](1))
1

scala> bar2(new Foo2[Int](1))
<console>:10: error: type mismatch;
 found   : Foo2[Int]
 required: Foo2[AnyVal]
Note: Int <: AnyVal, but class Foo2 is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
              bar(new Foo2[Int](1))
                  ^
scala> bar2(new Foo2[Any](1))
<console>:10: error: type mismatch;
 found   : Foo2[Any]
 required: Foo2[AnyVal]
Note: Any >: AnyVal, but class Foo2 is invariant in type T.
You may wish to define T as -T instead. (SLS 4.5)
              bar2(new Foo2[Any](1))
                   ^

Scala の ArrayBuffer の定義は非変。
ミュータブルコレクションの型パラメータを非変にしておくと同じ型しかパラメータ型として指定できない制約をかけることができる。

class ArrayBuffer[A](override protected val initialSize: Int) 
	  extends Buffer[A] 
..snip..

javaでは配列が共変でコレクションは非変。

反変(contravariant)

  • 型コンストラクタに反変の変位指定アノテーション[-T]を指定するとTのスーパー型を扱えるようにできる。
  • T型とTのスーパー型は指定できるが、Tのサブ型は指定できない。
scala> class Foo3[-T]
defined class Foo3

scala> class T1
defined class T1

scala> class T2 extends T1
defined class T2

scala> class T3 extends T2
defined class T3

scala>  def bar3(a: Foo3[T2]) = println(a)
bar3: (a: Foo3[T2])Unit

scala> bar3(new Foo3[T1])
$line1.$read$$iw$$iw$Foo3@1ad9b0f

scala> bar3(new Foo3[T2])
$line1.$read$$iw$$iw$Foo3@2445d7

scala> bar3(new Foo3[T3])
<console>:13: error: type mismatch;
 found   : Foo3[T3]
 required: Foo3[T2]
              bar3(new Foo3[T3])
                   ^

関数(変換処理)の型の入力は反変、出力は共変 trait Funtcion1[-T1, +R] extends AnyRef