Why is printf "%d\n" 3
ambiguous but not show 3
? Could the printf
module be rewritten to provide automatic disambiguation? Presumably something like show
must be done at the lower levels of printf
... or is there some crucial difference between printf
and show
that requires the disambiguation of numbers?
If printf
can be rewritten to automatically handle numbers without explicit disambiguation, then what is show
doing right? How does show
turn numbers into strings without the :: Int
disambiguation of printf
?
Here is the correct operation of show
(without any disambiguation) and also the correct operation of printf
(with the disambiguation):
$ cat printStrLnShow3
import Text.Printf
main = putStrLn (show 3)
$ runghc printStrLnShow3
3
$ cat printfWithInt3
import Text.Printf
main = printf "%d\n" (3 :: Int)
$ runghc printfWithInt3
3
Here is the ambiguous variable error when printf
does not disambiguate the number:
$ cat printfWithAmbiguous3
import Text.Printf
main = printf "%d\n" 3
$ runghc printfWithAmbiguous3
printfWithAmbiguous3:2:8:
No instance for (PrintfArg a0) arising from a use of `printf'
The type variable `a0' is ambiguous
Possible fix: add a type signature that fixes these type variable(s)
Note: there are several potential instances:
instance [safe] PrintfArg Char -- Defined in `Text.Printf'
instance [safe] PrintfArg Double -- Defined in `Text.Printf'
instance [safe] PrintfArg Float -- Defined in `Text.Printf'
...plus 12 others
In the expression: printf "%d" 3
In an equation for `main': main = printf "%d" 3
printfWithAmbiguous3:2:22:
No instance for (Num a0) arising from the literal `3'
The type variable `a0' is ambiguous
Possible fix: add a type signature that fixes these type variable(s)
Note: there are several potential instances:
instance Num Double -- Defined in `GHC.Float'
instance Num Float -- Defined in `GHC.Float'
instance Integral a => Num (GHC.Real.Ratio a)
-- Defined in `GHC.Real'
...plus 11 others
In the second argument of `printf', namely `3'
In the expression: printf "%d" 3
In an equation for `main': main = printf "%d" 3
This is a quirk of the defaulting rules, which explicitly say that you may default a typeclass-polymorphic value to something monomorphic only when there are a fixed set of classes in the context:
In situations where an ambiguous type is discovered, an ambiguous type variable,
v
, is defaultable if:
v
appears only in constraints of the formC v
, whereC
is a class, and- at least one of these classes is a numeric class, (that is,
Num
or a subclass ofNum
), and- all of these classes are defined in the
Prelude
or a standard library (Figures 6.2–6.3 show the numeric classes, and Figure 6.1 shows the classes defined in thePrelude
.)Each defaultable variable is replaced by the first type in the default list that is an instance of all the ambiguous variable’s classes. It is a static error if no such type is found.
(Section 4.3.4 of the Report.) The troubling one here is bullet point 3, because the PrintfArg a
constraint on the type of 3 :: (Num a, PrintfArg a) => a
mentions the class PrintfArg
which is not in the Prelude
.
GHC offers the ExtendedDefaultRules
pragma to relax these rules, as described in the manual:
Find all the unsolved constraints. Then:
- Find those that are of form
(C a)
wherea
is a type variable, and partition those constraints into groups that share a common type variablea
.- Keep only the groups in which at least one of the classes is an interactive class (defined below).
- Now, for each remaining group G, try each type ty from the default-type list in turn; if setting
a
= ty would allow the constraints in G to be completely solved. If so, defaulta
to ty.- The unit type
()
and the list type[]
are added to the start of the standard list of types which are tried when doing type defaulting.Note that any multi-parameter constraints
(D a b)
or(D [a] Int)
do not participate in the process (either to help or to hinder); but they must of course be soluble once the defaulting process is complete.
And indeed, turning on this pragma makes your file work:
{-# LANGUAGE ExtendedDefaultRules #-}
import Text.Printf
main = printf "%d\n" 3