เคล็ดลับคือการใช้คลาสประเภท ในกรณีของprintf
คีย์คือPrintfType
คลาสประเภท ไม่เปิดเผยวิธีการใด ๆ แต่ส่วนสำคัญอยู่ที่ประเภทอยู่ดี
class PrintfType r
printf :: PrintfType r => String -> r
ดังนั้นจึงprintf
มีประเภทผลตอบแทนที่มากเกินไป เล็กน้อยในกรณีเราไม่มีข้อโต้แย้งพิเศษดังนั้นเราจึงจำเป็นที่จะสามารถยกตัวอย่างไปr
IO ()
สำหรับสิ่งนี้เรามีอินสแตนซ์
instance PrintfType (IO ())
ถัดไปเพื่อรองรับอาร์กิวเมนต์ที่มีจำนวนตัวแปรเราจำเป็นต้องใช้การเรียกซ้ำที่ระดับอินสแตนซ์ โดยเฉพาะอย่างยิ่งที่เราต้องการเช่นเพื่อที่ว่าถ้าr
เป็นPrintfType
ประเภทฟังก์ชั่นนี้ยังมีx -> r
PrintfType
-- instance PrintfType r => PrintfType (x -> r)
แน่นอนว่าเราต้องการสนับสนุนข้อโต้แย้งที่สามารถจัดรูปแบบได้จริงเท่านั้น นั่นคือสิ่งที่คลาสประเภทที่สองเข้าPrintfArg
มาดังนั้นอินสแตนซ์ที่แท้จริงคือ
instance (PrintfArg x, PrintfType r) => PrintfType (x -> r)
นี่คือเวอร์ชันที่เรียบง่ายซึ่งรับอาร์กิวเมนต์จำนวนเท่าใดก็ได้ในShow
คลาสและพิมพ์ออกมา:
{-# 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)
ที่นี่bar
ใช้การดำเนินการ IO ซึ่งสร้างขึ้นแบบวนซ้ำจนกว่าจะไม่มีข้อโต้แย้งอีกต่อไปเมื่อถึงจุดนั้นเราก็ดำเนินการตามนั้น
*Main> foo 3 :: IO ()
3
*Main> foo 3 "hello" :: IO ()
3
"hello"
*Main> foo 3 "hello" True :: IO ()
3
"hello"
True
QuickCheck ยังใช้เทคนิคเดียวกันโดยที่Testable
คลาสมีอินสแตนซ์สำหรับกรณีพื้นฐานBool
และแบบวนซ้ำสำหรับฟังก์ชันที่ใช้อาร์กิวเมนต์ในArbitrary
คลาส
class Testable a
instance Testable Bool
instance (Arbitrary x, Testable r) => Testable (x -> r)