为什么 Haskell 需要为 printf 消除数字歧义而不是为 show 消除数字歧义?

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

为什么 printf "%d\n" 3 有歧义而不是 show 3?能否重写 printf 模块以提供自动消歧功能?大概 show 之类的事情必须在 printf 的较低级别完成......或者 printfshow 之间是否存在一些需要消除数字歧义的关键区别?

如果 printf 可以 重写为自动处理数字而无需明确消除歧义,那么 show 做对了什么? show 如何在没有 printf:: Int 消歧的情况下将数字转换为字符串?

下面是show的正确操作(没有任何歧义)和printf的正确操作(有歧义):

$ 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

这是 歧义变量 错误,当 printf 消除数字歧义时:

$ 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

这是默认规则的一个怪癖,它明确指出只有当 class 中有一组固定的上下文:

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。)这里令人不安的是要点 3,因为 3 :: (Num a, PrintfArg a) => a 类型的 PrintfArg a 约束提到了 class PrintfArg 不在 Prelude.

GHC 提供 ExtendedDefaultRules 用法来放宽这些规则,如 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.

事实上,打开这个 pragma 可以让你的文件工作:

{-# LANGUAGE ExtendedDefaultRules #-}
import Text.Printf
main = printf "%d\n" 3