Безопасность типов Haskell является второй никто не только к зависимому от типизированных языков. Но с Text.Printf творится какое-то глубокое волшебство, которое кажется довольно нестабильным .
> printf "%d\n" 3
3
> printf "%s %f %d" "foo" 3.3 3
foo 3.3 3
В чем заключается глубокая магия этого? Как Text.Printf.printf
функция может принимать такие вариативные аргументы?
Какой общий метод используется для разрешения вариативных аргументов в Haskell и как он работает?
(Примечание: при использовании этой техники, очевидно, теряется некоторая безопасность типов.)
> :t printf "%d\n" "foo"
printf "%d\n" "foo" :: (PrintfType ([Char] -> t)) => t
haskell
printf
variadic-functions
polyvariadic
Дэн Бертон
источник
источник
Ответы:
Уловка заключается в использовании классов типов. В случае
printf
с ключом являетсяPrintfType
класс типа. Он не предоставляет никаких методов, но в любом случае важная часть находится в типах.Так
printf
есть перегруженный возвращаемый тип. В тривиальном случае, у нас нет никаких дополнительных аргументов, поэтому мы должны быть в состоянии создать экземплярr
вIO ()
. Для этого у нас есть экземплярЗатем, чтобы поддерживать переменное количество аргументов, нам нужно использовать рекурсию на уровне экземпляра. В частности, нам нужен экземпляр, чтобы if
r
is aPrintfType
, тип функцииx -> r
также был aPrintfType
.Конечно, мы хотим поддерживать только аргументы, которые можно отформатировать. Здесь на помощь
PrintfArg
приходит второй класс типа . Фактический экземплярВот упрощенная версия, которая принимает любое количество аргументов в
Show
классе и просто выводит их:Здесь
bar
выполняется действие ввода-вывода, которое создается рекурсивно до тех пор, пока не будет больше аргументов, после чего мы просто выполняем его.QuickCheck также использует ту же технику, где у
Testable
класса есть экземпляр для базового случаяBool
и рекурсивный для функций, которые принимают аргументы вArbitrary
классе.источник
printf "%d" True
. Для меня это очень мистично, так как кажется, что значение времени выполнения (?)"%d"
Расшифровывается во время компиляции и требует наличияInt
. Для меня это совершенно сбивает с толку. . . особенно потому, что в исходном коде не используются такие вещи, какDataKinds
илиTemplateHaskell
(я проверил исходный код, но не понял его.)printf "%d" True
состоит в том, что нетBool
экземпляраPrintfArg
. Если вы передаете аргумент неправильного типа, у которого есть экземплярPrintfArg
, он компилируется и генерирует исключение во время выполнения. Пример:printf "%d" "hi"