Setup: Mari kita asumsikan kita memiliki tipe yang disebut Iterator
yang memiliki parameter tipe Element
:
interface Iterator<Element> {}
Kemudian kami memiliki antarmuka Iterable
yang memiliki satu metode yang akan mengembalikan Iterator
.
// T has an upper bound of Iterator
interface Iterable<T: Iterator> {
getIterator(): T
}
Masalah dengan Iterator
menjadi generik adalah bahwa kita harus menyediakannya dengan argumen tipe.
Satu ide untuk menyelesaikan ini adalah "menyimpulkan" jenis iterator. Kode pseudo berikut ini mengungkapkan ide bahwa ada variabel tipe Element
yang disimpulkan sebagai argumen tipe ke Iterator
:
interface <Element> Iterable<T: Iterator<Element>> {
getIterator(): T
}
Dan kemudian kita menggunakannya di suatu tempat seperti ini:
class Vec<Element> implements Iterable<VecIterator<Element>> {/*...*/}
Definisi ini Iterable
tidak digunakan di Element
tempat lain dalam definisinya tetapi kasus penggunaan saya yang sebenarnya tidak. Fungsi-fungsi tertentu yang menggunakan Iterable
juga harus dapat membatasi parameter mereka untuk menerima Iterable
yang hanya mengembalikan jenis iterator tertentu, seperti iterator dua arah, yang mengapa iterator yang dikembalikan adalah parameter bukan hanya tipe elemen.
Pertanyaan:
- Apakah ada nama yang ditetapkan untuk variabel tipe disimpulkan ini? Bagaimana dengan teknik secara keseluruhan? Tidak mengetahui nomenklatur spesifik telah membuatnya sulit untuk mencari contoh-contoh ini di alam liar atau belajar tentang fitur-fitur khusus bahasa.
- Tidak semua bahasa dengan obat generik memiliki teknik ini; apakah ada nama untuk teknik serupa dalam bahasa-bahasa ini?
sumber
Jawaban:
Saya tidak tahu apakah ada istilah khusus untuk masalah ini, tetapi ada tiga kelas solusi umum:
Dan tentu saja solusi default: terus mengeja semua parameter tersebut.
Hindari jenis beton.
Anda telah mendefinisikan
Iterable
antarmuka sebagai:Ini memberi pengguna antarmuka daya maksimum karena mereka mendapatkan jenis beton yang tepat
T
dari iterator. Ini juga memungkinkan kompiler untuk menerapkan lebih banyak optimasi seperti inlining.Namun, jika
Iterator<E>
antarmuka yang dikirim secara dinamis maka mengetahui jenis konkret tidak diperlukan. Ini misalnya solusi yang digunakan Java. Antarmuka akan ditulis sebagai:Variasi yang menarik dari ini adalah
impl Trait
sintaks Rust yang memungkinkan Anda mendeklarasikan fungsi dengan tipe pengembalian abstrak, tetapi mengetahui bahwa tipe konkret akan diketahui di situs panggilan (sehingga memungkinkan optimasi). Ini berperilaku mirip dengan parameter tipe implisit.Izinkan parameter jenis placeholder.
The
Iterable
antarmuka tidak perlu tahu tentang jenis elemen, jadi mungkin akan mungkin untuk menulis ini sebagai:Di mana
T: Iterator<_>
menyatakan kendala "T adalah iterator apa pun, terlepas dari jenis elemen". Lebih tepatnya, kita dapat menyatakan ini sebagai: "ada beberapa jenisElement
sehinggaT
merupakanIterator<Element>
", tanpa harus mengetahui jenis konkret untukElement
. Ini berarti bahwa tipe-ekspresiIterator<_>
tidak menggambarkan tipe aktual, dan hanya dapat digunakan sebagai batasan tipe.Gunakan tipe keluarga / tipe terkait.
Misalnya dalam C ++, suatu tipe mungkin memiliki anggota tipe. Ini umum digunakan di seluruh perpustakaan standar, misalnya
std::vector::value_type
. Ini tidak benar-benar menyelesaikan masalah parameter tipe di semua skenario, tetapi karena suatu tipe dapat merujuk ke tipe lain, parameter tipe tunggal dapat menggambarkan seluruh keluarga jenis terkait.Mari kita definisikan:
Kemudian:
Ini terlihat sangat fleksibel, tetapi perhatikan bahwa ini dapat membuatnya lebih sulit untuk mengekspresikan batasan tipe. Misal seperti yang tertulis
Iterable
tidak menerapkan tipe elemen iterator apa pun, dan kami mungkin ingin mendeklarasikannyainterface Iterator<T>
. Dan Anda sekarang berurusan dengan jenis kalkulus yang cukup kompleks. Sangat mudah untuk secara tidak sengaja membuat sistem semacam itu tidak dapat dipastikan (atau mungkin sudah?).Perhatikan bahwa tipe terkait bisa sangat nyaman sebagai default untuk parameter tipe. Misalnya dengan asumsi bahwa
Iterable
antarmuka memerlukan parameter tipe terpisah untuk tipe elemen yang biasanya tetapi tidak selalu sama dengan tipe elemen iterator, dan bahwa kita memiliki parameter tipe placeholder, mungkin bisa dikatakan:Namun, itu hanya fitur ergonomi bahasa, dan tidak membuat bahasa lebih kuat.
Tipe sistemnya sulit, jadi bagus untuk melihat apa yang berfungsi dan tidak berfungsi dalam bahasa lain.
Misalnya pertimbangkan untuk membaca bab Ciri-Ciri Tingkat Lanjut dalam Rust Book, yang membahas tipe-tipe terkait. Tetapi perlu dicatat bahwa beberapa poin yang mendukung jenis terkait bukan generik hanya berlaku di sana karena bahasa tidak memiliki fitur subtipe dan setiap sifat hanya dapat diimplementasikan paling banyak sekali per jenis. Ie Rust traits bukan antarmuka seperti Java.
Sistem tipe menarik lainnya termasuk Haskell dengan berbagai ekstensi bahasa. Modul / fungsi OCaml adalah versi tipe keluarga yang relatif sederhana, tanpa secara langsung mencampurkannya dengan objek atau tipe parameter. Java terkenal karena keterbatasan dalam sistem tipenya, mis. Generik dengan penghapusan tipe, dan tidak ada generik atas tipe nilai. C # sangat mirip dengan Java tetapi berhasil menghindari sebagian besar keterbatasan ini, dengan biaya peningkatan kompleksitas implementasi. Scala mencoba mengintegrasikan generik gaya C # dengan kacamata jenis Haskell di atas platform Java. Templat sederhana C ++ yang menipu dipelajari dengan baik tetapi tidak seperti kebanyakan implementasi generik.
Ada baiknya juga melihat perpustakaan standar bahasa ini (terutama koleksi perpustakaan standar seperti daftar atau tabel hash) untuk melihat pola mana yang umum digunakan. Misalnya C ++ memiliki sistem kompleks dengan kemampuan iterator yang berbeda, dan Scala menyandikan kemampuan pengumpulan halus sebagai ciri. Antarmuka perpustakaan standar Java terkadang tidak sehat, misalnya
Iterator#remove()
, tetapi dapat menggunakan kelas bersarang sebagai jenis tipe terkait (misalnyaMap.Entry
).sumber