为何GHCi中`Ambiguous type..`错误表现存在差异?
先看你遇到的三个场景:
场景1:正常运行的代码
创建文件写入:
import Safe t1 = tailMay []
加载到GHCi后执行print t1,输出Nothing,完全正常。
场景2:运行失败的代码
但如果改成这样就报错了:
import Safe t1 = tailMay [] t2 = print $ tailMay []
GHCi抛出的错误是:
Ambiguous type variable
a0' arising from a use ofprint' prevents the constraint(Show a0)' from being solved. Probable fix: use a type annotation to specify whata0' should be.
These potential instances exist:
instance Show Ordering -- Defined inGHC.Show' instance Show Integer -- Defined inGHC.Show'
instance Show a => Show (Maybe a) -- Defined in `GHC.Show'
...plus 22 others
场景3:同样报错的代码
第三个示例也触发了相同错误:
import Safe t1 = tailMay main = do print $ t1 [] print $ t1 [1,2,3]
错误产生的原因
这本质是Haskell的类型推断机制和多态类型约束共同作用的结果:
- 先看
tailMay的类型:它是[a] -> Maybe a,也就是说,给它一个任意类型的列表,返回对应类型的Maybe值。 - 当你写
tailMay []时,这个空列表[]的类型是[a],这里的a是一个完全没有约束的类型变量——编译器不知道你想让它代表什么具体类型(Int?String?还是自定义类型?)。 print函数的类型是Show b => b -> IO (),它要求参数必须是一个能被展示的类型(即实现了Show类型类)。对于print $ tailMay []来说,参数是Maybe a,而Maybe a的Show实例依赖于a本身的Show实例(因为instance Show a => Show (Maybe a))。- 问题来了:编译器完全不知道
a应该是什么类型——它可以是任何实现了Show的类型,也可以是没实现的,但print需要确定的Show实例才能编译。这种“不知道选哪个类型”的情况就是类型歧义,所以编译器报错了。
那为什么场景1没问题?因为场景1中t1只是被定义为tailMay [],并没有被用到需要Show约束的地方。直到你在GHCi里执行print t1时,GHCi会默认给a分配一个符合Show的默认类型(比如(),即空元组),所以能正常输出。但在场景2和3中,print是直接写在模块定义里的,编译器在编译阶段就需要确定类型,没有GHCi那种交互式的默认推断,所以就报错了。
无需显式类型注解修复场景2的方法
如果你不想写显式的类型注解(比如tailMay ([] :: [Int])),可以用以下两种方法:
方法1:启用ExtendedDefaultRules扩展
Haskell默认的类型默认规则比较保守,只对少数类型(比如数字类型)生效。开启ExtendedDefaultRules扩展后,编译器会对更多类型变量应用默认值,比如会把无约束的a默认设为()(空元组,它天生实现了Show),这样就能解决歧义。
修改后的代码:
{-# LANGUAGE ExtendedDefaultRules #-} import Safe t1 = tailMay [] t2 = print $ tailMay []
这样编译和运行都不会报错,print会输出Nothing。
方法2:让编译器从上下文推断类型
如果你的模块里已经有其他使用具体类型的代码,编译器可以通过上下文自动推断a的类型。比如,如果你在模块里加一个使用[Int]的定义:
import Safe t1 = tailMay [] someIntList = [1,2,3] :: [Int] t2 = print $ tailMay []
这时候编译器会推断出tailMay []中的a是Int(因为模块里有[Int]的上下文),Maybe Int有Show实例,所以不会报错。不过这种方法依赖于模块内的其他代码,不如扩展方法通用。
内容的提问来源于stack exchange,提问作者Vladimir

