Genel olarak, bir kovaryant tipi parametre, sınıf alt tiplendiğinde değişmesine izin verilen bir parametredir (alternatif olarak alt tiplemeye göre değişir, dolayısıyla "co-" öneki). Daha somut olarak:
trait List[+A]
List[Int]
bir alttür List[AnyVal]
çünkü Int
bir alttür AnyVal
. Bu List[Int]
, bir tür değerin ne zaman List[AnyVal]
beklendiğinin bir örneğini sağlayabileceğiniz anlamına gelir . Bu, jeneriklerin çalışması için gerçekten çok sezgisel bir yoldur, ancak değişebilir verilerin varlığında kullanıldığında sesinin (tip sistemini bozduğu) ortaya çıkıyor. Bu nedenle jenerikler Java'da değişmezdir. Java dizilerini (yanlış bir şekilde kovaryant olan) kullanarak ortaya çıkan sessizliğe kısa bir örnek:
Object[] arr = new Integer[1];
arr[0] = "Hello, there!";
Bir tür String
diziye yalnızca tür değeri atadık Integer[]
. Açık olması gereken nedenlerden dolayı, bu kötü bir haber. Java'nın tür sistemi aslında derleme zamanında buna izin verir. JVM ArrayStoreException
çalışma zamanında "yardımcı olur" . Array
Sınıfta type parametresi değişmez olduğu için Scala'nın tür sistemi bu sorunu önler (bildirim [A]
yerine [+A]
).
Kontravaryans olarak bilinen başka bir varyans türü olduğuna dikkat edin . Kovaryansın neden bazı sorunlara neden olabileceğini açıkladığı için bu çok önemlidir. Çelişki, kelimenin tam anlamıyla kovaryansın tersidir: parametreler alt tiplemeyle yukarı doğru değişir . Kısmen çok daha az yaygındır çünkü çok sezgiseldir, ancak çok önemli bir uygulamaya sahiptir: fonksiyonlar.
trait Function1[-P, +R] {
def apply(p: P): R
}
Type parametresindeki " - " sapma notuna dikkat edin P
. Bir bütün olarak bu deklarasyon Function1
, çelişkili P
ve içinde kovaryant anlamına gelir R
. Böylece, aşağıdaki aksiyomları türetebiliriz:
T1' <: T1
T2 <: T2'
---------------------------------------- S-Fun
Function1[T1, T2] <: Function1[T1', T2']
Bunun T1'
bir alt türü (veya aynı tür) olması gerektiğine dikkat edin T1
, oysa T2
ve için tersidir T2'
. İngilizce olarak, bu aşağıdaki gibi okunabilir:
Bir fonksiyon bir başka fonksiyon bir alt tipi olan B parametre türü, eğer A parametresi tipte bir süper tip olan B dönüş türü ise A dönüş türü bir alt tipi olan B .
Bu kuralın nedeni okuyucuya bir alıştırma olarak bırakılmıştır (ipucu: yukarıdaki örnek dizim gibi işlevler alt tür olarak yazıldığında farklı durumlar hakkında düşünün).
Yeni bulunan eş ve karşıtlık bilginizle, aşağıdaki örneğin neden derlenmeyeceğini görebilmelisiniz:
trait List[+A] {
def cons(hd: A): List[A]
}
Sorun A
kovaryant, cons
fonksiyon ise type parametresinin değişmez olmasını bekliyor . Böylece A
yanlış yönü değiştiriyor. İlginçtir ki, bu sorunu List
çelişkili hale getirerek çözebiliriz A
, ancak daha sonra işlev dönüş türünün kovaryant olmasını beklediği için dönüş türü List[A]
geçersiz olur .cons
Buradaki sadece iki seçeneğimiz şunlardır: a) A
değişmez yapmak , kovaryansın hoş, sezgisel alt yazma özelliklerini kaybetmek veya b) alt sınır olarak cons
tanımlanan yönteme yerel bir tür parametresi eklemek A
:
def cons[B >: A](v: B): List[B]
Bu artık geçerli. Bunun A
aşağı doğru değiştiğini, ancak alt sınırından beri B
yukarı doğru değişebildiğini hayal edebilirsiniz . Bu yöntem beyanı ile kovaryant olabiliriz ve her şey yolunda gider.A
A
A
Bu hile yalnızca List
, daha az spesifik türde bir örneği döndürdüğümüzde işe yaradığına dikkat edin B
. Değişken hale getirmeye çalışırsanız , derleyici tarafından izin verilmeyen bir tür değişkenine List
tür değerleri atamaya çalıştığınız için işler bozulur. Değişebilirliğe sahip olduğunuzda, belirli bir tipte (erişimciyle birlikte) değişmezlik anlamına gelen bir yöntem parametresi gerektiren bir çeşit mutasyona sahip olmanız gerekir. Kovaryans değişmez verilerle çalışır, çünkü tek olası işlem kovaryant dönüş tipi verilebilen bir erişimcidir.B
A
var
ayarlanamazken ayarlanabilirval
. Skala'nın değişmez koleksiyonlarının kovaryant olduğu, ancak değişebilen koleksiyonların aynı olmasının nedeni de aynıdır.