Die Typensicherheit von Haskell ist unübertroffen, nur bei Sprachen mit abhängiger Eingabe. Aber mit Text.Printf ist eine tiefe Magie im Gange , die ziemlich typisch wirkt.
> printf "%d\n" 3
3
> printf "%s %f %d" "foo" 3.3 3
foo 3.3 3
Was ist die tiefe Magie dahinter? Wie kann die Text.Printf.printf
Funktion solche variadischen Argumente annehmen?
Welche allgemeine Technik wird verwendet, um in Haskell verschiedene Argumente zu berücksichtigen, und wie funktioniert sie?
(Randnotiz: Bei Verwendung dieser Technik geht anscheinend die Sicherheit einiger Typen verloren.)
> :t printf "%d\n" "foo"
printf "%d\n" "foo" :: (PrintfType ([Char] -> t)) => t
haskell
printf
variadic-functions
polyvariadic
Dan Burton
quelle
quelle
Antworten:
Der Trick besteht darin, Typklassen zu verwenden. Im Fall von
printf
ist der Schlüssel diePrintfType
Typklasse. Es werden keine Methoden verfügbar gemacht, aber der wichtige Teil liegt trotzdem in den Typen.Hat
printf
also einen überladenen Rückgabetyp. Im trivialen Fall haben wir keine zusätzlichen Argumente, so dass wir zu instanziiert müssen in die Lager
zuIO ()
. Dafür haben wir die InstanzUm eine variable Anzahl von Argumenten zu unterstützen, müssen wir als nächstes die Rekursion auf Instanzebene verwenden. Insbesondere benötigen wir eine Instanz, damit, wenn a
r
istPrintfType
, ein Funktionstypx -> r
auch a istPrintfType
.Natürlich wollen wir nur Argumente unterstützen, die tatsächlich formatiert werden können. Hier kommt die zweite Typklasse ins
PrintfArg
Spiel. Die eigentliche Instanz ist alsoHier ist eine vereinfachte Version, die eine beliebige Anzahl von Argumenten in der
Show
Klasse verwendet und diese nur druckt:Hier wird
bar
eine E / A-Aktion ausgeführt, die rekursiv aufgebaut wird, bis keine Argumente mehr vorhanden sind. An diesem Punkt führen wir sie einfach aus.QuickCheck verwendet dieselbe Technik, bei der die
Testable
Klasse eine Instanz für den BasisfallBool
und eine rekursive Instanz für Funktionen hat, die Argumente in derArbitrary
Klasse annehmen .quelle
printf "%d" True
. Dies ist für mich sehr mystisch, da es den Anschein hat, dass der Laufzeitwert (?)"%d"
Zur Kompilierungszeit entschlüsselt wird, um eine zu erfordernInt
. Das ist absolut verwirrend für mich. . . zumal der Quellcode keine Dinge wieDataKinds
oder verwendetTemplateHaskell
(ich habe den Quellcode überprüft, ihn aber nicht verstanden.)printf "%d" True
ist, dass es keineBool
Instanz von gibtPrintfArg
. Wenn Sie ein Argument des falschen Typs übergeben, für das eine Instanz vorhanden istPrintfArg
, wird es kompiliert und zur Laufzeit eine Ausnahme ausgelöst. Beispiel:printf "%d" "hi"