L'astuce consiste à utiliser des classes de type. Dans le cas de printf
la clé est le PrintfType
classe de type. Elle n'expose aucune méthode, mais l'essentiel se trouve dans les types de toute façon.
class PrintfType r
printf :: PrintfType r => String -> r
Así que printf
a un type de retour surchargé. Dans le cas trivial, nous n'avons pas d'arguments supplémentaires, donc nous devons être en mesure d'instancier r
a IO ()
. Pour cela, nous avons l'instance
instance PrintfType (IO ())
Ensuite, afin de supporter un nombre variable d'arguments, nous devons utiliser la récursion au niveau de l'instance. En particulier, nous avons besoin d'une instance pour que si r
es un PrintfType
un type de fonction x -> r
est également un PrintfType
.
-- instance PrintfType r => PrintfType (x -> r)
Bien entendu, nous ne voulons soutenir que les arguments qui peuvent effectivement être formatés. C'est là que la deuxième classe de type PrintfArg
vient dans. Donc l'instance actuelle est
instance (PrintfArg x, PrintfType r) => PrintfType (x -> r)
Voici une version simplifiée qui prend n'importe quel nombre d'arguments dans le champ Show
et les imprime 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, bar
prend une action IO qui est construite récursivement jusqu'à ce qu'il n'y ait plus d'arguments, auquel 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ù l'élément Testable
a une instance pour le cas de base Bool
et un autre récursif pour les fonctions qui prennent des arguments dans la catégorie Arbitrary
classe.
class Testable a
instance Testable Bool
instance (Arbitrary x, Testable r) => Testable (x -> r)