一般に、共変型パラメーターは、クラスがサブタイプ化されるにつれて変化することが許されるものです (または、サブタイプ化によって変化するため、"co-" 接頭辞が付けられます)。より具体的には:
trait List[+A]
List[Int]
は のサブタイプであるList[AnyVal]
ため、Int
は のサブタイプですAnyVal
。これはList[Int]
、型の値List[AnyVal]
が期待される場合のインスタンスを提供できることを意味します。これは、ジェネリックが機能するための非常に直感的な方法ですが、変更可能なデータが存在する場合に使用すると、適切ではない (型システムが壊れる) ことがわかります。これが、Java でジェネリックが不変である理由です。Java 配列 (誤って共変) を使用した不健全性の簡単な例:
Object[] arr = new Integer[1];
arr[0] = "Hello, there!";
String
type の値を typeの配列に代入しましたInteger[]
。明らかな理由から、これは悪いニュースです。Java の型システムは、コンパイル時にこれを実際に許可します。JVM は実行時に "役立つ" をスローArrayStoreException
します。Scala の型システムは、Array
クラスの型パラメーターが不変であるため (宣言は[A]
ではなく[+A]
)、この問題を防ぎます。
反分散として知られる別の種類の分散があることに注意してください。これは、共分散がいくつかの問題を引き起こす可能性がある理由を説明するため、非常に重要です。反変性は、文字どおり共分散の反対です。パラメーターは、サブタイプによって上方に変化します。非常に直感に反するため、部分的にあまり一般的ではありませんが、非常に重要なアプリケーションが 1 つあります。関数です。
trait Function1[-P, +R] {
def apply(p: P): R
}
型パラメーターの " - " バリアンス アノテーションに注意してください。P
この宣言は全体として、Function1
が で反変でP
で共変であることを意味しR
ます。したがって、次の公理を導き出すことができます。
T1' <: T1
T2 <: T2'
---------------------------------------- S-Fun
Function1[T1, T2] <: Function1[T1', T2']
T1'
は のサブタイプ (または同じタイプ) である必要がありますが、とのT1
場合は逆です。英語では、これは次のように読むことができます。T2
T2'
Aのパラメーター型がBのパラメーター型のスーパータイプであり、Aの戻り値の型がBの戻り値の型のサブタイプである場合、関数Aは別の関数Bのサブタイプです。
この規則の理由は、読者への演習として残されています (ヒント: 上記の配列の例のように、関数がサブタイプ化されるさまざまなケースについて考えてみてください)。
共分散と反分散に関する新たな知識があれば、次の例がコンパイルされない理由を理解できるはずです。
trait List[+A] {
def cons(hd: A): List[A]
}
問題は、関数がその型パラメーターがinvariantであることを期待してA
いるのに対し、それは共変であることです。したがって、間違った方向に変化しています。興味深いことに、 を反変にすることでこの問題を解決できますが、関数は戻り値の型が共変であると想定しているため、戻り値の型は無効になります。cons
A
List
A
List[A]
cons
ここでの 2 つのオプションは、a)A
不変にすること、共分散の優れた直感的なサブタイピング プロパティを失うこと、または b)下限としてcons
定義するメソッドにローカル型パラメーターを追加することです。A
def cons[B >: A](v: B): List[B]
これで有効になりました。A
は下向きに変化していると想像できますが、下限があるためB
上向きに変化する可能性があります。このメソッド宣言により、共変になることができ、すべてがうまくいきます。A
A
A
このトリックはList
、特定性の低い type に特化したインスタンスを返す場合にのみ機能することに注意してくださいB
。ミュータブルにしようとすると、最終的に type の値を typeの変数にList
代入しようとすることになるため、事態は崩壊します。これはコンパイラによって許可されていません。可変性がある場合は常に、(アクセサーと共に) 不変性を意味する特定の型のメソッド パラメーターを必要とする、ある種のミューテーターが必要です。唯一可能な操作はアクセサーであり、アクセサーには共変の戻り値の型が与えられる可能性があるため、共分散は不変データで機能します。B
A