99 votes

Comment fonctionne printf en Haskell ?

La sécurité des types de Haskell est seconde à aucun seulement aux langages typés de manière dépendante. Mais il y a une certaine magie profonde qui se passe avec Text.Printf qui semble plutôt de type-wonky.

> printf "%d\n" 3
3
> printf "%s %f %d" "foo" 3.3 3
foo 3.3 3

Quelle est la magie profonde derrière tout cela ? Comment le Text.Printf.printf prend des arguments variadiques comme ceci ?

Quelle est la technique générale utilisée pour permettre les arguments variadiques en Haskell, et comment fonctionne-t-elle ?

(Note annexe : une certaine sécurité de frappe est apparemment perdue lorsqu'on utilise cette technique).

> :t printf "%d\n" "foo"
printf "%d\n" "foo" :: (PrintfType ([Char] -> t)) => t

128voto

hammar Points 89293

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)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X