Sebagai lanjutan dari pertanyaan ini , dapatkah seseorang menjelaskan yang berikut di Scala:
class Slot[+T] (var some: T) {
// DOES NOT COMPILE
// "COVARIANT parameter in CONTRAVARIANT position"
}
Saya memahami perbedaan antara +T
dan T
dalam deklarasi tipe (ini mengkompilasi jika saya menggunakan T
). Tetapi kemudian bagaimana seseorang benar-benar menulis kelas yang kovarian dalam parameter tipenya tanpa terpaksa menciptakan hal yang tidak diparameterisasi ? Bagaimana saya bisa memastikan bahwa yang berikut ini hanya dapat dibuat dengan instance dari T
?
class Slot[+T] (var some: Object){
def get() = { some.asInstanceOf[T] }
}
EDIT - sekarang dapatkan ini sebagai berikut:
abstract class _Slot[+T, V <: T] (var some: V) {
def getT() = { some }
}
ini semua baik, tapi saya sekarang memiliki dua tipe parameter, di mana saya hanya ingin satu. Saya akan kembali mengajukan pertanyaan sebagai berikut:
Bagaimana saya bisa menulis kelas yang tidak berubah Slot
yang kovarian dalam tipenya?
EDIT 2 : Duh! Saya menggunakan var
dan tidak val
. Berikut ini yang saya inginkan:
class Slot[+T] (val some: T) {
}
sumber
var
bisa diselesaikan sementaraval
tidak. Itu adalah alasan yang sama mengapa koleksi abadi scala adalah kovarian tetapi yang tidak bisa berubah.Jawaban:
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:
List[Int]
adalah subtipeList[AnyVal]
karenaInt
merupakan subtipe dariAnyVal
. Ini berarti bahwa Anda dapat memberikan contohList[Int]
kapan nilai tipeList[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):Kami baru saja menetapkan nilai tipe
String
ke array tipeInteger[]
. Untuk alasan yang harus jelas, ini adalah berita buruk. Sistem tipe Java sebenarnya memungkinkan ini pada waktu kompilasi. JVM akan "membantu" melemparArrayStoreException
pada saat runtime. Sistem tipe Scala mencegah masalah ini karena parameter tipe padaArray
kelas 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.
Perhatikan anotasi varian " - " pada
P
parameter tipe. Deklarasi ini secara keseluruhan berartiFunction1
bersifat contravariant inP
dan covariant inR
. Dengan demikian, kita dapat menurunkan aksioma berikut:Perhatikan bahwa
T1'
harus berupa subtipe (atau jenis yang sama) dariT1
, sedangkan itu adalah kebalikan dariT2
danT2'
. Dalam bahasa Inggris, ini dapat dibaca sebagai berikut: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:
Masalahnya adalah itu
A
kovarian, sementaracons
fungsi mengharapkan parameter tipenya menjadi invarian . Dengan demikian,A
memvariasikan arah yang salah. Yang cukup menarik, kita bisa menyelesaikan masalah ini dengan membuatList
contravarianA
, tetapi kemudian tipe pengembalianList[A]
tidak valid karenacons
fungsi mengharapkan tipe pengembaliannya menjadi kovarian .Hanya dua opsi kami di sini adalah a) membuat
A
invarian, kehilangan sifat sub-pengetikan yang bagus dan intuitif kovarians, atau b) menambahkan parameter tipe lokal kecons
metode yang didefinisikanA
sebagai batas bawah:Ini sekarang valid. Anda dapat membayangkan bahwa
A
itu bervariasi ke bawah, tetapiB
dapat bervariasi ke atas sehubungan denganA
ituA
adalah batas bawahnya. Dengan deklarasi metode ini, kita bisaA
menjadi kovarian dan semuanya berjalan lancar.Perhatikan bahwa trik ini hanya berfungsi jika kita mengembalikan instance
List
yang dikhususkan untuk tipe yang kurang spesifikB
. Jika Anda mencoba membuatList
bisa berubah, hal-hal rusak karena Anda akhirnya mencoba untuk menetapkan nilai tipeB
ke variabel tipeA
, 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.sumber
trait Animal
,trait Cow extends Animal
,def iNeedACowHerder(herder: Cow => Unit, c: Cow) = herder(c)
dandef iNeedAnAnimalHerder(herder: Animal => Unit, a: Animal) = herder(a)
. Maka,iNeedACowHerder({ a: Animal => println("I can herd any animal, including cows") }, new Cow {})
tidak apa-apa, karena penggembala hewan kita dapat menggembalakan sapi, tetapiiNeedAnAnimalHerder({ c: Cow => println("I can herd only cows, not any animal") }, new Animal {})
memberikan kesalahan kompilasi, karena penggembala sapi kita tidak dapat menggembalakan semua hewan.@Daniel telah menjelaskannya dengan sangat baik. Tetapi untuk menjelaskannya secara singkat, jika diizinkan:
slot.get
kemudian akan melempar kesalahan saat runtime karena tidak berhasil dalam mengonversikanAnimal
keDog
(ya!).Secara umum, kemampuan berubah tidak cocok dengan ko-varians dan kontra-varians. Itulah alasan mengapa semua koleksi Java adalah invarian.
sumber
Lihat Scala dengan contoh , halaman 57+ untuk diskusi lengkap tentang ini.
Jika saya memahami komentar Anda dengan benar, Anda perlu membaca ulang bagian yang dimulai di bagian bawah halaman 56 (pada dasarnya, apa yang saya pikir Anda minta tidak aman untuk jenis tanpa menjalankan pemeriksaan waktu berjalan, yang tidak dilakukan scala, jadi kamu kurang beruntung). Menerjemahkan contoh mereka untuk menggunakan konstruk Anda:
Jika Anda merasa saya tidak memahami pertanyaan Anda (kemungkinan berbeda), coba tambahkan lebih banyak penjelasan / konteks ke deskripsi masalah dan saya akan coba lagi.
Menanggapi hasil edit Anda: Slot yang tidak dapat diubah adalah situasi yang sangat berbeda ... * tersenyum * Saya harap contoh di atas membantu.
sumber
Anda perlu menerapkan batas bawah pada parameter. Saya mengalami kesulitan mengingat sintaks, tetapi saya pikir itu akan terlihat seperti ini:
Scala-by-example agak sulit dipahami, beberapa contoh konkret akan membantu.
sumber