La sécurité des types de Haskell est inégalée uniquement par rapport aux langages à typage dépendant. Mais il y a une magie profonde en cours avec Text.Printf qui semble plutôt insensée .
> 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 la Text.Printf.printf
fonction peut-elle accepter des arguments variadiques comme celui-ci?
Quelle est la technique générale utilisée pour permettre des arguments variadiques dans Haskell, et comment cela fonctionne-t-il?
(Note latérale: certains types de sécurité sont apparemment perdus lors de l'utilisation de cette technique.)
> :t printf "%d\n" "foo"
printf "%d\n" "foo" :: (PrintfType ([Char] -> t)) => t
haskell
printf
variadic-functions
polyvariadic
Dan Burton
la source
la source
Réponses:
L'astuce consiste à utiliser des classes de types. Dans le cas de
printf
, la clé est laPrintfType
classe de type. Il n'expose aucune méthode, mais l'essentiel est quand même dans les types.A donc
printf
un type de retour surchargé. Dans le cas trivial, nous n'avons pas d'arguments supplémentaires, nous devons donc pouvoir instancierr
versIO ()
. Pour cela, nous avons l'instanceEnsuite, 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
r
est aPrintfType
, un type de fonctionx -> r
est également aPrintfType
.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 estVoici une version simplifiée qui prend n'importe quel nombre d'arguments dans la
Show
classe et les affiche simplement:Ici,
bar
prend 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.QuickCheck utilise également la même technique, où la
Testable
classe a une instance pour le cas de baseBool
et une instance récursive pour les fonctions qui prennent des arguments dans laArbitrary
classe.la source
printf "%d" True
. C'est très mystique pour moi, car il semble que la valeur d'exécution (?)"%d"
Soit déchiffrée au moment de la compilation pour exiger un fichierInt
. Cela me déroute absolument. . . d'autant plus que le code source n'utilise pas des choses commeDataKinds
ouTemplateHaskell
(j'ai vérifié le code source, mais je ne l'ai pas compris.)printf "%d" True
est qu'il n'y a pas d'Bool
instance dePrintfArg
. Si vous passez un argument de type incorrect qui ne possède une instance dePrintfArg
, il ne compile et lance une exception à l' exécution. Ex:printf "%d" "hi"