Karena tidak ada orang lain yang menjawab pertanyaan, saya pikir saya akan mencobanya sendiri. Saya harus sedikit filosofis.
Pemograman generik adalah semua tentang pengabstrakan pada tipe yang sama, tanpa kehilangan informasi tipe (yang terjadi dengan polimorfisme nilai berorientasi objek). Untuk melakukan ini, tipe-tipe tersebut harus harus berbagi semacam antarmuka (satu set operasi, bukan istilah OO) yang dapat Anda gunakan.
Dalam bahasa berorientasi objek, tipe memenuhi antarmuka berdasarkan kelas. Setiap kelas memiliki antarmuka sendiri, didefinisikan sebagai bagian dari tipenya. Karena semua kelas List<T>
memiliki antarmuka yang sama, Anda dapat menulis kode yang berfungsi apa pun yang T
Anda pilih. Cara lain untuk memaksakan antarmuka adalah batasan warisan, dan meskipun keduanya tampak berbeda, mereka agak mirip jika Anda memikirkannya.
Dalam sebagian besar bahasa berorientasi objek, List<>
bukan jenis yang tepat. Tidak memiliki metode, dan karenanya tidak memiliki antarmuka. Hanya List<T>
yang memiliki hal-hal ini. Pada dasarnya, dalam istilah yang lebih teknis, satu-satunya jenis yang dapat Anda abstraksi secara bermakna adalah yang memiliki jenis tersebut *
. Untuk menggunakan tipe yang lebih tinggi di dunia berorientasi objek, Anda harus membatasi tipe frase dengan cara yang konsisten dengan batasan ini.
Misalnya, seperti yang disebutkan dalam komentar, kita dapat melihat Option<>
dan List<>
sebagai "dapat dipetakan", dalam arti bahwa jika Anda memiliki fungsi, Anda dapat mengonversikannya Option<T>
menjadi Option<S>
, atau List<T>
menjadi List<S>
. Mengingat bahwa kelas tidak dapat digunakan untuk abstrak ke tipe yang lebih tinggi secara langsung, kami membuat antarmuka:
IMappable<K<_>, T> where K<T> : IMappable<K<_>, T>
Dan kemudian kami mengimplementasikan antarmuka di keduanya List<T>
dan Option<T>
sebagai IMappable<List<_>, T>
dan IMappable<Option<_>, T>
masing - masing. Apa yang kami lakukan adalah menggunakan jenis yang lebih tinggi untuk menempatkan batasan pada jenis yang sebenarnya (yang tidak lebih tinggi) Option<T>
dan List<T>
. Ini adalah bagaimana hal itu dilakukan dalam Scala, meskipun tentu saja Scala memiliki fitur seperti ciri, variabel tipe, dan parameter implisit yang membuatnya lebih ekspresif.
Dalam bahasa lain, dimungkinkan untuk melakukan abstrak pada jenis yang lebih tinggi secara langsung. Di Haskell, salah satu otoritas tertinggi pada sistem tipe, kita dapat menggunakan kelas tipe untuk tipe apa pun, bahkan jika itu memiliki jenis yang lebih tinggi. Sebagai contoh,
class Mappable mp where
map :: mp a -> mp b
Ini adalah kendala yang ditempatkan langsung pada tipe (tidak ditentukan) mp
yang mengambil satu parameter tipe, dan mengharuskannya dikaitkan dengan fungsi map
yang mengubah suatu mp<a>
menjadi mp<b>
. Kita kemudian dapat menulis fungsi yang membatasi tipe yang lebih tinggi dengan Mappable
seperti di bahasa berorientasi objek Anda bisa menempatkan batasan pewarisan. Yah, semacam itu.
Singkatnya, kemampuan Anda untuk menggunakan tipe yang lebih tinggi tergantung pada kemampuan Anda untuk membatasi atau menggunakannya sebagai bagian dari kendala tipe.