此代码正常工作正常:
data Heh a = Heh a
instance (Eq a) => Eq (Heh a) where
(Heh a1) == (Heh a2) = a1 == a2
Run Code Online (Sandbox Code Playgroud)
然而这给出了一个错误:
data Heh a = Heh a
instance (Eq a) => Eq (Heh a) where
(Heh a1) == (Heh a2) = (a1 :: a) == a2
-- Error: Couldn't match expected type ‘a1’ with actual type ‘a’
-- ...
Run Code Online (Sandbox Code Playgroud)
唯一的变化是添加:: a.
但为什么会失败呢?是不是a1真的a?还有什么可能呢?
这与此forall有何关系?我理解Haskell有一个forall关键字来处理这个问题.(顺便说一下,如果在GHC 8.0之前无法实现这一点,请随时告诉我如何在即将推出的GHC 8.0中进行操作.)
类型检查器不知道a最后一行中的内容与a上面一行中的给定相同,在where子句中你有一个新的范围!
如果你想关闭它(获得新的范围),你需要ScopedTypeVariables通过添加来启用你可以做的
{-# LANGUAGE ScopedTypeVariables #-}
Run Code Online (Sandbox Code Playgroud)
到源文件的顶部
或输入:set -XScoped…ghci.
或者用它来调用ghci ghci -XScoped… myfile.hs
您是对的,解决这个问题与关键字有关forall。不过,原因需要一些解释。如果您不喜欢那么多解释,请直接跳到最后。如果您还需要更多信息,请参阅GHC 手册的语法扩展和其他类型系统扩展部分。
Haskell 中由类型变量参数化的类型声明(例如 )data Heh a = Heh a本质上是创建一个类型级函数(类型构造函数),该函数将类型作为输入并返回新类型。(种类Heh是我们对类型进行分类的方式,就像类型是我们对值进行分类的方式一样)的“种类”是* -> *whileInt是种类*,并且Heh Int(Heh应用于类型的类型构造函数Int)也是种类*。所以 a* -> *应用于 a*会得到 another *,这是一种普通类型。
变量,无论是在值级别还是类型级别,都必须通过某种绑定形式进行绑定,该形式定义了绑定的范围(名称与特定变量的绑定生效的程序文本区域)。在值级别,我们习惯于在各处看到这些绑定器:一些示例是函数定义模式中的参数绑定、表达式中的绑定以及 lambda 表达式中的绑定(例如,将名称绑定在函数体中)表达)。let\x -> xx
混乱始于类型级变量;它们似乎根本没有绑定器,它们只是位于类型声明的中间。这主要是因为 Haskell 的类型系统源自 Hindley-Milner 类型系统,该系统最初根本没有类型注释,并且使用了一种稍微不同的方式来思考可以采用多种类型的表达式。随着多态类型的概念变得更加形式化,Hindley-Milner 中的“多类型”值变得“多态”,并且该理论的语法类型版本包括类型级绑定器和相应的值级绑定器,它们将采用类型并返回表达式用类型变量的表达式主体中替换的类型。由于这些绑定程序在编译时已完全解析,因此它们被排除在类型级和值级语法之外。由于在原始系统中,绑定器只能出现在类型声明中的一个位置,因此不存在歧义。
如果您没有遵循所有这些,请不要担心;要点是,由于历史原因,随着基本概念及其在类型化函数语言中实现方式的演变,事情的发展方式是这样的。
不管怎样,它forall是类型级变量绑定器,有点像 lambda 表达式是值级变量绑定器。如果没有它,则假定forall每个类型级表达式的开头都有一个隐式绑定所有自由类型变量。显式绑定类型变量可以更清楚地表明它们是“普遍量化的”(如果您不熟悉普遍量化的概念,请参阅一阶逻辑简介),并且还提供了“存在量化”的可能性" 以及更高级别的多态类型变量(带有RankNTypes扩展名)。这可能看起来很奇怪,因为存在量化通常是通过“存在”绑定器来表达的,但是如果您将 a 放入类型声明forall a中,data而该类型声明不会将a其本身带入范围(例如data Foo b = forall a. Foo (a, a -> b)),那么您将获得与 ( 上的存在量化相同的效果a)当然,假设您启用了ExistentialQuantification扩展)。
但您真正关心的是如何将类型变量量化的范围扩大到整个instance声明;这就是ScopedTypeVariables扩展的用途。正如我之前所说,Hindley-Milner 类型系统最初没有使用绑定和自由类型变量的概念(有类型方案(或多类型)和单类型,它们在逻辑解析算法中发挥作用),甚至没有类型注释。当类型注释第一次被添加时,它们代表“未知的单类型”,并且仅指被注释的特定表达式,而不是像 aforall或 lambda 这样的作用域绑定构造。因此,如果同一个名称碰巧在类型环境中出现多次,则它们可能会被重命名。通过打开ScopedTypeVariables,在绑定相同名称的显式绑定范围内表达的任何类型变量forall不再是未知的单类型变量(即错误消息中的“刚性类型变量”),而是对绑定类型变量的引用在该名称的最近的封闭类型活页夹中具有相同的名称。
无论如何,这是解决您的难题所需的最终结果:
{-# LANGUAGE ScopedTypeVariables #-}
data Heh a = Heh a
instance forall a. (Eq a) => Eq (Heh a) where
(Heh a1) == (Heh a2) = (a1 :: a) == a2
Run Code Online (Sandbox Code Playgroud)
实例声明中的范围forall延伸到整个实例声明,并且由于处于活动状态,因此类型注释中ScopedTypeVariables对 on 的引用在绑定中引用相同的内容。aa1aforall
如果您查看文档,您会发现还有一些其他语法结构也绑定类型变量。在这种情况下,实际上没有必要,因为和声明forall头部中的类和实例变量会以与扩展处于活动状态时相同的方式自动绑定类型变量。但是,如果您有一个带有类型声明的独立函数,该函数的作用域需要跨越整个函数体,则您需要知道如何使用来获取所需的类型变量作用域。classinstanceforallforall