GHC发生检查阻止您构建无限类型。它的目的是防止代码中的常见错误或防止类型检查程序无限循环,或两者兼而有之?
它识别的是什么情况,恶意用户是否有可能欺骗它(如在安全Haskell上下文中)进入循环?如果类型系统是图灵完成的(是吗?)我不明白GHC如何保证计算停止。
答案 0 :(得分:10)
将类型推断视为求解方程组。我们来看看吧 例如:
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]]]
等等到我们的系统中(或者,作为丹尼尔在他的回答中指出,我们可以在我们的类型系统中允许无限类型,但这会使错误的程序如f x = 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的类型系统不是Turing-complete而且typechecker保证停止 - 除非你启用UndecidableInstances
,在这种情况下它理论上可以进入无限循环。但是,GHC通过具有固定深度的递归堆栈确保终止,因此不可能构建一个永远不会停止的程序(编辑:根据CAMcCann的评论毕竟,有可能在类型级别上有一个尾递归模拟,允许循环而不增加堆栈深度。
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
安全Haskell不会保护您免受此影响 - 如果您希望允许潜在恶意用户在您的系统上编译Haskell程序,请查看mueval。
答案 1 :(得分:8)
GHC发生检查阻止你构建无限类型。
只有在防止语法无限的类型的字面意义上才是这样。这里真正发生的只是一个递归类型,其中统一算法在某种意义上需要内联递归。
通过使递归显式化,始终可以定义完全相同的类型。这甚至可以使用fix
的类型级版本来完成:
newtype Fix f = Fix (f (Fix f))
例如,类型Fix ((->) a)
相当于将b
与(a -> b)
统一。
然而,在实践中,“无限类型”错误几乎总是表示代码中的错误(因此如果它已经破坏,您可能不应该Fix
它。通常情况是混合参数顺序或在不使用显式类型签名的代码中将表达式放在错误的位置。
以正确的方式非常幼稚的类型推理系统可能会扩展递归直到内存不足,但停止问题不会进入它 - 如果类型需要与其自身的一部分统一,永远不会起作用(至少在Haskell中,可能会有一些语言将其视为等同于上面明确的递归newtype
)。
GHC中的类型检查和类型推断都不是Turing-complete,除非你启用UndecidableInstances
,在这种情况下你可以通过功能依赖或类型族进行任意计算。
安全Haskell根本没有真正进入画面。很容易生成非常大的推断类型,尽管有限,但会耗尽系统内存,如果内存为我服务,那么Safe Haskell并不会限制UndecidableInstances
的使用。
答案 2 :(得分:7)
我的书签中有以下精彩邮件:There's nothing wrong with infinite types!即使有无限类型,也没有使类型检查器循环的真正危险。类型系统不是图灵完整的(除非你明确要求它与UndecidableInstances
扩展名一样)。
答案 3 :(得分:4)
目的(在Haskell编译器中)是为了防止代码中的常见错误。可以构造一个类型检查器和推理引擎,它将支持无限类型的递归。 this question中还有一些更多信息。
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类型推断是decidable,因此不可能欺骗编译器进行循环(当然,除了错误,我怀疑this example漏洞)。 GHC默认使用系统F的受限子集,其类型推断也是可判定的。对于某些扩展,例如RankNTypes
,GHC确实允许代码进行哪种类型推断是不可判定的,但是后来需要类型注释,所以再次没有类型推断阶段循环的危险。
由于图灵完整语言不可判定,因此默认类型系统无法完成图灵。我不知道GHC的类型系统是否变得图灵完备并且启用了一些扩展组合,但是某些扩展(例如UndecidableInstances
)允许编写会使编译器崩溃的代码。
顺便提一下,禁用发生检查的主要问题是许多常见的编码错误导致无限类型错误,因此禁用它通常会导致比解决的问题更多的问题。如果你打算使用无限类型,newtype包装器将允许它没有多余的表示法。