Saya tidak terlalu mahir dalam Haskell, jadi ini mungkin pertanyaan yang sangat mudah.
Batasan bahasa apa yang dipecahkan oleh Rank2Types ? Bukankah fungsi di Haskell sudah mendukung argumen polimorfik?
haskell
types
polymorphism
higher-rank-types
Andrey Shchekin
sumber
sumber
Jawaban:
Ya, tetapi hanya dari peringkat 1. Ini berarti bahwa meskipun Anda dapat menulis fungsi yang menggunakan tipe argumen berbeda tanpa ekstensi ini, Anda tidak dapat menulis fungsi yang menggunakan argumennya sebagai tipe berbeda dalam pemanggilan yang sama.
Misalnya fungsi berikut tidak dapat diketik tanpa ekstensi ini karena
g
digunakan dengan tipe argumen yang berbeda dalam definisif
:Perhatikan bahwa sangat mungkin untuk meneruskan fungsi polimorfik sebagai argumen ke fungsi lain. Jadi sesuatu seperti
map id ["a","b","c"]
itu legal. Tetapi fungsinya hanya dapat menggunakannya sebagai monomorfik. Dalam contohmap
penggunaanid
seolah-olah memiliki tipeString -> String
. Dan tentu saja Anda juga bisa meneruskan fungsi monomorfik sederhana dari tipe yang diberikan, bukanid
. Tanpa rank2types, tidak ada cara bagi suatu fungsi untuk mensyaratkan argumennya harus berupa fungsi polimorfik dan dengan demikian tidak ada cara untuk menggunakannya sebagai fungsi polimorfik.sumber
f' g x y = g x + g y
. Jenis peringkat-1 yang disimpulkan adalahforall a r. Num r => (a -> r) -> a -> a -> r
. Karenaforall a
berada di luar panah fungsi, pemanggil harus terlebih dahulu memilih tipe untuka
; jika mereka memilihInt
, kami dapatf' :: forall r. Num r => (Int -> r) -> Int -> Int -> r
, dan sekarang kami telah memperbaikig
argumen sehingga dapat diterimaInt
tetapi tidakString
. Jika kita mengaktifkanRankNTypes
kita dapat menambahkan keteranganf'
dengan tipeforall b c r. Num r => (forall a. a -> r) -> b -> c -> r
. Tapi tidak bisa menggunakannya — apa jadinyag
?Sulit untuk memahami polimorfisme peringkat tinggi kecuali Anda mempelajari Sistem F secara langsung, karena Haskell dirancang untuk menyembunyikan detailnya dari Anda demi kesederhanaan.
Tapi pada dasarnya, ide kasarnya adalah tipe polimorfik tidak memiliki ekstensi
a -> b
bentuk seperti yang mereka lakukan di Haskell; pada kenyataannya, mereka terlihat seperti ini, selalu dengan bilangan eksplisit:Jika Anda tidak tahu simbol "∀", itu dibaca sebagai "untuk semua";
∀x.dog(x)
berarti "untuk semua x, x adalah seekor anjing." "Λ" adalah lambda kapital, digunakan untuk mengabstraksi parameter tipe; apa yang baris kedua katakan adalah bahwa id adalah fungsi yang mengambil tipet
, dan kemudian mengembalikan fungsi yang diparameterisasi oleh tipe itu.Anda lihat, di Sistem F, Anda tidak bisa begitu saja menerapkan fungsi seperti itu
id
ke nilai secara langsung; pertama, Anda perlu menerapkan fungsi Λ ke suatu tipe untuk mendapatkan fungsi λ yang Anda terapkan ke nilai. Jadi contohnya:Standard Haskell (yaitu, Haskell 98 dan 2010) menyederhanakan hal ini untuk Anda dengan tidak memiliki salah satu pembilang tipe, lambda kapital, dan aplikasi tipe ini, tetapi di balik layar GHC menempatkannya saat menganalisis program untuk kompilasi. (Saya yakin, ini semua adalah hal waktu kompilasi, tanpa overhead waktu proses.)
Tetapi penanganan otomatis Haskell terhadap hal ini berarti bahwa ia mengasumsikan bahwa "∀" tidak pernah muncul di cabang kiri jenis fungsi ("→").
Rank2Types
danRankNTypes
matikan batasan tersebut dan memungkinkan Anda untuk mengganti aturan default Haskell untuk tempat penyisipanforall
.Mengapa Anda ingin melakukan ini? Karena Sistem F yang lengkap dan tidak terbatas sangat kuat, dan dapat melakukan banyak hal keren. Misalnya, penyembunyian tipe dan modularitas dapat diimplementasikan menggunakan tipe peringkat yang lebih tinggi. Ambil contoh fungsi lama biasa dari tipe peringkat-1 berikut (untuk mengatur pemandangan):
Untuk menggunakan
f
, pemanggil terlebih dahulu harus memilih tipe apa yang akan digunakanr
dana
, lalu memberikan argumen dari tipe yang dihasilkan. Jadi Anda bisa memilihr = Int
dana = String
:Tapi sekarang bandingkan itu dengan tipe peringkat lebih tinggi berikut:
Bagaimana cara kerja fungsi jenis ini? Nah, untuk menggunakannya, pertama-tama Anda tentukan jenis yang akan digunakan
r
. Katakanlah kita memilihInt
:Tapi sekarang
∀a
ada di dalam fungsi panah, jadi Anda tidak bisa memilih tipe apa yang akan digunakana
; Anda harus menerapkanf' Int
fungsi Λ dari tipe yang sesuai. Ini berarti bahwa implementasif'
dapat memilih tipe apa yang akan digunakana
, bukan pemanggilf'
. Sebaliknya, tanpa tipe peringkat yang lebih tinggi, pemanggil selalu memilih tipe.Untuk apa ini berguna? Sebenarnya, untuk banyak hal, tapi satu idenya adalah Anda dapat menggunakan ini untuk memodelkan hal-hal seperti pemrograman berorientasi objek, di mana "objek" menggabungkan beberapa data tersembunyi bersama dengan beberapa metode yang bekerja pada data tersembunyi. Jadi misalnya, sebuah objek dengan dua metode — satu yang mengembalikan
Int
dan yang lainnya mengembalikanString
, bisa diimplementasikan dengan tipe ini:Bagaimana cara kerjanya? Objek diimplementasikan sebagai fungsi yang memiliki beberapa data internal tipe tersembunyi
a
. Untuk benar-benar menggunakan objek, kliennya mengirimkan fungsi "callback" yang akan dipanggil oleh objek dengan dua metode. Sebagai contoh:Di sini kita, pada dasarnya, memanggil metode kedua objek, metode yang tipenya
a → String
untuk yang tidak diketahuia
. Yah, tidak diketahuimyObject
klien; tetapi klien ini tahu, dari tanda tangan, bahwa mereka akan dapat menerapkan salah satu dari dua fungsi ke sana, dan mendapatkan salah satuInt
atau aString
.Untuk contoh Haskell yang sebenarnya, di bawah ini adalah kode yang saya tulis ketika saya belajar sendiri
RankNTypes
. Ini mengimplementasikan tipe yang disebutShowBox
yang menggabungkan nilai dari beberapa tipe tersembunyi bersama denganShow
instance kelasnya. Perhatikan bahwa dalam contoh di bawah, saya membuat daftarShowBox
elemen pertamanya yang dibuat dari angka, dan yang kedua dari string. Karena tipe disembunyikan dengan menggunakan tipe peringkat yang lebih tinggi, ini tidak melanggar pemeriksaan tipe.PS: bagi siapa saja yang membaca ini yang bertanya-tanya kenapa
ExistentialTypes
menggunakan GHCforall
, saya yakin alasannya adalah karena menggunakan teknik semacam ini di belakang layar.sumber
exists
kata kunci, Anda dapat mendefinisikan tipe eksistensial sebagai (misalnya)data Any = Any (exists a. a)
, di manaAny :: (exists a. a) -> Any
. Dengan menggunakan ∀xP (x) → Q ≡ (∃xP (x)) → Q, kita dapat menyimpulkan bahwaAny
bisa juga memiliki tipeforall a. a -> Any
dan dari situlahforall
kata kunci berasal. Saya percaya bahwa tipe eksistensial seperti yang diterapkan oleh GHC hanyalah tipe data biasa yang juga membawa semua kamus kelas tipe yang diperlukan (saya tidak dapat menemukan referensi untuk mendukung ini, maaf).data ApplyBox r = forall a. ApplyBox (a -> r) a
; ketika Anda mencocokkan polaApplyBox f x
, Anda mendapatkanf :: h -> r
danx :: h
untuk tipe "tersembunyi" yang dibatasih
. Jika saya mengerti benar, kasus kamus kelas tipe diterjemahkan menjadi sesuatu seperti ini:data ShowBox = forall a. Show a => ShowBox a
diterjemahkan menjadi sesuatu sepertidata ShowBox' = forall a. ShowBox' (ShowDict' a) a
;instance Show ShowBox' where show (ShowBox' dict val) = show' dict val
;show' :: ShowDict a -> a -> String
.Jawaban Luis Casillas memberikan banyak info bagus tentang apa arti tipe peringkat 2, tetapi saya hanya akan memperluas satu hal yang tidak dia bahas. Mewajibkan argumen menjadi polimorfik tidak hanya mengizinkannya digunakan dengan banyak tipe; ia juga membatasi apa yang dapat dilakukan fungsi tersebut dengan argumennya dan bagaimana ia dapat menghasilkan hasilnya. Artinya, ini memberi lebih sedikit kepada penelepon fleksibilitas yang . Mengapa Anda ingin melakukan itu? Saya akan mulai dengan contoh sederhana:
Misalkan kita memiliki tipe data
dan kami ingin menulis fungsi
yang mengambil fungsi yang seharusnya memilih salah satu elemen dari daftar yang diberikan dan mengembalikan
IO
aksi meluncurkan rudal ke target itu. Kami bisa memberikanf
tipe sederhana:Masalahnya adalah kita bisa lari tanpa sengaja
dan kemudian kita akan mendapat masalah besar! Pemberian
f
tipe polimorfik rank 1tidak membantu sama sekali, karena kami memilih jenis
a
saat kami meneleponf
, dan kami hanya mengkhususkanCountry
dan menggunakan perangkat jahat kami\_ -> BestAlly
lagi. Solusinya adalah dengan menggunakan tipe rank 2:Sekarang fungsi yang kita berikan harus polimorfik, jadi
\_ -> BestAlly
tidak akan mengetik cek! Faktanya, tidak ada fungsi yang mengembalikan elemen yang tidak ada dalam daftar yang diberikan akan melakukan kesalahan ketik (meskipun beberapa fungsi yang masuk ke loop tak terbatas atau menghasilkan kesalahan dan karena itu tidak pernah kembali akan melakukannya).Hal di atas tentu saja dibuat-buat, tetapi variasi pada teknik ini adalah kunci untuk membuat
ST
monad aman.sumber
Tipe peringkat lebih tinggi tidak eksotis seperti jawaban lain yang dibuat. Percaya atau tidak, banyak bahasa berorientasi objek (termasuk Java dan C #!) Menampilkannya. (Tentu saja, tidak ada seorang pun di komunitas itu yang mengenal mereka dengan nama yang terdengar menakutkan "tipe peringkat lebih tinggi".)
Contoh yang akan saya berikan adalah implementasi buku teks dari pola Pengunjung, yang saya gunakan sepanjang waktu dalam pekerjaan saya sehari-hari. Jawaban ini tidak dimaksudkan sebagai pengenalan pola pengunjung; pengetahuan yang mudah tersedia di tempat lain .
Dalam aplikasi SDM imajiner yang konyol ini, kami ingin beroperasi pada karyawan yang mungkin merupakan staf tetap penuh waktu atau kontraktor sementara. Variasi yang saya sukai dari pola Pengunjung (dan memang yang relevan dengan
RankNTypes
) menentukan parameter jenis pengunjung yang kembali.Intinya adalah bahwa sejumlah pengunjung dengan jenis pengembalian yang berbeda dapat beroperasi pada data yang sama. Ini berarti tidak
IEmployee
boleh mengungkapkan pendapat tentang apa yangT
seharusnya.Saya ingin menarik perhatian Anda pada tipe. Perhatikan bahwa
IEmployeeVisitor
secara universal mengkuantifikasi jenis kembaliannya, sedangkanIEmployee
mengkuantifikasikannya di dalamAccept
metode - artinya, pada peringkat yang lebih tinggi. Menerjemahkan dengan kikuk dari C # ke Haskell:Jadi begitulah. Tipe berperingkat lebih tinggi muncul di C # ketika Anda menulis tipe yang berisi metode umum.
sumber
Slide dari kursus Haskell Bryan O'Sullivan di Stanford membantu saya memahami
Rank2Types
.sumber
Bagi mereka yang akrab dengan bahasa berorientasi objek, fungsi peringkat lebih tinggi hanyalah fungsi generik yang mengharapkan fungsi generik lain sebagai argumennya.
Misalnya di TypeScript Anda bisa menulis:
Lihat bagaimana tipe fungsi generik
Identify
menuntut fungsi generik dari tipe tersebutIdentifier
? Ini membuatIdentify
fungsi peringkat lebih tinggi.sumber
Accept
memiliki tipe polimorfik peringkat-1, tetapi metodenyaIEmployee
sendiri adalah peringkat-2. Jika seseorang memberi sayaIEmployee
, saya dapat membukanya dan menggunakanAccept
metodenya di semua jenis.Visitee
kelas yang Anda perkenalkan. Fungsif :: Visitee e => T e
dasarnya adalah (setelah hal-hal kelas diinginkan)f :: (forall r. e -> Visitor e r -> r) -> T e
. Haskell 2010 memungkinkan Anda lolos dengan polimorfisme peringkat-2 terbatas menggunakan kelas-kelas seperti itu.forall
di contoh saya. Saya tidak memiliki referensi, tetapi Anda mungkin menemukan sesuatu di "Scrap Your Type Classes" . Polimorfisme peringkat yang lebih tinggi memang dapat menimbulkan masalah pemeriksaan tipe, tetapi pengurutan terbatas yang tersirat dalam sistem kelas sudah cukup.