Saya mulai memahami bagaimana forall
kata kunci digunakan dalam apa yang disebut "tipe eksistensial" seperti ini:
data ShowBox = forall s. Show s => SB s
Namun, ini hanya sebagian dari cara forall
penggunaannya dan saya tidak bisa menggunakan pikiran saya dalam hal-hal seperti ini:
runST :: forall a. (forall s. ST s a) -> a
Atau jelaskan mengapa ini berbeda:
foo :: (forall a. a -> a) -> (Char, Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))
Atau semuanya RankNTypes
...
Saya cenderung lebih memilih bahasa Inggris yang jelas dan bebas jargon daripada jenis bahasa yang normal di lingkungan akademik. Sebagian besar penjelasan yang saya coba baca tentang ini (yang dapat saya temukan melalui mesin pencari) memiliki masalah berikut:
- Mereka tidak lengkap. Mereka menjelaskan satu bagian dari penggunaan kata kunci ini (seperti "tipe eksistensial") yang membuat saya merasa senang sampai saya membaca kode yang menggunakannya dengan cara yang sama sekali berbeda (seperti
runST
,foo
dan dibar
atas). - Mereka padat dengan asumsi bahwa saya telah membaca yang terbaru di cabang matematika diskrit, teori kategori atau aljabar abstrak apa pun yang populer minggu ini. (Jika saya tidak pernah membaca kata-kata "baca makalah apa pun untuk detail implementasi" lagi, itu akan terlalu cepat.)
- Mereka ditulis dengan cara-cara yang sering mengubah konsep-konsep sederhana menjadi tata bahasa dan semantik yang terpelintir dan patah.
Begitu...
Aktif ke pertanyaan aktual. Adakah yang bisa sepenuhnya menjelaskan forall
kata kunci dalam bahasa Inggris yang jelas dan sederhana (atau, jika ada di suatu tempat, tunjukkan penjelasan yang jelas yang saya lewatkan) yang tidak menganggap saya seorang ahli matematika yang menguasai jargon?
Diedit untuk menambahkan:
Ada dua jawaban menonjol dari yang berkualitas tinggi di bawah ini, tetapi sayangnya saya hanya dapat memilih satu sebagai yang terbaik. Jawaban Norman terperinci dan bermanfaat, menjelaskan berbagai hal dengan cara yang menunjukkan beberapa landasan teoretis forall
dan pada saat yang sama menunjukkan kepada saya beberapa implikasi praktisnya. jawaban yairchumencakup area yang tidak disebutkan orang lain (variabel tipe cakupan) dan mengilustrasikan semua konsep dengan kode dan sesi GHCi. Apakah mungkin untuk memilih keduanya sebagai yang terbaik, saya akan melakukannya. Sayangnya saya tidak bisa dan, setelah memeriksa kedua jawaban dengan cermat, saya telah memutuskan bahwa yairchu sedikit keluar dari Norman karena kode ilustratif dan penjelasan terlampir. Ini agak tidak adil, karena saya benar-benar membutuhkan kedua jawaban untuk memahami ini sampai pada titik yang forall
tidak meninggalkan saya dengan rasa takut yang samar ketika saya melihatnya dalam jenis tanda tangan.
Jawaban:
Mari kita mulai dengan contoh kode:
Kode ini tidak dapat dikompilasi (kesalahan sintaks) di Haskell 98. Diperlukan ekstensi untuk mendukung
forall
kata kunci.Pada dasarnya, ada 3 yang berbeda penggunaan umum untuk
forall
kata kunci (atau setidaknya sehingga tampaknya ), dan masing-masing memiliki ekstensi Haskell sendiri:ScopedTypeVariables
,RankNTypes
/Rank2Types
,ExistentialQuantification
.Kode di atas tidak mendapatkan kesalahan sintaks dengan salah satu yang diaktifkan, tetapi hanya ketik-cek dengan
ScopedTypeVariables
diaktifkan.Variabel Jenis yang Dicakup:
Variabel tipe cakupan membantu seseorang menentukan tipe untuk kode di dalam
where
klausa. Itu membuatb
dival :: b
satu sama sepertib
difoob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b
.Suatu titik yang membingungkan : Anda mungkin mendengar bahwa ketika Anda menghilangkan
forall
dari suatu jenis itu sebenarnya masih ada secara implisit. ( dari jawaban Norman: "biasanya bahasa-bahasa ini menghilangkan forall dari tipe polimorfik" ). Klaim ini benar, tetapi mengacu pada penggunaan lain dariforall
, dan bukan padaScopedTypeVariables
penggunaan.Peringkat-N-Jenis:
Mari kita mulai dengan yang
mayb :: b -> (a -> b) -> Maybe a -> b
setara denganmayb :: forall a b. b -> (a -> b) -> Maybe a -> b
, kecuali ketikaScopedTypeVariables
diaktifkan.Ini berarti bahwa ia bekerja untuk setiap
a
danb
.Katakanlah Anda ingin melakukan sesuatu seperti ini.
Apa yang harus menjadi tipe ini
liftTup
? IniliftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)
. Untuk mengetahui alasannya, mari kita coba kode:"Hmm .. mengapa GHC menyimpulkan bahwa tuple harus mengandung dua jenis yang sama? Katakan saja mereka tidak harus"
Hmm. jadi di sini GHC tidak membiarkan kita menerapkan
liftFunc
padav
karenav :: b
danliftFunc
menginginkanx
. Kami benar-benar ingin fungsi kami mendapatkan fungsi yang menerima segala kemungkinanx
!Jadi bukan
liftTup
itu yang bekerja untuk semuax
, itu fungsi yang didapatnya yang berfungsi.Kuantifikasi Eksistensial:
Mari kita gunakan sebuah contoh:
Apa bedanya dengan Tipe-N-Tipe?
Dengan Rank-N-Types,
forall a
berarti ekspresi Anda harus sesuai dengan semua yang mungkina
. Sebagai contoh:Daftar kosong berfungsi sebagai daftar jenis apa pun.
Jadi dengan Existential-Quantification,
forall
s dalamdata
definisi berarti bahwa, nilai yang terkandung dapat dari jenis apa pun yang cocok, bukan harus dari semua jenis yang sesuai.sumber
ScopedTypeVariables
tampak lebih buruk dari itu. Jika Anda menulis jenisb -> (a -> b) -> Maybe a -> b
dengan ekstensi ini di atasnya masih akan sama persis denganforall a b. b -> (a -> b) -> Maybe a -> b
. Namun, jika Anda ingin merujuk ke yang samab
(dan tidak secara kuantitatif dikuantifikasi) maka Anda perlu menulis versi terkuantifikasi secara eksplisit. Kalau tidak,STV
akan menjadi ekstensi yang sangat mengganggu.ScopedTypeVariables
, dan saya tidak berpikir itu buruk. imho itu adalah alat yang sangat membantu untuk proses pemrograman, dan terutama untuk pemula Haskell, dan saya bersyukur bahwa itu ada.Tidak. (Yah, mungkin Don Stewart bisa.)
Berikut adalah hambatan untuk penjelasan yang sederhana, jelas atau
forall
:Ini adalah penjumlah. Anda harus memiliki setidaknya sedikit logika (predikat kalkulus) untuk melihat penjumlah universal atau eksistensial. Jika Anda belum pernah melihat kalkulus predikat atau tidak nyaman dengan quantifiers (dan saya telah melihat siswa selama ujian kualifikasi PhD yang tidak nyaman), maka bagi Anda, tidak ada penjelasan yang mudah
forall
.Ini adalah tipe quantifier. Jika Anda belum melihat Sistem F dan berlatih menulis jenis polimorfik, Anda akan merasa
forall
bingung. Pengalaman dengan Haskell atau ML tidak cukup, karena biasanya bahasa-bahasa ini menghilangkanforall
dari tipe polimorfik. (Menurut saya, ini adalah kesalahan desain bahasa.)Dalam Haskell khususnya,
forall
digunakan dengan cara yang menurut saya membingungkan. (Saya bukan tipe teori, tetapi pekerjaan saya membuat saya berhubungan dengan banyak teori tipe, dan saya cukup nyaman dengan itu.) Bagi saya, sumber utama kebingungan adalah yangforall
digunakan untuk menyandikan tipe yang Saya sendiri lebih suka menulisexists
. Itu dibenarkan oleh sedikit isomorfisme tipe rumit yang melibatkan penjumlah dan panah, dan setiap kali saya ingin memahaminya, saya harus mencari hal-hal dan mengerjakan sendiri isomorfisma.Jika Anda tidak nyaman dengan ide isomorfisma tipe, atau jika Anda tidak memiliki praktik memikirkan isomorfisma tipe, penggunaan
forall
ini akan menghalangi Anda.Sementara konsep umum
forall
selalu sama (mengikat untuk memperkenalkan variabel tipe), detail penggunaan yang berbeda dapat sangat bervariasi. Bahasa Inggris Informal bukan alat yang sangat baik untuk menjelaskan variasi. Untuk benar-benar memahami apa yang terjadi, Anda perlu matematika. Dalam hal ini, matematika yang relevan dapat ditemukan dalam teks pengantar Benjamin Pierce Jenis dan Bahasa Pemrograman , yang merupakan buku yang sangat bagus.Adapun contoh khusus Anda,
runST
harus membuat kepala Anda sakit. Jenis peringkat tinggi (terutama di sebelah kiri panah) jarang ditemukan di alam liar. Saya mendorong Anda untuk membaca makalah yang memperkenalkanrunST
: "Lazy Functional State Threads" . Ini adalah makalah yang sangat bagus, dan itu akan memberi Anda intuisi yang jauh lebih baik untuk tiperunST
tertentu dan untuk tipe peringkat yang lebih tinggi secara umum. Penjelasannya membutuhkan beberapa halaman, ini dilakukan dengan sangat baik, dan saya tidak akan mencoba untuk menyingkatnya di sini.Mempertimbangkan
Jika saya menelepon
bar
, saya bisa memilih jenis apa sajaa
yang saya suka, dan saya bisa meneruskannya dari jenisa
ke jenisa
. Misalnya, saya bisa melewatkan fungsi(+1)
atau fungsireverse
. Anda bisa memikirkanforall
sebagai "Saya bisa memilih tipe sekarang". (Kata teknis untuk memilih jenis adalah instantiating .)Pembatasan pemanggilan
foo
jauh lebih ketat: argumen untukfoo
harus menjadi fungsi polimorfik. Dengan tipe itu, satu-satunya fungsi yang bisa saya lewatifoo
adalahid
atau fungsi yang selalu menyimpang atau salahundefined
. Alasannya adalah bahwa denganfoo
, yangforall
ada di sebelah kiri dari panah, sehingga pemanggilfoo
saya tidak bisa memilih apaa
yang-bukan itu adalah implementasi darifoo
yang mendapat untuk memilih apaa
yang. Karenaforall
berada di sebelah kiri panah, daripada di atas panah seperti padabar
, instantiasi terjadi di tubuh fungsi daripada di situs panggilan.Ringkasan: Sebuah lengkap penjelasan tentang
forall
kata kunci membutuhkan matematika dan dapat dipahami hanya oleh seseorang yang telah mempelajari matematika. Bahkan penjelasan parsial sulit dimengerti tanpa matematika. Tapi mungkin sebagian, penjelasan non-matematika saya sedikit membantu. Bacalah Launchbury dan Peyton Jones dirunST
!Tambahan: Jargon "di atas", "di bawah", "di sebelah kiri". Ini tidak ada hubungannya dengan cara tekstual jenis ditulis dan semuanya berkaitan dengan pohon sintaksis abstrak. Dalam sintaksis abstrak, a
forall
mengambil nama variabel tipe, dan kemudian ada tipe lengkap "di bawah" forall. Panah mengambil dua jenis (tipe argumen dan hasil) dan membentuk tipe baru (tipe fungsi). Jenis argumen adalah "di sebelah kiri" panah; itu adalah anak panah kiri di pohon abstrak-sintaksis.Contoh:
Dalam
forall a . [a] -> [a]
, forall berada di atas panah; apa yang ada di sebelah kiri panah itu[a]
.Di
jenis dalam tanda kurung akan disebut "forall di sebelah kiri panah". (Saya menggunakan jenis seperti ini di pengoptimal yang sedang saya kerjakan.)
sumber
forall a . [a] -> [a]
, forall ada di sebelah kiri panah.forall
dalam keadaan seperti itu, secara efektif, garis kebisingan. Saya akan memeriksa makalah yang Anda tautkan (terima kasih untuk tautannya juga!) Dan melihat apakah itu ada dalam pemahaman saya. Pujian.exists
tidak pernah diterapkan. (Ini bukan bagian dari Sistem F!) Di Haskell, bagian dari Sistem F dibuat tersirat, tetapiforall
merupakan salah satu hal yang tidak dapat disapu sepenuhnya di bawah karpet. Seolah-olah mereka mulai dengan Hindley-Milner, yang akan memungkinkanforall
dibuat tersirat, dan kemudian memilih sistem tipe yang lebih kuat, membingungkan kita yang mempelajari 'forall' dan 'ada' FOL dan berhenti di sana.Jawaban asli saya:
Seperti yang ditunjukkan Norman, sangat sulit untuk memberikan penjelasan bahasa Inggris yang jelas dan jelas tentang istilah teknis dari teori jenis. Kita semua berusaha.
Hanya ada satu hal yang perlu diingat tentang 'forall': ia mengikat tipe ke beberapa cakupan . Setelah Anda memahami itu, semuanya cukup mudah. Ini setara dengan 'lambda' (atau bentuk 'biarkan') pada tingkat tipe - Norman Ramsey menggunakan gagasan "kiri" / "di atas" untuk menyampaikan konsep ruang lingkup yang sama ini dalam jawaban yang sangat bagus .
Sebagian besar penggunaan 'forall' sangat sederhana, dan Anda dapat menemukannya diperkenalkan di GHC Users Manual, S7.8 ., Khususnya S7.8.5 yang luar biasa pada bentuk bersarang 'forall'.
Di Haskell, kami biasanya meninggalkan binder untuk jenis, ketika jenis itu dikuantifikasi secara universal, seperti:
setara dengan:
Itu dia.
Karena Anda dapat mengikat variabel tipe sekarang ke beberapa ruang lingkup, Anda dapat memiliki cakupan selain tingkat atas (" dikuantifikasi secara universal "), seperti contoh pertama Anda, di mana variabel tipe hanya terlihat dalam struktur data. Ini memungkinkan untuk tipe tersembunyi (" tipe eksistensial "). Atau kita dapat memiliki nesting binding yang sewenang - wenang ("peringkat tipe N").
Untuk sangat memahami sistem tipe, Anda perlu mempelajari beberapa jargon. Itulah sifat ilmu komputer. Namun, penggunaan sederhana, seperti di atas, harus dapat dipahami secara intuitif, melalui analogi dengan 'let' pada tingkat nilai. Pengantar yang bagus adalah Launchbury dan Peyton Jones .
sumber
length :: forall a. [a] -> Int
tidak setara denganlength :: [a] -> Int
saatScopedTypeVariables
diaktifkan. Ketikaforall a.
ada, hal itu mempengaruhilength
'swhere
ayat (jika memiliki salah satu) dan mengubah makna dari jenis variabel bernamaa
di dalamnya.Eh, dan bagaimana dengan logika first-order sederhana?
forall
cukup jelas dalam kaitannya dengan kuantifikasi universal , dan dalam konteks itu istilah eksistensial lebih masuk akal juga, meskipun akan kurang canggung jika adaexists
kata kunci. Apakah kuantifikasi efektif secara universal atau eksistensial tergantung pada penempatan kuantifier relatif terhadap tempat variabel digunakan di sisi mana dari panah fungsi dan itu semua agak membingungkan.Jadi, jika itu tidak membantu, atau jika Anda tidak suka logika simbolik, dari perspektif pemrograman-ish yang lebih fungsional, Anda dapat menganggap variabel tipe hanya sebagai parameter tipe (implisit) ke fungsi. Fungsi yang mengambil parameter tipe dalam pengertian ini secara tradisional ditulis menggunakan huruf kapital lambda untuk alasan apa pun, yang akan saya tulis di sini sebagai
/\
.Jadi, pertimbangkan
id
fungsinya:Kita dapat menulis ulang sebagai lambdas, memindahkan "parameter tipe" dari tanda tangan tipe dan menambahkan anotasi tipe inline:
Inilah hal yang sama dilakukan untuk
const
:Jadi
bar
fungsi Anda mungkin seperti ini:Perhatikan bahwa tipe fungsi yang diberikan
bar
sebagai argumen tergantung padabar
parameter tipe. Pertimbangkan jika Anda memiliki sesuatu seperti ini sebagai gantinya:Di sini
bar2
adalah menerapkan fungsi untuk sesuatu tipeChar
, jadi memberikanbar2
parameter tipe apa pun selainChar
akan menyebabkan kesalahan tipe.Di sisi lain, inilah yang
foo
akan terlihat:Tidak seperti itu
bar
,foo
sebenarnya tidak mengambil parameter tipe apa pun! Dibutuhkan fungsi yang sendiri mengambil parameter tipe, lalu menerapkan fungsi itu ke dua berbeda jenis.Jadi, ketika Anda melihat
forall
tanda tangan tipe, anggap saja sebagai ekspresi lambda untuk tanda tangan tipe . Sama sepertiforall
lambda biasa, ruang lingkup meluas sejauh mungkin ke kanan, hingga melampirkan tanda kurung, dan sama seperti variabel terikat dalam lambda biasa, variabel tipe terikat oleh aforall
hanya dalam lingkup dalam ekspresi terkuantifikasi.Post scriptum : Mungkin Anda mungkin bertanya-tanya - sekarang kita sedang berpikir tentang fungsi yang mengambil parameter tipe, mengapa kita tidak bisa melakukan sesuatu yang lebih menarik dengan parameter itu daripada memasukkannya ke dalam tanda tangan tipe? Jawabannya adalah kita bisa!
Fungsi yang menempatkan variabel tipe bersama-sama dengan label dan mengembalikan tipe baru adalah konstruktor tipe , yang Anda bisa menulis sesuatu seperti ini:
Tetapi kita membutuhkan notasi yang sama sekali baru, karena cara penulisan jenis itu, seperti
Either a b
, sudah menyarankan "menerapkan fungsiEither
ke parameter ini".Di sisi lain, fungsi yang semacam "pola cocok" pada parameter tipe, mengembalikan nilai yang berbeda untuk tipe yang berbeda, adalah metode kelas tipe . Sedikit ekspansi ke
/\
sintaksis saya di atas menunjukkan sesuatu seperti ini:Secara pribadi, saya pikir saya lebih suka sintaksis aktual Haskell ...
Suatu fungsi yang "pola cocok" dengan parameter tipenya dan mengembalikan tipe yang semau dan ada adalah tipe keluarga atau dependensi fungsional - dalam kasus sebelumnya, bahkan sudah sangat mirip dengan definisi fungsi.
sumber
λ
dalam hal ini, tetapi ekstensi sintaks unicode GHC tidak mendukung itu karena λ adalah sebuah huruf , fakta yang disayangkan secara hipotesis juga berlaku untuk abstraksi big-lambda hipotetis saya. Oleh karena itu/\
dengan analogi\
. Saya kira saya bisa saja menggunakan∀
tetapi saya mencoba untuk menghindari predikat kalkulus ...Berikut adalah penjelasan cepat dan kotor dalam istilah sederhana yang kemungkinan besar sudah Anda kenal.
Kata
forall
kunci ini benar-benar hanya digunakan dalam satu cara di Haskell. Itu selalu berarti hal yang sama ketika Anda melihatnya.Kuantifikasi universal
Sebuah jenis diukur secara universal adalah jenis formulir
forall a. f a
. Nilai tipe itu dapat dianggap sebagai fungsi yang mengambil tipea
sebagai argumennya dan mengembalikan nilai tipef a
. Kecuali bahwa dalam Haskell argumen tipe ini diteruskan secara implisit oleh sistem tipe. "Fungsi" ini harus memberi Anda nilai yang sama apa pun tipe yang diterimanya, sehingga nilainya polimorfik .Misalnya, perhatikan tipenya
forall a. [a]
. Nilai tipe itu mengambil tipe laina
dan memberi Anda kembali daftar elemen dari tipe yang samaa
. Hanya ada satu kemungkinan implementasi, tentu saja. Itu harus memberi Anda daftar kosong karenaa
dapat benar-benar jenis apa pun. Daftar kosong adalah satu-satunya nilai daftar yang polimorfik dalam jenis elemennya (karena tidak memiliki elemen).Atau tipenya
forall a. a -> a
. Penelepon dari fungsi semacam itu menyediakan tipea
dan nilai tipea
. Implementasi kemudian harus mengembalikan nilai dari jenis yang samaa
. Hanya ada satu kemungkinan implementasi lagi. Itu harus mengembalikan nilai yang sama seperti yang diberikan.Kuantifikasi eksistensial
Sebuah jenis eksistensial dihitung akan memiliki bentuk
exists a. f a
, jika Haskell didukung notasi itu. Nilai jenis itu dapat dianggap sebagai pasangan (atau "produk") yang terdiri dari suatu jenisa
dan nilai jenisf a
.Misalnya, jika Anda memiliki nilai tipe
exists a. [a]
, Anda memiliki daftar elemen tipe tertentu. Itu bisa berupa tipe apa saja, tetapi bahkan jika Anda tidak tahu apa itu, ada banyak hal yang dapat Anda lakukan untuk daftar seperti itu. Anda bisa membalikkannya, atau Anda bisa menghitung jumlah elemen, atau melakukan operasi daftar lainnya yang tidak bergantung pada jenis elemen.OK, jadi tunggu sebentar. Mengapa Haskell menggunakan
forall
untuk menunjukkan tipe "eksistensial" seperti berikut?Ini bisa membingungkan, tapi itu benar-benar menggambarkan tipe konstruktor data
SB
:Setelah dikonstruksi, Anda dapat menganggap nilai tipe
ShowBox
terdiri dari dua hal. Ini adalah tipes
bersama dengan nilai tipes
. Dengan kata lain, ini adalah nilai dari tipe yang dikuantifikasi secara eksistensial.ShowBox
dapat benar-benar ditulis sebagaiexists s. Show s => s
, jika Haskell mendukung notasi itu.runST
dan teman-temanMengingat itu, bagaimana perbedaannya?
Mari kita ambil
bar
. Dibutuhkan jenisa
dan fungsi tipea -> a
, dan menghasilkan nilai tipe(Char, Bool)
. Kita bisa memilihInt
sebagaia
dan memberikan fungsi tipeInt -> Int
misalnya. Tetapifoo
berbeda. Ini membutuhkan implementasifoo
agar dapat melewati semua jenis yang diinginkannya ke fungsi yang kita berikan. Jadi satu-satunya fungsi yang bisa kita berikan adalahid
.Kita sekarang harus dapat menangani arti dari jenis
runST
:Jadi
runST
harus dapat menghasilkan nilai tipea
, apa pun tipe yang kita berikana
. Untuk melakukannya, ia menggunakan argumen tipeforall s. ST s a
yang pasti harus entah bagaimana menghasilkana
. Terlebih lagi, itu harus dapat menghasilkan nilai jenisa
tidak peduli apa jenis implementasirunST
memutuskan untuk memberikans
.Oke, lalu bagaimana? Keuntungannya adalah bahwa hal ini membatasi penelepon
runST
karena jenisnyaa
tidak dapat melibatkan jeniss
sama sekali. Anda tidak dapat memberikan nilai tipeST s [s]
, misalnya. Apa artinya itu dalam praktik adalah bahwa implementasirunST
bebas untuk melakukan mutasi dengan nilai tipes
. Jenis ini menjamin bahwa mutasi ini bersifat lokal untuk implementasirunST
.Tipe
runST
adalah contoh tipe polimorfik peringkat-2 karena tipe argumennya berisiforall
kuantifier. Tipe difoo
atas juga dari peringkat 2. Tipe polimorfik biasa, seperti yangbar
ada di peringkat, adalah peringkat-1, tetapi menjadi peringkat-2 jika jenis argumen diharuskan bersifat polimorfik, denganforall
penjumlahannya sendiri . Dan jika suatu fungsi mengambil argumen peringkat-2 maka tipenya adalah peringkat-3, dan seterusnya. Secara umum, jenis yang mengambil argumen polimorfik peringkatn
memiliki peringkatn + 1
.sumber
Saya akan mencoba dan menjelaskan arti dan mungkin penerapan
forall
dalam konteks Haskell dan sistem tipenya.Tetapi sebelum Anda memahami bahwa saya ingin mengarahkan Anda ke sebuah pembicaraan yang sangat mudah diakses dan bagus oleh Runar Bjarnason berjudul " Kendala Pembebasan, Pembatasan Liberties ". Pembicaraan penuh dengan contoh dari kasus penggunaan dunia nyata serta contoh di Scala untuk mendukung pernyataan ini, meskipun tidak disebutkan
forall
. Saya akan mencoba menjelaskanforall
perspektif di bawah ini.Sangat penting untuk mencerna dan meyakini pernyataan ini untuk melanjutkan dengan penjelasan berikut, jadi saya mendorong Anda untuk menonton ceramah (setidaknya sebagian darinya).
Sekarang contoh yang sangat umum, menunjukkan ekspresifitas sistem tipe Haskell adalah tipe signature ini:
foo :: a -> a
Dikatakan bahwa dengan jenis tanda tangan ini, hanya ada satu fungsi yang dapat memuaskan tipe ini dan itu adalah
identity
fungsi atau apa yang lebih dikenal.id
.Pada tahap awal saya belajar Haskell, saya selalu bertanya-tanya fungsi di bawah ini:
mereka berdua memenuhi tanda tangan tipe di atas, lalu mengapa orang Haskell mengklaim itu
id
saja yang memenuhi tanda tangan tipeItu karena ada implisit
forall
tersembunyi dalam tipe tanda tangan. Jenis yang sebenarnya adalah:Jadi, sekarang mari kita kembali ke pernyataan: Batasan membebaskan, kebebasan membatasi
Menerjemahkannya ke sistem tipe, pernyataan ini menjadi:
Batasan pada level type, menjadi kebebasan pada level term
dan
Kebebasan pada level tipe, menjadi kendala pada level term
Mari kita coba buktikan pernyataan pertama:
Batasan pada tingkat tipe ..
Jadi menempatkan batasan pada tanda tangan tipe kami
menjadi kebebasan pada level istilah memberi kita kebebasan atau fleksibilitas untuk menulis semua ini
Hal yang sama dapat diamati dengan membatasi
a
dengan typeclass lain dllJadi sekarang apa jenis tanda tangan:
foo :: (Num a) => a -> a
diterjemahkan adalah:Ini dikenal sebagai kuantifikasi eksistensial, yang diterjemahkan menjadi ada beberapa contoh
a
yang fungsinya ketika diberi makan sesuatu jenisa
mengembalikan sesuatu dari jenis yang sama, dan contoh-contoh itu semua milik himpunan Bilangan.Karenanya kita dapat melihat menambahkan kendala (yang
a
seharusnya menjadi bagian dari himpunan Angka), membebaskan tingkat istilah untuk memiliki beberapa implementasi yang mungkin.Sekarang sampai pada pernyataan kedua dan yang benar-benar membawa penjelasan tentang
forall
:Kebebasan pada level tipe, menjadi kendala pada level term
Jadi sekarang mari kita lepaskan fungsi pada level type:
Sekarang ini diterjemahkan menjadi:
yang berarti bahwa implementasi tanda tangan jenis ini harus sedemikian rupa
a -> a
untuk semua keadaan.Jadi sekarang ini mulai membatasi kita pada level term. Kami tidak bisa lagi menulis
karena implementasi ini tidak akan memuaskan jika kita menempatkan
a
sebagaiBool
.a
bisa berupa aChar
atau a[Char]
atau tipe data khusus. Dalam semua keadaan itu harus mengembalikan sesuatu dari tipe yang sama. Kebebasan pada tingkat tipe ini adalah apa yang dikenal sebagai Kuantifikasi Universal dan satu-satunya fungsi yang dapat memuaskan iniyang biasa dikenal dengan
identity
fungsiOleh karena itu
forall
adalahliberty
pada level tipe, yang tujuan sebenarnya adalahconstrain
level istilah untuk implementasi tertentu.sumber
Alasan mengapa ada berbagai penggunaan kata kunci ini adalah bahwa kata kunci itu sebenarnya digunakan dalam setidaknya dua ekstensi sistem tipe yang berbeda: tipe peringkat lebih tinggi, dan eksistensial.
Mungkin lebih baik membaca dan memahami kedua hal itu secara terpisah, daripada mencoba untuk mendapatkan penjelasan mengapa 'forall' adalah bagian sintaksis yang tepat untuk keduanya sekaligus.
sumber
Bagaimana eksistensial eksistensial?
Penjelasan mengapa
forall
dalamdata
definisi bersifat isomorfik(exists a. a)
(pseudo-Haskell) dapat ditemukan di wikibooks "Haskell / Existentially quantized types" .Berikut ini adalah ringkasan singkat kata demi kata:
Ketika pencocokan pola / mendekonstruksi
MkT x
, apa jenisnyax
?x
dapat berupa jenis apa saja (sebagaimana dinyatakan dalamforall
), dan jenisnya adalah:Oleh karena itu, berikut ini isomorfik:
forall berarti forall
Penafsiran saya yang sederhana tentang semua ini, adalah "
forall
benar-benar berarti 'untuk semua'". Perbedaan penting untuk dibuat adalah dampakforall
pada definisi versus aplikasi fungsi .A
forall
berarti definisi nilai atau fungsi harus polimorfik.Jika hal yang didefinisikan adalah nilai polimorfik , maka itu berarti bahwa nilai tersebut harus valid untuk semua yang sesuai
a
, yang cukup ketat.Jika hal yang didefinisikan adalah fungsi polimorfik , maka itu berarti bahwa fungsi tersebut harus valid untuk semua yang sesuai
a
, yang tidak membatasi karena hanya karena fungsi tersebut polimorfik tidak berarti parameter yang diterapkan harus polimorfik. Artinya, jika fungsi ini berlaku untuk semuaa
, maka sebaliknya setiap yang cocoka
dapat diterapkan ke fungsi. Namun, tipe parameter hanya dapat dipilih satu kali dalam definisi fungsi.Jika a
forall
berada di dalam tipe parameter fungsi (yaitu, aRank2Type
) maka itu berarti parameter yang diterapkan harus benar - benar polimorfik, agar konsisten dengan gagasan definisiforall
cara adalah polimorfik. Dalam hal ini, jenis parameter dapat dipilih lebih dari satu kali dalam definisi fungsi ( "dan dipilih oleh implementasi fungsi", seperti yang ditunjukkan oleh Norman )Oleh karena itu, alasan mengapa
data
definisi eksistensial memungkinkan adaa
adalah karena konstruktor data adalah fungsi polimorfik :jenis MkT ::
a -> *
Yang berarti apa pun
a
dapat diterapkan ke fungsi. Berbeda dengan, katakanlah, nilai polimorfik :jenis valueT ::
a
Yang berarti bahwa definisi valueT haruslah polimorfik. Dalam hal ini,
valueT
dapat didefinisikan sebagai daftar kosong[]
semua jenis.Perbedaan
Meskipun makna untuk
forall
konsistenExistentialQuantification
danRankNType
, eksistensial memiliki perbedaan karenadata
konstruktor dapat digunakan dalam pencocokan pola. Seperti yang didokumentasikan dalam panduan pengguna ghc :sumber