2016-03-08 87 views
1

此代码工作正常预期:为什么我不能在类型类实例中提供一个类型?

data Heh a = Heh a 
instance (Eq a) => Eq (Heh a) where 
    (Heh a1) == (Heh a2) = a1 == a2 

然而,这给出了一个错误:

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’ 
    --  ... 

唯一的变化是增加了:: a

但是,为什么会失败?是不是a1的确是a?还有什么可能呢?

这如何涉及到forall?我理解Haskell有一个forall关键字来处理这个问题。 (顺便说一句,如果直到GHC 8.0才能实现,请随时在即将到来的GHC 8.0中让我知道该怎么做。)

+0

是,涉及到'forall'因为这个关键字明确地标志着一个范围 - 但我仍然认为,范围只标明一个函数体内,而不是where子句,但我对'forall'没有专家。 – epsilonhalbe

+1

@ epsilonhalbe对你有正确的答案;您需要'forall'和'ScopedTypeVariables'扩展名的组合。显式'forall'还有其他用途(通过其他扩展),比如创建存在类型,或者只是为了明确。 –

+0

我推荐*总是*使用'ScopedTypeVariables'。 – dfeuer

回答

3

你是正确的,因为决议,这同forall关键字做打电话ghci的。不过,这个原因需要一些解释。如果你不关心那么多的解释,那就直接跳到最后。如果你还想要更多,请参阅GHC手册的Syntax ExtensionsOther Type System Extensions部分。

Haskell中由类型变量(例如data Heh a = Heh a)参数化的类型声明本质上是创建一个类型级函数(类型构造函数),它将一个类型作为输入并返回一个新类型。该的Heh“种”(一种是我们如何划分类型,如类型是如何分类的值)为* -> *,而Int是一种*Heh Int(适用于类型IntHeh类型构造函数)是一种*以及。因此,适用于*一个* -> *给出了另一个*,这是那种正常的类型。

变量,无论是在值级别或类型级别,必须一些结合形式,它定义了程序文本名称的特定变量是在其中结合的范围(区域结合效果)的约束力。在价值层面,我们已经习惯了看到这些粘合剂所有的地方:一些例子在功能定义模式参数绑定,在let表达式的绑定,并在lambda表达式的绑定(如\x -> x结合中的名称x表达的主体)。

混乱始于型级变量;他们似乎根本没有绑定,他们只是在类型声明的中间。这很大程度上是因为Haskell的类型系统是从Hindley-Milner类型系统开始下降的,而Hindley-Milner类型系统根本不具有类型注释,并且对于可能采用多种类型的表达式采用了不同的思维方式。多态类型的概念变得更加形式化,“polytyped”从辛德米尔纳值成了“多态”和理论的语法类型版本包含的类型级粘合剂和相应的价值层次的粘合剂,将采取和回报类型变量的表达式主体中的类型被替换的表达式。因为这些绑定器在编译时被完全解析,所以它们被排除在类型和值级别的语法之外。由于在原始系统中,活页夹只能在类型声明中的一个位置出现,因此不存在歧义。

如果你没有按照这一切,不用担心它;要点是,事情的发展是出于历史原因,因为基本概念以及它们在类型化函数式语言中实现的方式不断发展。

反正forall是类型级变量粘合剂,有点像一个lambda表达式是一个值级变量粘合剂。没有它,假设在每个绑定所有自由类型变量的类型级表达式的开始处有一个隐含的forall。明确地绑定类型变量使得它们更“清楚”,它们是“普遍量化的”(如果你不熟悉普遍量化的概念,请参阅一阶逻辑的介绍),并且也打开了“存在量化”的可能性“和更高排名的多态类型变量(扩展名为RankNTypes)。这似乎很奇怪,因为存在量化通常表示通过“存在”的粘合剂,但如果你把一个forall adata类型声明不把a进入活动范围本身(例如data Foo b = forall a. Foo (a, a -> b)),你得到相同的效果作为存在量化a(假设您启用ExistentialQuantification扩展,当然)。

但是你真正关心的是如何提高你的类型变量量化到整个instance声明的范围;这是ScopedTypeVariables扩展的用途。正如我之前所说的,Hindley-Milner类型系统最初并没有使用绑定和自由类型变量的概念(有逻辑解析算法中扮演角色的类型方案(或多类型)和monotypes)甚至类型注释。当首先加入类型注释,它们代表“未知monotypes”和称为刚好在所述特定表达被注释而不是一个范围的结合构造像forall或λ。因此,如果同一个名称在类型环境中出现不止一次,则它们将被重命名。通过打开ScopedTypeVariables,在明确绑定相同名称的绑定范围内表达的任何类型变量不再是未知单形变量(即来自错误消息的“刚性类型变量”),而是引用绑定类型变量该名称最近的封闭类型联编程序中的同名名称。

总之,这里的解决您的难题所需要的最终结果是:

{-# LANGUAGE ScopedTypeVariables #-} 

data Heh a = Heh a 
instance forall a. (Eq a) => Eq (Heh a) where 
    (Heh a1) == (Heh a2) = (a1 :: a) == a2 

forall在实例声明的范围遍及实例声明延伸,因为ScopedTypeVariables是积极的,在提及aa1上的类型注释是指与forall中的相同的a绑定。

如果你看的文档,你会发现有结合类型的变量,以及一些其他的语法结构。在这种情况下,forall实际上不是必需的,因为在classinstance声明头部类和实例变量自动绑定中相同的方式的类型变量为forall确实当扩展是有效的。但是,如果你有需要跨功能的整个身体先限定类型声明一个独立的功能,你需要知道如何使用forall得到你想要的类型变量的作用域。

+0

这个工程即使没有'forall'关键字,只要'ScopedTypeVariables'处于活动状态。我不确定这是故意还是错误,但确实如此。 – dfeuer

+2

我注意到在测试代码的同时我写了答案;不过,我几乎找不到原因并更新了答案。 –

+0

我还有两个问题:你做了什么,你是怎么做到的? :)所有这些知识......令人惊叹!最后的问题:Haskell专业程序员有多少比例知道你写的内容的一半?这是每个专业Haskell程序员最终应该学习的东西吗? – haskellHQ

8

类型检查器不知道最后的a线是相同它上面的一个给定的a,你有一个新的范围where子句中!

如果你想要把它们关掉(获得新的范围),你需要通过添加

{-# LANGUAGE ScopedTypeVariables #-} 

到您的源文件

的顶部,使ScopedTypeVariables,你可以做或键入:set -XScoped…内ghci中。

或与ghci -XScoped… myfile.hs

相关问题