Bí quyết là sử dụng các lớp kiểu. Trong trường hợp của printf, khóa là PrintfTypeloại kiểu. Nó không tiết lộ bất kỳ phương pháp nào, nhưng phần quan trọng là ở các loại.
class PrintfType r
printf :: PrintfType r => String -> r
Vì vậy, printfcó một kiểu trả về quá tải. Trong trường hợp tầm thường, chúng tôi không có lập luận thêm, vì vậy chúng tôi cần để có thể nhanh chóng rđến IO (). Đối với điều này, chúng tôi có ví dụ
instance PrintfType (IO ())
Tiếp theo, để hỗ trợ một số lượng biến đối số, chúng ta cần sử dụng đệ quy ở cấp cá thể. Đặc biệt, chúng ta cần một thể hiện để nếu rlà a PrintfType, thì một kiểu hàm x -> rcũng là a PrintfType.
-- instance PrintfType r => PrintfType (x -> r)
Tất nhiên, chúng tôi chỉ muốn hỗ trợ các đối số thực sự có thể được định dạng. Đó là nơi mà lớp loại thứ hai PrintfArgxuất hiện. Vì vậy, trường hợp thực tế là
instance (PrintfArg x, PrintfType r) => PrintfType (x -> r)
Đây là một phiên bản đơn giản hóa lấy bất kỳ số lượng đối số nào trong Showlớp và chỉ in chúng:
{-# 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)
Ở đây, barthực hiện một hành động IO được tạo đệ quy cho đến khi không còn đối số nào nữa, lúc này chúng ta chỉ cần thực thi nó.
*Main> foo 3 :: IO ()
3
*Main> foo 3 "hello" :: IO ()
3
"hello"
*Main> foo 3 "hello" True :: IO ()
3
"hello"
True
QuickCheck cũng sử dụng kỹ thuật tương tự, trong đó Testablelớp có một thể hiện cho trường hợp cơ sở Boolvà một bản đệ quy cho các hàm nhận đối số trong Arbitrarylớp.
class Testable a
instance Testable Bool
instance (Arbitrary x, Testable r) => Testable (x -> r)