Haskell’s type safety is second to none only to dependently-typed languages. But there is some deep magic going on with Text.Printf that seems rather type-wonky.
> printf "%d\n" 3
3
> printf "%s %f %d" "foo" 3.3 3
foo 3.3 3
What is the deep magic behind this? How can the Text.Printf.printf function take variadic arguments like this?
What is the general technique used to allow for variadic arguments in Haskell, and how does it work?
(Side note: some type safety is apparently lost when using this technique.)
> :t printf "%d\n" "foo"
printf "%d\n" "foo" :: (PrintfType ([Char] -> t)) => t
The trick is to use type classes. In the case of
printf, the key is thePrintfTypetype class. It does not expose any methods, but the important part is in the types anyway.So
printfhas an overloaded return type. In the trivial case, we have no extra arguments, so we need to be able to instantiatertoIO (). For this, we have the instanceNext, in order to support a variable number of arguments, we need to use recursion at the instance level. In particular we need an instance so that if
ris aPrintfType, a function typex -> ris also aPrintfType.Of course, we only want to support arguments which can actually be formatted. That’s where the second type class
PrintfArgcomes in. So the actual instance isHere’s a simplified version which takes any number of arguments in the
Showclass and just prints them:Here,
bartakes an IO action which is built up recursively until there are no more arguments, at which point we simply execute it.QuickCheck also uses the same technique, where the
Testableclass has an instance for the base caseBool, and a recursive one for functions which take arguments in theArbitraryclass.