Triknya adalah dengan menggunakan kelas tipe. Dalam kasus printf, kuncinya adalah PrintfTypekelas tipe. Itu tidak mengekspos metode apa pun, tetapi bagian yang penting adalah pada jenisnya.
class PrintfType r
printf :: PrintfType r => String -> r
Jadi printfmemiliki tipe pengembalian yang kelebihan beban. Dalam kasus sepele, kami tidak memiliki argumen tambahan, jadi kami harus dapat membuat contoh rke 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 radalah a PrintfType, jenis fungsi x -> rjuga a PrintfType.
-- instance PrintfType r => PrintfType (x -> r)
Tentu saja, kami hanya ingin mendukung argumen yang sebenarnya bisa diformat. Di situlah kelas tipe kedua PrintfArgmasuk Jadi contoh sebenarnya adalah
instance (PrintfArg x, PrintfType r) => PrintfType (x -> r)
Berikut adalah versi sederhana yang mengambil sejumlah argumen di Showkelas 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, barmengambil 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 Testablekelas memiliki instance untuk kasus dasar Bool, dan rekursif untuk fungsi yang mengambil argumen di Arbitrarykelas.
class Testable a
instance Testable Bool
instance (Arbitrary x, Testable r) => Testable (x -> r)