一般に、共変の型パラメーターは、クラスがサブタイプ化されると変化することが許可されるパラメーターです(または、サブタイプ化によって変化するため、「co-」接頭辞)。より具体的に:
trait List[+A]
List[Int]
はのサブタイプであるList[AnyVal]
ためInt
、はのサブタイプですAnyVal
。これはList[Int]
、typeの値List[AnyVal]
が期待される場合のインスタンスを提供できることを意味します。これは、ジェネリックスが機能するための非常に直感的な方法ですが、可変データの存在下で使用すると、健全ではない(型システムを壊す)ことがわかります。これが、ジェネリックがJavaで不変である理由です。Java配列(誤って共変)を使用した不健全性の簡単な例:
Object[] arr = new Integer[1];
arr[0] = "Hello, there!";
typeの値をtype String
の配列に割り当てましたInteger[]
。明らかなはずの理由で、これは悪いニュースです。Javaの型システムでは、コンパイル時にこれを実際に許可しています。JVMはArrayStoreException
実行時に「便利に」スローされます。Scalaの型システムは、Array
クラスの型パラメーターが不変であるため(宣言では[A]
なく[+A]
)、この問題を回避します。
反変と呼ばれる別のタイプの分散があることに注意してください。共分散がいくつかの問題を引き起こす理由を説明しているため、これは非常に重要です。逆分散は文字通り共分散の逆です。パラメータはサブタイプによって上方に変化します。非常に重要なアプリケーションである関数がありますが、直感に反するため、あまり一般的ではありません。
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'
はのサブタイプ(または同じタイプ)である必要がありますが、and T1
の反対です。英語では、これは次のように読むことができます。T2
T2'
関数Aは、他の機能のサブタイプであるBのパラメータタイプ場合Aは、のパラメータの型のスーパータイプであるBの戻り型ながら、Aは、の戻り型のサブタイプであるB。
このルールの理由は、読者への演習に任されています(ヒント:上記の私の配列の例のように、関数がサブタイプ化されているため、さまざまなケースについて考えてください)。
共分散と反変についての新たな知識により、次の例がコンパイルされない理由を理解できるはずです。
trait List[+A] {
def cons(hd: A): List[A]
}
問題は、それ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
は、あまり具体的でない型に特化したインスタンスを返す場合にのみ機能することに注意してくださいB
。あなたが作るしようとするList
可変、物事はあなたがタイプの割り当て値にしようとしてしまうので、ブレークダウンB
型の変数にA
コンパイラによって許可されていません。ミュータビリティがある場合は常に、ある種のミューテーターが必要です。ミューテーターには、特定のタイプのメソッドパラメーターが必要です。これは、(アクセサーと共に)不変性を意味します。共変は不変データで機能します。可能な操作はアクセサーのみであり、これには共変の戻り型が指定される場合があるためです。
var
、そうでval
はないのに設定可能だからです。scalaの不変コレクションが共変であるのと同じ理由ですが、可変コレクションはそうではありません。