繁体   English   中英

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

[英]Why can't I provide a type within a typeclass instance?

此代码可以正常工作:

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中做到这一点。)

类型检查不知道a在最后一行是一样的a在它上面的一个给定的,里面where你有一个新的范围条款!

如果要关闭它(获取新的作用域),则需要启用ScopedTypeVariables ,您可以通过添加

{-# LANGUAGE ScopedTypeVariables #-}

到源文件的顶部

或在ghci中输入:set -XScoped…

或使用ghci -XScoped… myfile.hs调用ghci ghci -XScoped… myfile.hs

您是正确的,这与forall关键字有关。 但是,该原因需要一些解释。 如果您不需要太多解释,请继续并跳到最后。 如果仍然需要更多信息,请参见《 GHC手册》的“ 语法扩展”和“ 其他类型的系统扩展”部分。

Haskell中由类型变量参数化的类型声明,例如data Heh a = Heh a ,实际上是在创建一个类型级别的函数(类型构造函数),该函数将类型作为输入并返回新类型。 Heh的“种类”(一种是我们对类型进行分类的方式,就像类型是对值进行分类的方式)是* -> *Int是种类*Heh Int (应用于Int类型的Heh类型构造函数)是以及* 因此,将* -> *应用于*会得到另一个* ,这是普通类型。

变量,无论是在值级别还是在类型级别,都必须通过某种绑定形式绑定 ,该绑定形式定义了绑定的范围(名称与特定变量的绑定有效的程序文本区域)。 在值级别,我们习惯于无处不在看到这些绑定器:一些示例是函数定义模式中的参数绑定, let表达式中的绑定以及lambda表达式中的绑定(例如\\x -> x绑定了在表达式主体内命名x )。

混乱始于类型级别的变量。 他们似乎没有粘合剂可言,它们只是存在于类型声明的中间。 这主要是因为Haskell的类型系统源自Hindley-Milner类型系统,该系统最初根本没有类型注释,并且使用了一种可以考虑多种类型的表达式的不同方法。 随着多态类型的概念变得更加形式化,Hindley-Milner的“多型”值变成了“多态”,并且该理论的句法类型版本包括类型级别的绑定器和对应的价值级别的绑定器,这些绑定器将接受一个类型并返回一个表达式用表达式主体中的类型替换类型变量。 由于这些绑定程序在编译时已完全解析,因此它们不在类型和值级别语法中。 由于在原始系统中,绑定器只能在类型声明中的某个位置出现,所以这里没有歧义。

如果您不遵循所有这些,就不必担心。 要点是,随着历史的发展,随着基础概念的发展以及以类型化功能语言实现它们的方式,事情得以解决。

无论如何, forall是类型级变量粘合剂,有点像一个lambda表达式是一个值级变量粘合剂。 如果没有它,则假定在每个类型级别表达式的开头都存在一个隐式的forall ,它绑定了所有自由类型变量。 显式绑定类型变量可以使它们更清楚地表明它们是“通用量化的”(如果您不熟悉通用量化的概念,请参见一阶逻辑简介),并且还提供了进行“现有量化的”可能性”以及排名较高的多态类型变量(具有RankNTypes扩展名)。 这似乎很奇怪,因为存在量化通常是通过“存在”活页夹来表示的,但是如果您在data类型声明中放入forall a ,则不会将a本身纳入范围内(例如, data Foo b = forall a. Foo (a, a -> b) )与对a的存在量化a相同的效果(当然,假设您启用ExistentialQuantification扩展)。

但是,您真正关心的是如何将类型变量量化的范围扩大到整个instance声明。 这就是ScopedTypeVariables扩展的用途。 正如我之前说过的,Hindley-Milner类型系统最初没有使用绑定和自由类型变量(存在类型方案(或多型)和单型,在逻辑解析算法中发挥作用)的概念,甚至没有使用类型注释。 首次添加类型注释时,它们表示“未知单型”,仅指被注释的特定表达式,而不是诸如forall或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

所述的范围forall在实例声明整个实例声明延伸,并且因为ScopedTypeVariables是活动的,则参照a在上类型的注释a1指的是相同的aforall结合。

如果您查看文档,则会发现还有一些绑定类型变量的语法构造。 在这种情况下, forall实际上不是必需的,因为在头部的类和实例变量classinstance声明自动绑定中相同的方式的类型变量为forall当扩展被激活一样。 但是,如果您有一个带有类型声明的独立函数,而该声明需要在函数的整个范围内进行作用域定义,则需要了解如何使用forall来获得所需的类型变量作用域。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM