L'astuce consiste à utiliser des classes de types. Dans le cas de printf, la clé est la PrintfTypeclasse de type. Il n'expose aucune méthode, mais l'essentiel est quand même dans les types.
class PrintfType r
printf :: PrintfType r => String -> r
A donc printfun type de retour surchargé. Dans le cas trivial, nous n'avons pas d'arguments supplémentaires, nous devons donc pouvoir instancier rvers IO (). Pour cela, nous avons l'instance
instance PrintfType (IO ())
Ensuite, pour prendre en charge un nombre variable d'arguments, nous devons utiliser la récursivité au niveau de l'instance. En particulier, nous avons besoin d'une instance pour que si rest a PrintfType, un type de fonction x -> rest également a PrintfType.
-- instance PrintfType r => PrintfType (x -> r)
Bien sûr, nous voulons uniquement prendre en charge les arguments qui peuvent réellement être formatés. C'est là qu'intervient la deuxième classe de type PrintfArg. Ainsi, l'instance réelle est
instance (PrintfArg x, PrintfType r) => PrintfType (x -> r)
Voici une version simplifiée qui prend n'importe quel nombre d'arguments dans la Showclasse et les affiche simplement:
{-# 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)
Ici, barprend une action IO qui est construite récursivement jusqu'à ce qu'il n'y ait plus d'arguments, à quel point nous l'exécutons simplement.
*Main> foo 3 :: IO ()
3
*Main> foo 3 "hello" :: IO ()
3
"hello"
*Main> foo 3 "hello" True :: IO ()
3
"hello"
True
QuickCheck utilise également la même technique, où la Testableclasse a une instance pour le cas de base Boolet une instance récursive pour les fonctions qui prennent des arguments dans la Arbitraryclasse.
class Testable a
instance Testable Bool
instance (Arbitrary x, Testable r) => Testable (x -> r)