Secara umum, parameter tipe kovarian adalah salah satu yang diizinkan untuk bervariasi ke bawah saat kelas subtipe (atau, berbeda dengan subtipe, maka awalan "co-"). Lebih konkret:
trait List[+A]
List[Int]adalah subtipe List[AnyVal]karena Intmerupakan subtipe dari AnyVal. Ini berarti bahwa Anda dapat memberikan contoh List[Int]kapan nilai tipe List[AnyVal]diharapkan. Ini benar-benar cara yang sangat intuitif untuk obat generik untuk bekerja, tetapi ternyata tidak sehat (memecah sistem tipe) saat digunakan di hadapan data yang dapat diubah. Inilah sebabnya mengapa obat generik tidak tetap di Jawa. Contoh singkat ketidaknyamanan menggunakan array Java (yang keliru kovarian):
Object[] arr = new Integer[1];
arr[0] = "Hello, there!";
Kami baru saja menetapkan nilai tipe Stringke array tipe Integer[]. Untuk alasan yang harus jelas, ini adalah berita buruk. Sistem tipe Java sebenarnya memungkinkan ini pada waktu kompilasi. JVM akan "membantu" melempar ArrayStoreExceptionpada saat runtime. Sistem tipe Scala mencegah masalah ini karena parameter tipe pada Arraykelas adalah invarian (deklarasi [A]bukan [+A]).
Perhatikan bahwa ada jenis varians lain yang dikenal sebagai contravariance . Ini sangat penting karena menjelaskan mengapa kovarians dapat menyebabkan beberapa masalah. Kontravarians secara harfiah berlawanan dengan kovarians: parameter bervariasi ke atas dengan subtyping. Ini jauh kurang umum sebagian karena sangat kontra-intuitif, meskipun memang memiliki satu aplikasi yang sangat penting: fungsi.
trait Function1[-P, +R] {
def apply(p: P): R
}
Perhatikan anotasi varian " - " pada Pparameter tipe. Deklarasi ini secara keseluruhan berarti Function1bersifat contravariant in Pdan covariant in R. Dengan demikian, kita dapat menurunkan aksioma berikut:
T1' <: T1
T2 <: T2'
---------------------------------------- S-Fun
Function1[T1, T2] <: Function1[T1', T2']
Perhatikan bahwa T1'harus berupa subtipe (atau jenis yang sama) dari T1, sedangkan itu adalah kebalikan dari T2dan T2'. Dalam bahasa Inggris, ini dapat dibaca sebagai berikut:
Fungsi A adalah subtipe dari fungsi lain B jika jenis parameter A adalah supertype dari jenis parameter dari B sedangkan tipe kembalinya A adalah subtipe dari jenis kembalinya B .
Alasan aturan ini dibiarkan sebagai latihan bagi pembaca (petunjuk: pikirkan tentang kasus yang berbeda karena fungsi subtipe, seperti contoh array saya dari atas).
Dengan pengetahuan co-dan contravariance yang baru Anda temukan, Anda seharusnya dapat melihat mengapa contoh berikut tidak dapat dikompilasi:
trait List[+A] {
def cons(hd: A): List[A]
}
Masalahnya adalah itu Akovarian, sementara consfungsi mengharapkan parameter tipenya menjadi invarian . Dengan demikian, Amemvariasikan arah yang salah. Yang cukup menarik, kita bisa menyelesaikan masalah ini dengan membuat Listcontravarian A, tetapi kemudian tipe pengembalian List[A]tidak valid karena consfungsi mengharapkan tipe pengembaliannya menjadi kovarian .
Hanya dua opsi kami di sini adalah a) membuat Ainvarian, kehilangan sifat sub-pengetikan yang bagus dan intuitif kovarians, atau b) menambahkan parameter tipe lokal ke consmetode yang didefinisikan Asebagai batas bawah:
def cons[B >: A](v: B): List[B]
Ini sekarang valid. Anda dapat membayangkan bahwa Aitu bervariasi ke bawah, tetapi Bdapat bervariasi ke atas sehubungan dengan Aitu Aadalah batas bawahnya. Dengan deklarasi metode ini, kita bisa Amenjadi kovarian dan semuanya berjalan lancar.
Perhatikan bahwa trik ini hanya berfungsi jika kita mengembalikan instance Listyang dikhususkan untuk tipe yang kurang spesifik B. Jika Anda mencoba membuat Listbisa berubah, hal-hal rusak karena Anda akhirnya mencoba untuk menetapkan nilai tipe Bke variabel tipe A, yang tidak diizinkan oleh kompiler. Setiap kali Anda memiliki mutabilitas, Anda perlu memiliki semacam mutator, yang memerlukan parameter metode tipe tertentu, yang (bersama-sama dengan accessor) menyiratkan invarian. Kovarian bekerja dengan data yang tidak dapat diubah karena satu-satunya operasi yang mungkin adalah accessor, yang dapat diberikan tipe pengembalian kovarian.
varbisa diselesaikan sementaravaltidak. Itu adalah alasan yang sama mengapa koleksi abadi scala adalah kovarian tetapi yang tidak bisa berubah.