Bí quyết là sử dụng các lớp kiểu. Trong trường hợp của printf
, khóa là PrintfType
loạ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, printf
có 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 r
là a PrintfType
, thì một kiểu hàm x -> r
cũ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 PrintfArg
xuấ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 Show
lớ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, bar
thự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 đó Testable
lớp có một thể hiện cho trường hợp cơ sở Bool
và một bản đệ quy cho các hàm nhận đối số trong Arbitrary
lớp.
class Testable a
instance Testable Bool
instance (Arbitrary x, Testable r) => Testable (x -> r)