在GHSP documentation的ScopedTypeVariables扩展中,概述将以下内容作为设计原则:
作用域类型变量代表类型 variable ,而不代表 type 。 (这是对GHC早期设计的更改。)
我知道范围化类型变量扩展的一般用途,但我不知道在这里代表类型变量和代表类型之间区别的含义。从语言用户的角度来看,差异的意义何在?
上面的评论暗示了两种设计,它们以不同的方式处理此决定并做出不同的权衡。什么是替代设计,它与当前实施的设计相比如何?
答案 0 :(得分:4)
tl; dr:该文档说明了它的意思,因为GHC中作用域类型变量的旧实现是不同的,而新文档(过度)则强调了旧行为与新行为之间的对比行为。实际上,使用ScopedTypeVariables
扩展名使用的作用域类型化变量只是普通的旧(刚性)类型变量,并且这些变量与您在常规Haskell类型签名中使用的类型变量相同,没有作用域(甚至如果您没有意识到它们是“刚性的”)。确实,作用域类型变量不只是“代表类型”,而且常规的无范围类型变量也不是单纯地“代表类型”。
更长的答案:
首先,撇开范围类型变量的问题,请考虑以下因素:
pluralize :: [a] -> [a]
pluralize x = x ++ "s"
如果将a
作为类型变量,简单地“理解为类型”,就可以了。 GHC将确定a
代表类型Char
,并且将得到的签名[Char] -> [Char]
确定为正确的pluralize
类型,因此不会有问题。实际上,如果我们要推断以下类型:
pluralize x = x ++ "s"
在普通的老式Hindley-Milner(HM)类型系统中,这可能正是发生的情况。作为键入(++)
应用程序的中间步骤,类型检查器将为x
的类型[a]
分配“新鲜的” HM类型变量a
,并且它将分配{在将pluralize
与类型[a] -> [a]
统一为[a]
和"s" :: [Char]
之前,先将{1}}的类型为a
。
相反,它被GHC类型检查器拒绝,因为此类型签名中的Char
不是HM风格的类型变量, not 也不仅仅代表类型。相反,它是一个严格的(即用户指定的)Haskell类型变量,并且类型检查器不允许在定义a
时将此类变量与自身以外的任何其他变量统一。
类似地,以下内容被拒绝:
pluralize
即使pairlist :: a -> b -> [a]
pairlist x y = [x,y]
和a
仅代表类型,也可以(因为它适用于{{1}的任何b
和a
类型},前提是b
和*
是同一类型)。相反,它被类型检查器拒绝,因为两个刚性的Haskell类型变量a
和b
无法统一。
现在,您可以尝试说明问题不是不是,因为类型变量是“刚性的”并且不能与具体类型(如a
)或每个类型统一其他,但Haskell类型签名中存在隐式量化,因此b
的签名实际上是:
Char
,因此,当确定pluralize
为“ pluralize :: forall a . [a] -> [a]
的代表”时,此a
量化的矛盾就会触发错误。该论点的问题在于,两种解释实际上或多或少是等效的。因为Haskell类型变量是刚性的(即因为Haskell中的类型签名被隐式地普遍量化),所以类型无法统一(即,统一与量化矛盾)。但是,事实证明,“刚性类型变量”的解释比“隐式量化”的解释更接近GHC类型检查器中实际发生的情况。因此,以上定义所产生的错误消息表示无法匹配刚性类型变量,而不是与通用类型变量量化的矛盾。
现在,让我们回到范围类型变量的问题上。在过去,GHC的Char
扩展名的实现方式大不相同。特别是对于模式类型签名,您可以编写以下内容(取自documentation for GHC 6.0):
forall a
文档继续说:
-fscoped-type-variables
左侧的模式类型签名表示以下事实:f :: [Int] -> Int -> Int f (xs::[a]) (y::a) = (head xs + y) :: a
必须是某种f
类型的事物的列表;并且xs
必须具有相同的类型。表达式a
[sic] 上的类型签名指定此表达式必须具有相同的类型y
。 不要求“(head xs)
”命名的类型实际上是类型变量。实际上,在这种情况下,“a
”命名的类型为{{ 1}}。 (这是对原始的相当复杂的规则的轻微放宽,该规则指定了模式绑定类型变量应被普遍量化。)
接着给出了使用范围类型变量的其他一些示例,例如:
a
2006年,Simon Peyton-Jones做出了重大承诺(ac10f840
),以增加对类型系统的隐含性,最终也大大改变了词法作用域类型变量的实现。提交文本包含对更改的详细说明,包括新设计的要求。
一个关键的设计选择是现在将词法范围的类型变量命名为刚性(即,用户指定的多态)Haskell类型变量,而不是像简单地代表一种类型并受统一约束的HM风格变量一样。
这使上述示例(a
,Int
和g (x::a) (y::b) = [x,y] -- a unifies with b
k (x::a) True = ... -- a unifies with Int
k (x::Int) False = ...
成为非法,因为模式匹配中的作用域类型变量现在的行为更像常规的刚性类型变量。
Soooo ...旧的设计可能是一个奇怪的技巧,它使作用域类型变量更像HM类型变量,并且与“常规” Haskell类型变量有很大的不同,而新系统使它们更加与无范围类型一致表现得很正常。
但是,更复杂的是,注释中的@duplode链接引用了一个建议,该建议在模式匹配的签名上下文中部分“撤消”此“限制”。我 可以说,旧的设计将作用域类型变量更像是一种特殊情况,不如新的设计更好地统一作用域和非作用域类型变量的处理,并且没有渴望回到旧的实现。但是,新的,更简单的实现方式具有不必要的局限性,即对模式签名的限制,应该将其视为允许非刚性类型变量的特殊情况。
答案 1 :(得分:0)
我正在添加此答案(针对我自己的问题),以扩展评论中duplode的参考。 ScopedTypeVariables当前正在更改,以允许作用域类型变量代表类型,而不仅仅是类型变量。对此的讨论改变了新旧设计的动机。但是,这不是,它解决了问题和K. A. Buhr的答案中提到的更早的设计。
在当前状态下,即将进行的更改之前,定义
prefix :: a -> [[a]] -> [[a]]
prefix (x :: b) yss = map xcons yss
where xcons ys = x : ys
是有效的(具有ScopedTypeVariables),其中b
是新引入的类型变量,与a
代表相同的含义。另一方面,如果prefix
专用于
prefix :: Int -> [[Int]] -> [[Int]]
prefix (x :: b) yss = map xcons yss
where xcons ys = x : ys
然后拒绝该程序:b
不是变量类型,因此禁止Int
代表Int
。西蒙·佩顿·琼斯(Simon Peyton Jones)谈到了其设计目的,使得b
不能代表Int
:
当时我担心拥有一种类型会造成混淆 只是Int别名的变量;那不是类型变量 完全没有但是在如今的GADT和类型相等中,我们都被使用了 对此。今天我们会做出不同的选择。
根据GHC维护者的当前共识,对b
代表Int
的限制被认为是不自然的,特别是考虑到类型相等性(a ~ Int) => ...
的可能性。这种约束的存在模糊了“绑定到类型变量”的真正含义。例子应该
f1 :: (a ~ Int) => Maybe a -> Int
f1 (Just (x :: b)) = ...
f2 :: (a ~ Int) => Maybe Int -> Int
f2 (Just (x :: a)) = ...
被允许吗?在新提案下,允许上述所有四个示例。
在我看来,这种张力最终来自两个完全不同的类型注释系统的共存。其中之一具有 preventing (防止)相同类型的名称的作用(例如,您不能写(\x -> x) :: a -> b
或(\x -> x) :: Int -> b
并期望b
与a
或Int
统一)。另一个启用并鼓励您为事物赋予新名称(模式类型签名,例如foo (x :: b) = ...
),该功能可以使您命名原本无法命名的类型。剩下的问题是模式类型签名是否应允许您别名化已命名的类型。答案的核心取决于您发现两个先例中哪个更引人注目。
参考文献: