类型奥秘。为什么这段代码可以编译?

Type mysteries. Why does this piece of code compile?

代码

default ()
h :: Bool
h = 1.0 == 1.0 --Error. Ambiguity.

不编译。这是意料之中的,因为存在歧义。它可以是 FloatDouble,Haskell 不知道我们想要哪一个。

但是代码

default ()
foo :: (Fractional a, Eq a) => a -> Bool
foo x = x == 1.0

编译成功。我不完全明白为什么。为什么这也不是模棱两可的?

我感觉这是因为每当我们调用 foo 时,我们保证选择了一个具体类型来代替 a,即我们保证有固定的 a 在编译时转换为 FloatDouble(或者我们的自定义类型同时具有 FractionalEq 的实例),因此没有歧义。

但这只是感觉,不知道是不是100%准确。

Section 4.3.4 of Haskell Report 中给出了此上下文中歧义的定义:

We say that an expression e has an ambiguous type if, in its type forall us. cx => t, there is a type variable u in us that occurs in cx but not in t. Such types are invalid.

foo :: (Fractional a, Eq a) => a -> Bool中没有这个变量(因为a出现在a -> Bool中)。 1.0 == 1.0里面的类型其实是(Fractional a, Eq a) => Bool,所以才会有这么一个变量。

原因这种歧义是一个错误是因为编译器没有办法在变量a的不同实例化之间进行选择,结果是该程序可以取决于它选择的是哪一个。在这种情况下,对于任何 合理的 实例化,1.0 == 1.0 应该始终是 True,但是 1) 编译器不知道这一点; 2)并非所有实例化都是合理的。

第二段代码编译通过,因为

foo :: (Fractional a, Eq a) => a -> Bool
foo x = x == 1.0

(==) :: Eq a => a -> a

所以编译器会知道浮点数文字 1.0 :: Rational a => ax 具有相同的类型,它有一个 Eq 实例,即 "meaning" 的 == 在这个函数中对于每次调用都是确定的。

而在第一段代码中,文字可以是任何类型(Rational a, Eq a) => a,所以==的"meaning"是有歧义的,h的值可以是True或false,取决于文字的类型,没有提供。

这是另一种概念性的表达方式:

这两种情况都有内在的歧义,但第一种是死胡同——编译器没有希望推断出解决歧义的方法。

第二种情况也有歧义,但歧义是公开的——无论谁调用歧义代码,都必须解决歧义,否则 delegate/bubble 它会进一步向上调用堆栈。

仅此而已 — 多态性是受控的歧义。第一种情况是不受控制的歧义,所以它没有用(即 polymorphism-related)歧义。所以它会被拒绝,直到它被注释为明确为止。