【问题标题】:Haskell rules for selecting between type instances for Int, Double and Integer用于在 Int、Double 和 Integer 类型实例之间进行选择的 Haskell 规则
【发布时间】:2015-09-13 13:02:52
【问题描述】:

我想知道在 GHCi 中评估表达式 1 .+. 2 时,Haskell 使用什么规则来始终决定 Integer 实例而不是其他实例:

import Debug.Trace

class MyFuns a where
    (.+.) :: a →  a →  a

instance MyFuns Double where
    x .+. y = trace "Double " $ x + y

instance MyFuns Integer where
    x .+. y = trace "Integer " $ x + y

instance MyFuns Int where 
    x .+. y = trace "Int " $ x + y

编辑:如果我在文件末尾添加以下代码

main = do
   let x = 1 .+. 2
   print x

为什么会出现这个错误?

No instance for (Num a0) arising from the literal ‘1’
The type variable ‘a0’ is ambiguous
Relevant bindings include x :: a0 (bound at fun2.hs:19:7)
Note: there are several potential instances:
  instance Integral a => Num (GHC.Real.Ratio a)
    -- Defined in ‘GHC.Real’
  instance Num Integer -- Defined in ‘GHC.Num’
  instance Num Double -- Defined in ‘GHC.Float’
  ...plus three others
In the first argument of ‘(.+.)’, namely ‘1’
In the expression: 1 .+. 2
In an equation for ‘x’: x = 1 .+. 2

然而,如果我在没有main = ... 的情况下在 GHCi 提示符处加载文件,然后键入 1 .+. 2,GHCi 会按预期打印 3。为什么会有这种行为?

谢谢

【问题讨论】:

标签: haskell


【解决方案1】:

section 4.3.4 in the Haskell report。当 Haskell 读取一个看起来是整数的文字(没有小数部分)时,类型是(除非更具体地推断)Num a => a需要选择类型的那一刻,它使用默认规则,通常默认为Integer。当文字有小数部分时,它是Fractional a => a,通常默认为Double

通过使用default 顶级声明,您可以更改这些设置,例如:

default (Int, Float)

默认NumIntFractionalFloat(因为Int 不是Fractional)。 请注意,此语句的效果是在其中声明它的模块本地的。

默认语句具有以下效果(引用报告):

每个可默认变量都被默认列表中的第一个类型替换,该类型是所有不明确变量类的实例。如果找不到此类类型,则为静态错误。

-XExtendedDefaultRules GHC 标志具有附加效果,请参阅here


编辑

至于你的错误,来源是以下声明,在GHC user guide和报告第4.3.4节的不同措辞中:

但是,用户必须指定类型很烦人,因此 GHCi 扩展了 Haskell 的类型默认规则(Haskell 2010 报告的第 4.3.4 节)如下。标准规则为每个类型变量a取每组约束(C1 a, C2 a, ..., Cn a),并默认类型变量if

  1. 类型变量a没有出现在其他约束中

  2. 所有的类 Ci 都是标准的。

  3. 至少有一个类 Ci 是数字。

我有意将重点放在第二个项目符号上。因为您使用的是.+.,所以其中一个数字类是MyFuns——它不是 Prelude 或标准库中的类,所以它不是“标准”类。幸运的是,正文继续如下:

在 GHCi 提示符下,或者如果给定 -XExtendedDefaultRules 标志,则在 GHC 中适用以下附加差异:

  • 上面的规则 2 放宽了:所有的类 Ci 都是单参数类型的类。

  • 上面的规则 3 放宽了这一点:Ci 类中至少有一个是数字,或者是 Show、Eq 或 Ord。

  • 单位类型 () 被添加到标准类型列表的开头,在进行类型默认时会尝试这些类型。

总之,如果您使用ExtendedDefaultRules 标志(如您所见,默认情况下它在 GHCi 中处于活动状态),您的代码也可以与您的自定义类一起正常编译:

{-# LANGUAGE ExtendedDefaultRules #-}

import Debug.Trace

default (Int, Float, Double)

class MyFuns a where
    (.+.) :: a -> a -> a

instance MyFuns Double where
    x .+. y = trace "Double " $ x + y

instance MyFuns Integer where
    x .+. y = trace "Integer " $ x + y

instance MyFuns Int where
    x .+. y = trace "Int " $ x + y

main = do
   print $ 1 .+. 2 -- Interpreted as Int

请注意,在此示例中,1.0 .+. 2.0 被解释为 double,而 1.0 + 2.0 被解释为 float:这是因为 Float 没有 MyFuns 实例,因此它在 default 列表中的条目被跳过。

【讨论】:

  • 我已经添加了default (Int, Float),但仍然没有调用 Int 实例,所以解决方案必须是别的东西。我正在使用 GHCi 7.8.3
  • 即使我通过 default () 禁用了 Num 的默认规则,仍然会调用 Integer 实例。
  • 我认为发生的事情是 GHCi 没有考虑到源文件中的默认语句(报告指出 default 语句的效果仅限于模块)。如果我用你的源代码加载一个模块并输入5 .+. 9,我会得到一个Integer。之后我输入default (Int, Float)(在ghci中),然后再次输入5 .+. 9,然后我得到一个Int。所以它对我有用。
  • 我测试并得到了和你一样的行为。这可能是 GHCi 中的一个错误 ...
  • @mljrg 如果仅仅因为您加载了一些第 3 方模块而突然更改默认类型,那可能会更多令人困惑......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-30
  • 1970-01-01
  • 1970-01-01
  • 2017-06-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多