GADT menyediakan sintaks yang jelas & lebih baik untuk kode menggunakan Jenis Eksistensial dengan menyediakan forall's implisit
Saya pikir ada kesepakatan umum bahwa sintaks GADT lebih baik. Saya tidak akan mengatakan bahwa itu karena GADTs menyediakan foralls implisit, tetapi lebih karena sintaks asli, diaktifkan dengan ExistentialQuantification
ekstensi, berpotensi membingungkan / menyesatkan. Sintaks itu, tentu saja, terlihat seperti:
data SomeType = forall a. SomeType a
atau dengan batasan:
data SomeShowableType = forall a. Show a => SomeShowableType a
dan saya pikir konsensusnya adalah bahwa penggunaan kata kunci di forall
sini memungkinkan jenisnya mudah dikacaukan dengan jenis yang sama sekali berbeda:
data AnyType = AnyType (forall a. a) -- need RankNTypes extension
Sintaks yang lebih baik mungkin menggunakan exists
kata kunci terpisah , jadi Anda akan menulis:
data SomeType = SomeType (exists a. a) -- not valid GHC syntax
Sintaks GADT, apakah digunakan dengan implisit atau eksplisit forall
, lebih seragam di semua tipe ini, dan tampaknya lebih mudah dipahami. Bahkan dengan eksplisit forall
, definisi berikut menemukan gagasan bahwa Anda dapat mengambil nilai dari jenis apa pun a
dan memasukkannya ke dalam monomorfik SomeType'
:
data SomeType' where
SomeType' :: forall a. (a -> SomeType') -- parentheses optional
dan mudah untuk melihat dan memahami perbedaan antara tipe itu dan:
data AnyType' where
AnyType' :: (forall a. a) -> AnyType'
Jenis Eksistensial tampaknya tidak tertarik pada jenis yang dikandungnya tetapi pola yang cocok dengan mereka mengatakan bahwa ada beberapa jenis kita tidak tahu apa jenisnya sampai & kecuali kita menggunakan Typeable atau Data.
Kami menggunakannya ketika kami ingin Sembunyikan jenis (mis: untuk Daftar Heterogen) atau kami tidak benar-benar tahu jenis apa pada Waktu Kompilasi.
Saya kira ini tidak terlalu jauh, meskipun Anda tidak harus menggunakan Typeable
atau Data
menggunakan tipe eksistensial. Saya pikir akan lebih akurat untuk mengatakan tipe eksistensial menyediakan "kotak" yang diketik dengan baik di sekitar tipe yang tidak ditentukan. Kotak itu memang "menyembunyikan" jenis itu dalam arti tertentu, yang memungkinkan Anda membuat daftar kotak yang heterogen, mengabaikan jenis yang dikandungnya. Ternyata eksistensial tanpa SomeType'
kendala , seperti di atas cukup berguna, tetapi tipe dibatasi:
data SomeShowableType' where
SomeShowableType' :: forall a. (Show a) => a -> SomeShowableType'
memungkinkan Anda untuk mencocokkan pola untuk mengintip ke dalam "kotak" dan membuat fasilitas kelas tipe tersedia:
showIt :: SomeShowableType' -> String
showIt (SomeShowableType' x) = show x
Perhatikan bahwa ini berfungsi untuk semua tipe kelas, bukan hanya Typeable
atau Data
.
Berkenaan dengan kebingungan Anda tentang halaman 20 dari slide deck, penulis mengatakan bahwa tidak mungkin untuk suatu fungsi yang memerlukan eksistensial Worker
untuk menuntut Worker
memiliki Buffer
instance tertentu . Anda dapat menulis fungsi untuk membuat Worker
menggunakan jenis tertentu Buffer
, seperti MemoryBuffer
:
class Buffer b where
output :: String -> b -> IO ()
data Worker x = forall b. Buffer b => Worker {buffer :: b, input :: x}
data MemoryBuffer = MemoryBuffer
instance Buffer MemoryBuffer
memoryWorker = Worker MemoryBuffer (1 :: Int)
memoryWorker :: Worker Int
tetapi jika Anda menulis fungsi yang menggunakan Worker
argumen, ia hanya dapat menggunakan Buffer
fasilitas kelas tipe umum (mis., fungsi output
):
doWork :: Worker Int -> IO ()
doWork (Worker b x) = output (show x) b
Itu tidak dapat mencoba untuk meminta b
jenis buffer tertentu, bahkan melalui pencocokan pola:
doWorkBroken :: Worker Int -> IO ()
doWorkBroken (Worker b x) = case b of
MemoryBuffer -> error "try this" -- type error
_ -> error "try that"
Akhirnya, informasi runtime tentang tipe-tipe eksistensial tersedia melalui argumen "kamus" implisit untuk typeclasses yang terlibat. The Worker
tipe di atas, selain produk yang memiliki kolom untuk buffer dan masukan, juga memiliki medan implisit tak terlihat yang menunjuk ke Buffer
kamus (agak seperti v-meja, meskipun itu tidak besar, karena hanya berisi pointer ke sesuai output
fungsi).
Secara internal, kelas tipe Buffer
direpresentasikan sebagai tipe data dengan bidang fungsi, dan instance adalah "kamus" dari tipe ini:
data Buffer' b = Buffer' { output' :: String -> b -> IO () }
dBuffer_MemoryBuffer :: Buffer' MemoryBuffer
dBuffer_MemoryBuffer = Buffer' { output' = undefined }
Jenis eksistensial memiliki bidang tersembunyi untuk kamus ini:
data Worker' x = forall b. Worker' { dBuffer :: Buffer' b, buffer' :: b, input' :: x }
dan fungsi seperti doWork
itu beroperasi pada Worker'
nilai-nilai eksistensial diimplementasikan sebagai:
doWork' :: Worker' Int -> IO ()
doWork' (Worker' dBuf b x) = output' dBuf (show x) b
Untuk kelas tipe dengan hanya satu fungsi, kamus sebenarnya dioptimalkan untuk tipe baru, jadi dalam contoh ini, Worker
tipe eksistensial mencakup bidang tersembunyi yang terdiri dari penunjuk fungsi ke output
fungsi buffer, dan itulah satu-satunya informasi runtime yang diperlukan oleh doWork
.