Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why does Haskell require numbers to be disambiguated for printf but not for show?

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
like image 938
Rick Majpruz Avatar asked Oct 17 '17 15:10

Rick Majpruz


1 Answers

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 form C v, where C is a class, and
  • at least one of these classes is a numeric class, (that is, Num or a subclass of Num), 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 the Prelude.)

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) where a is a type variable, and partition those constraints into groups that share a common type variable a.
  • 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, default a 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
like image 75
Daniel Wagner Avatar answered Oct 16 '22 20:10

Daniel Wagner