@darwin-yuan
2016-07-03T09:19:06.000000Z
字数 8157
阅读 3906
类型系统
Haskell
在关于类型系统的大量文献里,都采用了一种来自于自然推演(Natural Deduction
)的符号系统来定义类型系统。HM
类型系统的规则定义也不例外。为了能够更好的理解后续的内容,我们先来简单介绍一下这套符号系统。
首先,所有的规则都是基于这样的模式:
在右边的NAME
是这条规则的名字,可有可无。在线的上方,描述的是前提(premise
),前提可以由多个判断(Judgement
)组成;在线的下面,给出的是结论(conclusion
),结论只能包含一个判断。
因而,这种定义可以理解为:
现在我们来看第一个规则:
这条规则的名字叫做VAR
,表示这是一条变量规则。其中 代表:的类型为; 代表已知的事实,在这里可以理解为全局环境。而符号 的意思是:可以推导出。
因而,这条规则的意思是:如果一个类型为 的变量 存在于环境 中;我们则可以从环境 得知 的类型是 。
注意这个规则里出现的类型都是,这就意味着存在于环境里的变量,即可能是个monotype
,也可能是个polytype
。
下面我们来看第二条规则:
ABS
代表的意思是Abstraction
,即Lambda
抽象。这里需要介绍一种新语义:在里,由已知事实 ,加上一个新假设的事实 ,可以推导出 。如果回到环境的角度,则 代表全局环境,而 则代表本地环境,它们合在一起,构成一个新环境,从它可以推导出。
所以,第二条规则的意思是:假如允许我们引入一个新的、类型为 的变量 ,从而就可以推导出 的类型为 ,那我们就可以知道:从现有环境 可以推导出 的类型为 。
第三条规则用来描述函数调用(Function Application
),定义如下:
意思很简单:如果由现有环境可以推出的类型为, 的类型为 ,那么我们就能推出:的类型为。
规则二和规则三,是对偶关系,分别定义函数和函数调用相关的类型规则。与这两条规则有关的类型全是,即monotype
。这就意味着,在HM
类型系统里,lambda
的参数和结果,都只能是monotype
。
对于形如 的类型,如果我们将其看作一个 函数,就会知道,在一次实例化的过程中,其参数 都只能各自被实例化为一种类型。比如:
而对于 这样类型的函数,我们在一个上下文里对 进行实例化后,得到的类型里 依然还是个多态类型。
这就像演算里的高阶函数一样,这种类型被称为类型系统里的高阶类型(higher rank type
)。
因而,我们可以在同一个上下文里,对这个高阶类型进行多次不同的实例化。比如如下代码,在对高阶类型支持的类型系统下是可以通过检查和编译的。
g :: forall a b. (forall c. c -> c) -> (a, b) -> (a, b)
g f (x,y) = (f x, f y)
但正如我们之前所提到的,在HM
类型系统里,所有的 都必须出现在最前面,这也就意味着不允许存在高阶类型。因而函数 的类型在HM
类型系统下非法。
我们重新回到类型规则,来看看第四条:
这条规则被称做Let Binding
,其意思是:如果由现有环境可以推论出的类型是;同时,如果引入一个类型同样为的临时变量,则可以由现有环境推论出的类型为一个单态类型;那么,我们就可以推论出:表达式的类型为单态类型。
从语义上,这条规则似乎没有什么难以理解的。并且,它貌似可以通过ABS
和APP
两条规则组合而来。毕竟,在算子理论里,等价于。
但是,这条规则正是HM
类型系统最为特别的地方:由于定义本身不允许高阶类型,因而如下代码完全无法通过类型系统的检查:
(\x -> x x) (\x -> x)
但是,在不引入高阶类型的前提下,Let Binding
却可以让上面等价语义的代码通过类型检查,如下:
let f = \x -> x in f f
在这个表达式里,的类型为,正如LET
规则所描述的,这是个所允许的多态类型。在表达式里,的两次出现,其类型都是多态类型,因而可以各自实例化:
因而,在HM
类型系统里,尽管let binding
和定义依然具备等价的替换(substitution
)语义,但是在类型的宽容度上,两者已经不再等价。所以,在HM
类型系统下,let binding
不仅仅是一个的语法糖,它提供了更强大的能力。而这种能力被称做LET
绑定时多态(let polymorphism
)。
在HM
类型系统下,所有的的类型都是monotype
。因而当看到如下定义时,我们必须清楚:等号右边的表达式的类型是,即没有前面的,因而它是monotype
。
你一定会质疑:明明类型标注上的写的清清楚楚!
没错,但那不是等号右边表达式的类型,而是等号左边的变量id
的类型。在HM
类型系统下,将monotype
转为polytype
的唯一方式就是通过LET绑定时多态,即通过等号进行变量绑定时。haskell
的顶层,不过是一个隐形的大let
语句。
id :: forall a. a -> a
id = \x -> x
事实上,等号右边的完全没有任何理由属于polytype
。因为,对于一个定义,如果不将其绑定到任何变量,那么它将只能使用一次,只有变量才让它具备多次被使用的可能性。只需要使用一次,意味着它只可能有一种类型,并不存在多次实例化的需要。
为了更清晰的理解这一点,我们来假设HM
类型系统并不支持LET绑定时多态
,这种情况下,我们用下面的表达式,与注释里在支持LET绑定时多态
情况下的let
表达式等价:
(\x -> x) (\x -> x) -- 等价于:let f = \x -> x in f f
这是因为,我们将同一个定义实现了两次。虽然它们的定义看起来一摸一样,但在类型系统的眼里,它们是两个独立的,因而会各自推演它们的类型。并且也确实各自推演出了不同的类型。
因而,站在的外部来看,的类型没有任何理由属于polytype
。
但是,站在的内部,当它使用一个函数类型的参数时(当然,参数是一个名字),是否支持其参数绑定时多态,将会产生重大的不同。
比如,如下代码里,参数被实例化为不同的类型。而这只有在允许参数绑定时多态的情况下,那行表达式才能通过类型系统的检查。而一旦支持了参数绑定时多态,那么函数的实现里,虽然传入的是一个(我们之前刚刚讨论过,的类型必然是monotype),但其与函数 的参数 进行绑定的过程中,类型会自动升级为polytype
。
f :: (forall a. a -> a) -> (Bool, Int)
f = \g -> (g True, g Int)
f' = f (\x -> x)
而参数绑定时多态,被称做higher rank type
。为了支持这种能力,需要程序员给出类型注解。这就违背了HM
类型系统设计的初衷。因为HM
类型系统的本意,正是在完全不需要程序员给出类型注解的前提下,可以完全推断出所有表达式的类型。
第五条规则被称做泛化(Generalization
),定义如下:
为了进一步理解polytype
里的类型变量,让我们再次回到的世界。
在表达式里,变量一共分为两类:
Free Variable
)Bound Variable
)比如,表达式的子表达式里,就是有界变量,因为有个同名参数,因而它被约束在的范围内;而由于没有任何参数可以绑定,被称作自由变量。
一个表达式里,只有自由变量可以被替换。比如,当我们试图对表达式执行替换操作,我们得到的结果是,只有自由变量 发生了替换,而有界变量 未受影响。
而在同一个表达式里,同一个名字的变量,即可能是自由的,也可能是有界的。这是因为它们虽然都是同一个名字,但事实上代表着不同的变量。比如:里,第一个是一个自由变量,第二个是参数,第三个则是一个有界变量,和也都是自由变量。
如果我们对这个表达式执行替换操作 ,得到的结果是:。
现在回到类型的世界。
既然是类型系统里的,同样的道理,对于这样的类型,是一个有界类型变量,而则属于自由类型变量。
如果对整个类型执行替换操作,得到的结果是。
而对于表达式,和都是自由类型变量。如果对整个类型执行替换操作,得到结果是。
需要强调的是:从的定义可以看出,在HM
类型系统里,如果一个类型属于polytype
,其所有的都只能出现在最前面。
也就是说,我们只能定义形如这样的polytype
,而不可能定义这样的polytype
。
函数则是用来求所拥有的所有自由类型变量,其结果是一个集合。
比如:;而。
环境里,存储的是所有已知的的集合。因而,
代表现有环境中所有的自由类型变量的集合。
它的意思是:如果由现有环境可以推导出表达式的类型为,对于里的任意自由变量,如果不存在于 ,那么它就可以成为 的 参数。
比如,我们基于现有的环境推导出的类型为,这个类型包含三个自由类型变量: 。其中,如果是环境中已经存在的自由类型变量,但不是,那么上述类型就可以泛化为:。
所以,为了更清晰的表述,这条规则可以修正为:
最后一条规则称做实例化(Instantiation
):
这条规则定义里有一个重要的新符号,它表示左边比右边的类型更通用,更泛化,更多态。当然,也可以反过来说,右边比左边的更特殊,更具体。比如:
在其它文献中,也有使用符号或的。它们都和一样,代表左边比右边更通用。比如:
但对我个人而言,符号的开口朝向更加通用的类型,更符合我的直觉。因而,本文均采用符号。
理解这个符号之后,这条规则的意思也就呼之欲出:如果从现有环境可以推出表达式的类型为,并且比更加通用,那么我们就可以从现有环境推出:表达式的类型可以是更加具体的。
或许你已经注意到,最后两条规则:INST
和GEN
是一对互逆的规则。一个表达式的类型可以通过GEN
提升到更加通用;相反,也可以通过INST
降格到更加具体。
本文介绍了HM
类型系统的六条类型规则。在随后的文章里会讨论基于这六条规则的类型推导。