error: covariant type T occurs in contravariant position in type について
このクラスは定義できない:
scala> class B[+T] {
| def f(arg: T): T = arg
| }
<console>:9: error: covariant type T occurs in contravariant position in type T of value arg
def f(arg: T): T = arg
理由:
Scalaでの表記は、
共変 : class A[+T]
反変 : class A[-T]
共変は、「型引数に子クラスが指定されているクラスを子クラスとして扱える」
反変はその逆、「型引数に親クラスが指定されているクラスを子クラスとして扱える」
扱える?どこでどういう風に扱うの?:
def f(arg: A[Number]) = arg // この関数fに、
f(new A[Integer]) // Numberの子クラスであるIntegerを指定したクラスAが引数に渡せる
共変のTを引数に受け取る関数は定義できない:
class A[+T] {
def f(arg: T) = arg // エラー
}
なぜ?
関数fは子クラスに定義されていることが保証されている(※)んだからfは定義できるんじゃないの?
※ 親クラスの関数は子クラスでも使えるため
関数に使用する型パラメータは何も指定しなくても子クラスを受け取れるので以下のように使用できる:
def f(arg: Number) = arg
val i: Integer = 1
f(i) // Numberを取る関数にNumberの子クラスであるIntegerが渡せる
これまでをふまえて、さっき定義できなかったこの関数がもし定義できるとしたら:
trait X
class Y extends X
class Z extends Y
class A[+T] {
def f(arg: T) = arg // 実際はエラー
}
val a: A[X] = new A[Z]
a.f(new Y)
変数aは実際はA[Z]なのにA[X]に束縛できるため、aの関数fに対して、クラスYが渡せることになってしまう
型パラメータを展開した状態の A[Z] はこうなる:
class A {
def f(arg: Z) = arg
}
この関数fに対して、new Yが渡せないのはわかる:
new A.f(new Y) // ZかZの子クラスを期待しているのにZの親クラスが指定できてしまうので、もし定義できるとしたら実行時にしかエラーを判定できない
これより、以下のように関数fを定義すれば良いことがわかる:
class A[+T] {
def f[U >: T](arg: U) = arg // UはTの親クラスであることを明示しておく
}
関数fは引数にTの親クラスを受け取れるので先ほどのコードがエラーにならない:
new A.f(new Y) // YはZの親クラス
ということは、
反変は「型引数に親クラスが指定されているクラスを子クラスとして扱える」ので、以下のように関数の引数の型に指定することができる:
class A[-T] {
def f(arg: T) = arg
}
おわり。