Triknya adalah dengan menggunakan kelas tipe. Dalam kasus printf
, kuncinya adalah PrintfType
kelas tipe. Itu tidak mengekspos metode apa pun, tetapi bagian yang penting adalah pada jenisnya.
class PrintfType r
printf :: PrintfType r => String -> r
Jadi printf
memiliki tipe pengembalian yang kelebihan beban. Dalam kasus sepele, kami tidak memiliki argumen tambahan, jadi kami harus dapat membuat contoh r
ke IO ()
. Untuk ini, kami memiliki contoh
instance PrintfType (IO ())
Selanjutnya, untuk mendukung sejumlah variabel argumen, kita perlu menggunakan rekursi di tingkat instance. Secara khusus kita membutuhkan sebuah contoh sehingga jika r
adalah a PrintfType
, jenis fungsi x -> r
juga a PrintfType
.
-- instance PrintfType r => PrintfType (x -> r)
Tentu saja, kami hanya ingin mendukung argumen yang sebenarnya bisa diformat. Di situlah kelas tipe kedua PrintfArg
masuk Jadi contoh sebenarnya adalah
instance (PrintfArg x, PrintfType r) => PrintfType (x -> r)
Berikut adalah versi sederhana yang mengambil sejumlah argumen di Show
kelas dan hanya mencetaknya:
{-# LANGUAGE FlexibleInstances #-}
foo :: FooType a => a
foo = bar (return ())
class FooType a where
bar :: IO () -> a
instance FooType (IO ()) where
bar = id
instance (Show x, FooType r) => FooType (x -> r) where
bar s x = bar (s >> print x)
Di sini, bar
mengambil tindakan IO yang dibangun secara rekursif hingga tidak ada lagi argumen, di mana kita cukup menjalankannya.
*Main> foo 3 :: IO ()
3
*Main> foo 3 "hello" :: IO ()
3
"hello"
*Main> foo 3 "hello" True :: IO ()
3
"hello"
True
QuickCheck juga menggunakan teknik yang sama, di mana Testable
kelas memiliki instance untuk kasus dasar Bool
, dan rekursif untuk fungsi yang mengambil argumen di Arbitrary
kelas.
class Testable a
instance Testable Bool
instance (Arbitrary x, Testable r) => Testable (x -> r)