为什么我不能在类型类实例中提供类型?

时间:2016-03-08 23:05:11

标签: haskell

此代码按预期正常工作:

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中进行操作。)

2 个答案:

答案 0 :(得分:8)

类型检查器不知道最后一行中的python -m my_project.script与上面一行中的sys.path.append()相同,在a子句中你有一个新范围!

如果您想关闭它(获取新范围),您需要启用a,您可以通过添加

来执行此操作
where

到源文件的顶部

或在ghci中输入ScopedTypeVariables

或使用{-# LANGUAGE ScopedTypeVariables #-}

调用ghci

答案 1 :(得分:3)

您需要更正此问题的解决方案与forall关键字有关。但原因需要一些解释。如果你不在乎那么多的解释,请继续跳到最后。如果您还想了解更多信息,请参阅GHC手册的Syntax ExtensionsOther Type System Extensions部分。

Haskell中由类型变量(例如data Heh a = Heh a)参数化的类型声明实质上是创建一个类型级函数(类型构造函数),它将类型作为输入并返回一个新类型。 "种类" Heh(我们如何对类型进行分类,类型是我们如何对值进行分类)是* -> *,而Int属于*Heh Int({应用于类型Heh的{​​{1}}类型构造函数也是类Int。因此,应用于*的{​​{1}}会提供另一个* -> *,这是正常类型。

变量,无论是在值级别还是类型级别,都必须绑定一些绑定表单,它定义范围(程序文本的区域,其中)绑定的名称与特定变量的绑定有效。在值级别,我们习惯于在整个地方看到这些绑定器:一些示例是函数定义模式中的参数绑定,*表达式中的绑定以及lambda表达式中的绑定(例如{ {1}}在表达式主体中绑定名称*

混淆开始于类型级变量;他们似乎根本没有任何活页夹,他们只是在类型声明的中间那里。这主要是因为Haskell的类型系统来自Hindley-Milner类型系统,该系统根本没有最初的类型注释,并且使用了一种不同的方式来思考可能采用多种类型的表达式。随着多态类型的概念变得更加正式化," polytyped"来自Hindley-Milner的价值变成了多态的"并且该理论的语法类型版本包括类型级绑定器和相应的值级绑定器,它们将采用类型并返回一个表达式,其类型变量在表达式的主体中替换。因为这些绑定器在编译时完全解析,所以它们被排除在类型和值级语法之外。由于在原始系统中,绑定器只能出现在类型声明中的一个位置,因此没有歧义。

如果你没有遵循这一切,不要担心;要点是,由于基本概念以及它们在类型化函数语言中的实现方式的发展,事情因历史原因而发生了变化。

无论如何,let是类型级变量绑定器,有点像lambda表达式是值级变量绑定器。没有它,假设在每个类型级表达式的开头都有一个隐式\x -> x,它绑定了所有自由类型变量。明确地绑定类型变量可以更清楚地表明它们是“普遍量化的”#34; (如果您不熟悉通用量化的概念,请参见一阶逻辑的介绍),并开启了存在性量化的可能性"排名较高的多态类型变量(带有x扩展名)也是如此。这可能看起来很奇怪,因为存在量化通常通过"存在"表示。如果你将forall置于forall类型声明中并且未将RankNTypes带入范围本身(例如forall a),则会获得相同的效果作为data的存在量化(当然,假设您启用了a扩展名。)

但您真正关心的是如何将类型变量量化的范围扩大到整个data Foo b = forall a. Foo (a, a -> b)声明;这是ScopedTypeVariables扩展名的用途。正如我之前所说,Hindley-Milner类型系统最初没有使用绑定和自由类型变量的概念(有类型方案(或多类型)和单一类型,它们在逻辑分辨率算法中起作用)甚至类型注释。当首次添加类型注释时,它们代表了未知的单一类型"并且仅提到注释的特定表达式,而不是像a或lambda那样的作用域绑定构造。因此,如果同一名称恰好在类型环境中出现多次,则会对其进行重命名。通过启用ExistentialQuantification,在绑定相同名称的显式instance绑定范围内表示的任何类型变量不再是未知的单型变量(即"刚性类型变量"来自您的错误消息),而是在该名称的最近的封闭类型绑定器中引用相同名称的绑定类型变量。

无论如何,这是解决难题所需的最终结果:

forall

实例声明中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 上类型注释中对forall的引用在ScopedTypeVariables绑定中引用相同的a

如果查看文档,您会发现还有一些其他语法结构也可以绑定类型变量。在这种情况下,实际上不需要a1,因为aforall声明头中的类和实例变量会以与forall相同的方式自动绑定类型变量当扩展名处于活动状态时。但是如果你有一个带有类型声明的独立函数,需要在函数的整个主体范围内进行范围化,那么你需要知道如何使用class来获得你想要的类型变量作用域。 / p>