Il trucco sta nell'usare classi di tipo. Nel caso di printf, la chiave è la PrintfTypeclasse del tipo. Non espone alcun metodo, ma la parte importante è comunque nei tipi.
class PrintfType r
printf :: PrintfType r => String -> r
Quindi printfha un tipo di ritorno sovraccarico. Nel caso banale, non abbiamo argomenti extra, quindi dobbiamo essere in grado di istanziare rin IO (). Per questo, abbiamo l'istanza
instance PrintfType (IO ())
Successivamente, per supportare un numero variabile di argomenti, è necessario utilizzare la ricorsione a livello di istanza. In particolare, abbiamo bisogno di un'istanza in modo che se rè a PrintfType, anche un tipo di funzione x -> rè a PrintfType.
-- instance PrintfType r => PrintfType (x -> r)
Naturalmente, vogliamo supportare solo argomenti che possono essere effettivamente formattati. È qui che PrintfArgentra in gioco la seconda classe di tipo . Quindi l'istanza reale è
instance (PrintfArg x, PrintfType r) => PrintfType (x -> r)
Ecco una versione semplificata che accetta un numero qualsiasi di argomenti nella Showclasse e li stampa semplicemente:
{-# 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)
Qui, baresegue un'azione IO che viene costruita in modo ricorsivo fino a quando non ci sono più argomenti, a quel punto la eseguiamo semplicemente.
*Main> foo 3 :: IO ()
3
*Main> foo 3 "hello" :: IO ()
3
"hello"
*Main> foo 3 "hello" True :: IO ()
3
"hello"
True
QuickCheck utilizza anche la stessa tecnica, dove la Testableclasse ha un'istanza per il caso base Boole una ricorsiva per le funzioni che accettano argomenti nella Arbitraryclasse.
class Testable a
instance Testable Bool
instance (Arbitrary x, Testable r) => Testable (x -> r)