Der Trick besteht darin, Typklassen zu verwenden. Im Fall von printfist der Schlüssel die PrintfTypeTypklasse. Es werden keine Methoden verfügbar gemacht, aber der wichtige Teil liegt trotzdem in den Typen.
class PrintfType r
printf :: PrintfType r => String -> r
Hat printfalso einen überladenen Rückgabetyp. Im trivialen Fall haben wir keine zusätzlichen Argumente, so dass wir zu instanziiert müssen in die Lage rzu IO (). Dafür haben wir die Instanz
instance PrintfType (IO ())
Um eine variable Anzahl von Argumenten zu unterstützen, müssen wir als nächstes die Rekursion auf Instanzebene verwenden. Insbesondere benötigen wir eine Instanz, damit, wenn a rist PrintfType, ein Funktionstyp x -> rauch a ist PrintfType.
-- instance PrintfType r => PrintfType (x -> r)
Natürlich wollen wir nur Argumente unterstützen, die tatsächlich formatiert werden können. Hier kommt die zweite Typklasse ins PrintfArgSpiel. Die eigentliche Instanz ist also
instance (PrintfArg x, PrintfType r) => PrintfType (x -> r)
Hier ist eine vereinfachte Version, die eine beliebige Anzahl von Argumenten in der ShowKlasse verwendet und diese nur druckt:
{-# 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)
Hier wird bareine E / A-Aktion ausgeführt, die rekursiv aufgebaut wird, bis keine Argumente mehr vorhanden sind. An diesem Punkt führen wir sie einfach aus.
*Main> foo 3 :: IO ()
3
*Main> foo 3 "hello" :: IO ()
3
"hello"
*Main> foo 3 "hello" True :: IO ()
3
"hello"
True
QuickCheck verwendet dieselbe Technik, bei der die TestableKlasse eine Instanz für den Basisfall Boolund eine rekursive Instanz für Funktionen hat, die Argumente in der ArbitraryKlasse annehmen .
class Testable a
instance Testable Bool
instance (Arbitrary x, Testable r) => Testable (x -> r)