[英]What cases do the GHC occurs check identify?
GHC 发生检查可防止您构造无限类型。 它的目的是防止代码中的常见错误或防止类型检查器无限循环,还是两者兼而有之?
它识别哪些情况,恶意用户是否有可能欺骗它(如在 Safe Haskell 上下文中)进入循环? 如果类型系统是图灵完备的(是吗?)我不明白 GHC 如何保证计算将停止。
将类型推断视为求解方程组。 让我们看一个例子:
f x = (x,2)
我们如何推断f
的类型? 我们看到它是一个函数:
f :: a -> b
此外,从f
的结构中我们可以看到以下等式类似地成立:
b = (c,d)
d = Int
c = a
通过求解这个方程组,我们可以看到f
的类型是a -> (a, Int)
。 现在让我们看看以下(错误的)函数:
f x = x : x
(:)
的类型是a -> [a] -> [a]
,因此生成以下方程组(简化):
a = a
a = [a]
所以我们得到一个方程a = [a]
,从中我们可以得出结论,这个方程组没有解,因此代码不是很好的类型。 如果我们不拒绝方程a = [a]
,我们确实会进入一个无限循环添加方程a = [a]
、 a = [[a]]
、 a = [[[a]]]
等我们的系统(或者,正如 Daniel 在他的回答中指出的那样,我们可以在我们的类型系统中允许无限类型,但这会导致错误的程序,例如fx = x : x
进行类型检查)。
你也可以在 ghci 中测试:
> let f x = x : x
<interactive>:2:15:
Occurs check: cannot construct the infinite type: a0 = [a0]
In the second argument of `(:)', namely `x'
In the expression: x : x
In an equation for `f': f x = x : x
至于您的其他问题:GHC Haskell 的类型系统不是图灵完备的,并且类型检查器保证会停止 - 除非您启用UndecidableInstances
,在这种情况下,理论上它可以进入无限循环。 然而,GHC 通过具有固定深度的递归堆栈来确保终止,因此不可能构建一个永不停止的程序(编辑:根据 CAMcCann 的评论,这毕竟是可能的 - 在允许在不增加堆栈深度的情况下循环的类型级别)。
然而,有可能使编译花费任意长的时间,因为 Hindley-Milner 类型推断的复杂性在最坏的(但不是平均的!)情况下是指数级的:
dup x = (x,x)
bad = dup . dup . dup . dup . dup . dup . dup . dup
. dup . dup . dup . dup . dup . dup . dup . dup
. dup . dup . dup . dup . dup . dup . dup . dup
. dup . dup . dup . dup . dup . dup . dup . dup
Safe Haskell 不会保护您免受此影响 - 如果您想允许潜在的恶意用户在您的系统上编译 Haskell 程序,请查看mueval 。
GHC 发生检查可防止您构造无限类型。
这仅在防止语法上无限的类型的字面意义上才是正确的。 这里真正发生的只是一种递归类型,在某种意义上,统一算法需要内联递归。
通过使递归显式,总是可以定义完全相同的类型。 这甚至可以通用地完成,使用类型级别的fix
:
newtype Fix f = Fix (f (Fix f))
例如,类型Fix ((->) a)
等效于将b
与(a -> b)
统一。
然而,在实践中,“无限类型”错误几乎总是表示代码中的错误(因此如果它坏了,你可能不应该Fix
它)。 通常的情况是混淆参数顺序或在不使用显式类型签名的代码中以其他方式在错误的位置使用表达式。
一个以正确的方式极其幼稚的类型推断系统可能会扩展递归,直到它耗尽内存,但停止问题不会进入它——如果一个类型需要与自身的一部分统一,那永远不会发生工作(至少在 Haskell 中,可能有一些语言将其视为等同于上面的显式递归newtype
)。
GHC 中的类型检查和类型推断都不是图灵完备的,除非您启用UndecidableInstances
,在这种情况下,您可以通过函数依赖项或类型系列进行任意计算。
Safe Haskell 根本没有真正进入画面。 很容易生成非常大的推断类型,尽管它是有限的,但会耗尽系统内存,如果内存为我服务,Safe Haskell 无论如何都不会限制使用UndecidableInstances
。
目的(在 Haskell 编译器中)是为了防止代码中的常见错误。 可以构建一个支持类型无限递归的类型检查器和推理引擎。 在这个问题中有一些进一步的信息。
OCaml 使用-rectypes
实现递归类型,所以这绝对是可能的。 OCaml 社区将更加精通出现的一些问题(默认情况下该行为是关闭的)。
发生检查标识无限类型扩展。 例如,这段代码:
Prelude> let a = [a]
<interactive>:2:10:
Occurs check: cannot construct the infinite type: t0 = [t0]
In the expression: a
In the expression: [a]
In an equation for `a': a = [a]
如果您尝试手动计算类型,则类型为[ [ [ [ ... ] ] ] ]
。 手工编写这样的类型是不可能的,因为它们是无限的。
发生检查发生在类型推断期间,这是与类型检查不同的阶段。 必须推断无限类型,因为它们不能手动注释。 Haskell-98 类型推断是可判定的,因此不可能诱使编译器进入循环(当然,我怀疑这个示例利用了错误)。 默认情况下,GHC 使用系统 F 的受限子集,其类型推断也是可判定的。 通过一些扩展,例如RankNTypes
,GHC 确实允许类型推断不可判定的代码,但随后需要类型注释,因此再次没有类型推断阶段循环的危险。
由于图灵完备语言是不可判定的,所以默认的类型系统不能是图灵完备的。 我不知道 GHC 的类型系统是否在启用某些扩展组合的情况下成为图灵完备的,但某些扩展(例如UndecidableInstances
)允许编写代码,这些代码会使编译器因堆栈溢出而崩溃。
顺便说一句,禁用发生检查的主要问题是许多常见的编码错误会导致无限类型的错误,因此禁用它通常会导致比解决的问题更多。 如果您确实打算使用无限类型,则 newtype 包装器将允许它没有太多多余的符号。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.