Beberapa bahasa mengizinkan kelas dan fungsi dengan parameter tipe (seperti di List<T>
mana T
mungkin tipe arbitrer). Misalnya, Anda dapat memiliki fungsi seperti:
List<S> Function<S, T>(List<T> list)
Namun beberapa bahasa memungkinkan konsep ini diperluas satu tingkat lebih tinggi, memungkinkan Anda untuk memiliki fungsi dengan tanda tangan:
K<S> Function<K<_>, S, T>(K<T> arg)
Di mana K<_>
itu sendiri adalah tipe seperti List<_>
yang memiliki parameter tipe. "Tipe parsial" ini dikenal sebagai konstruktor tipe.
Pertanyaan saya adalah, mengapa Anda membutuhkan kemampuan ini? Masuk akal untuk memiliki tipe suka List<T>
karena semuanya List<T>
hampir persis sama, tetapi semua K<_>
bisa sangat berbeda. Anda dapat memiliki Option<_>
dan List<_>
yang tidak memiliki fungsi sama sekali.
Functor
contoh dalam jawaban Luis Casillas cukup intuitif. Apa kesamaanList<T>
danOption<T>
kesamaan? Jika Anda memberi saya salah satu dan fungsiT -> S
saya bisa memberi AndaList<S>
atauOption<S>
. Kesamaan yang mereka miliki adalah bahwa Anda dapat mencoba untuk mendapatkanT
nilai dari keduanya.IReadableHolder<T>
.IMappable<K<_>, T>
dengan metodeK<S> Map(Func<T, S> f)
, pelaksanaan sebagaiIMappable<Option<_>, T>
,IMappable<List<_>, T>
. Jadi Anda harus membatasiK<T> : IMappable<K<_>, T>
untuk memanfaatkannya.Jawaban:
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 yangT
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. HanyaList<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<>
danList<>
sebagai "dapat dipetakan", dalam arti bahwa jika Anda memiliki fungsi, Anda dapat mengonversikannyaOption<T>
menjadiOption<S>
, atauList<T>
menjadiList<S>
. Mengingat bahwa kelas tidak dapat digunakan untuk abstrak ke tipe yang lebih tinggi secara langsung, kami membuat antarmuka:Dan kemudian kami mengimplementasikan antarmuka di keduanya
List<T>
danOption<T>
sebagaiIMappable<List<_>, T>
danIMappable<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>
danList<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,
Ini adalah kendala yang ditempatkan langsung pada tipe (tidak ditentukan)
mp
yang mengambil satu parameter tipe, dan mengharuskannya dikaitkan dengan fungsimap
yang mengubah suatump<a>
menjadimp<b>
. Kita kemudian dapat menulis fungsi yang membatasi tipe yang lebih tinggi denganMappable
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.
sumber
(Mappable mp) => mp a -> mp b
, Anda telah menempatkan batasanmp
untuk menjadi anggota kelas tipeMappable
. Saat Anda mendeklarasikan tipe sepertiOption
menjadi instanceMappable
, Anda menambahkan perilaku ke tipe itu. Saya kira Anda bisa memanfaatkan perilaku itu secara lokal tanpa pernah membatasi tipe apa pun, tapi kemudian tidak ada bedanya dengan mendefinisikan fungsi biasa.*
tanpa membuatnya tidak dapat digunakan. Memang benar bahwa kelas tipe sangat kuat ketika bekerja dengan tipe yang lebih tinggi.