Keamanan tipe Haskell tidak ada duanya hanya untuk bahasa yang diketik secara dependen. Tapi ada beberapa keajaiban mendalam yang terjadi dengan Text.Printf yang sepertinya agak miring.
> printf "%d\n" 3
3
> printf "%s %f %d" "foo" 3.3 3
foo 3.3 3
Apa keajaiban di balik ini? Bagaimana Text.Printf.printf
fungsi tersebut menerima argumen variadic seperti ini?
Apa teknik umum yang digunakan untuk memungkinkan argumen variadic di Haskell, dan bagaimana cara kerjanya?
(Catatan tambahan: beberapa jenis keamanan tampaknya hilang saat menggunakan teknik ini.)
> :t printf "%d\n" "foo"
printf "%d\n" "foo" :: (PrintfType ([Char] -> t)) => t
haskell
printf
variadic-functions
polyvariadic
Dan Burton
sumber
sumber
Jawaban:
Triknya adalah dengan menggunakan kelas tipe. Dalam kasus
printf
, kuncinya adalahPrintfType
kelas tipe. Itu tidak mengekspos metode apa pun, tetapi bagian yang penting adalah pada jenisnya.Jadi
printf
memiliki tipe pengembalian yang kelebihan beban. Dalam kasus sepele, kami tidak memiliki argumen tambahan, jadi kami harus dapat membuat contohr
keIO ()
. Untuk ini, kami memiliki contohSelanjutnya, untuk mendukung sejumlah variabel argumen, kita perlu menggunakan rekursi di tingkat instance. Secara khusus kita membutuhkan sebuah contoh sehingga jika
r
adalah aPrintfType
, jenis fungsix -> r
juga aPrintfType
.Tentu saja, kami hanya ingin mendukung argumen yang sebenarnya bisa diformat. Di situlah kelas tipe kedua
PrintfArg
masuk Jadi contoh sebenarnya adalahBerikut adalah versi sederhana yang mengambil sejumlah argumen di
Show
kelas dan hanya mencetaknya:Di sini,
bar
mengambil tindakan IO yang dibangun secara rekursif hingga tidak ada lagi argumen, di mana kita cukup menjalankannya.QuickCheck juga menggunakan teknik yang sama, di mana
Testable
kelas memiliki instance untuk kasus dasarBool
, dan rekursif untuk fungsi yang mengambil argumen diArbitrary
kelas.sumber
printf "%d" True
. Ini sangat mistis bagi saya, karena tampaknya nilai runtime (?)"%d"
Diuraikan pada waktu kompilasi untuk memerlukan fileInt
. Ini benar-benar membingungkan saya. . . terutama karena kode sumber tidak menggunakan hal-hal sepertiDataKinds
atauTemplateHaskell
(saya memeriksa kode sumber, tetapi tidak memahaminya.)printf "%d" True
adalah karena tidak adaBool
instance dariPrintfArg
. Jika Anda meneruskan argumen dengan tipe yang salah yang memang memiliki instancePrintfArg
, itu mengkompilasi dan melontarkan pengecualian pada waktu proses. Contoh:printf "%d" "hi"