Bezpieczeństwo typów Haskella nie ma sobie równych tylko w przypadku języków z typami zależnymi. Ale w Text.Printf zachodzi pewna głęboka magia, która wydaje się raczej dziwna .
> printf "%d\n" 3
3
> printf "%s %f %d" "foo" 3.3 3
foo 3.3 3
Jaka kryje się za tym głęboka magia? W jaki sposób Text.Printf.printf
funkcja może przyjmować różne argumenty, takie jak ta?
Jaka jest ogólna technika umożliwiająca stosowanie argumentów wariadycznych w Haskell i jak to działa?
(Uwaga dodatkowa: przy stosowaniu tej techniki najwyraźniej tracimy pewne bezpieczeństwo typów).
> :t printf "%d\n" "foo"
printf "%d\n" "foo" :: (PrintfType ([Char] -> t)) => t
haskell
printf
variadic-functions
polyvariadic
Dan Burton
źródło
źródło
Odpowiedzi:
Sztuczka polega na użyciu klas typów. W przypadku
printf
klucza jest toPrintfType
klasa typu. Nie ujawnia żadnych metod, ale ważna część i tak jest w typach.Więc
printf
ma przeciążony typ powrotu. W trywialnym przypadku nie mamy żadnych dodatkowych argumentów, więc musimy mieć możliwość wystąpieniar
doIO ()
. W tym celu mamy instancjęNastępnie, aby obsłużyć zmienną liczbę argumentów, musimy użyć rekursji na poziomie instancji. W szczególności potrzebujemy instancji, więc jeśli
r
jest aPrintfType
, typ funkcjix -> r
również jestPrintfType
.Oczywiście chcemy obsługiwać tylko argumenty, które faktycznie można sformatować. W tym miejscu
PrintfArg
pojawia się klasa drugiego typu . A więc rzeczywista instancja jestOto uproszczona wersja, która pobiera dowolną liczbę argumentów z
Show
klasy i po prostu je drukuje:Tutaj
bar
wykonuje akcję IO, która jest budowana rekurencyjnie, dopóki nie ma więcej argumentów, w którym to momencie po prostu ją wykonujemy.QuickCheck używa również tej samej techniki, w której
Testable
klasa ma instancję dla przypadku podstawowegoBool
i rekursywną dla funkcji, które pobierają argumenty wArbitrary
klasie.źródło
printf "%d" True
. Jest to dla mnie bardzo mistyczne, ponieważ wydaje się, że wartość czasu wykonania (?) Jest"%d"
odczytywana w czasie kompilacji, aby wymagać plikuInt
. To jest dla mnie absolutnie zaskakujące. . . zwłaszcza, że kod źródłowy nie używa takich rzeczy jakDataKinds
lubTemplateHaskell
(sprawdziłem kod źródłowy, ale go nie zrozumiałem).printf "%d" True
jest brakBool
wystąpieniaPrintfArg
. Jeśli przekażesz argument niewłaściwego typu, który ma wystąpieniePrintfArg
, kompiluje się i zgłasza wyjątek w czasie wykonywania. Np .:printf "%d" "hi"