Skip to content

Instantly share code, notes, and snippets.

@sndyuk
Created December 16, 2012 03:24
Show Gist options
  • Save sndyuk/4302976 to your computer and use it in GitHub Desktop.
Save sndyuk/4302976 to your computer and use it in GitHub Desktop.
error: covariant type T occurs in contravariant position in type ... について Scala

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
}

おわり。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment