形式语义04 Types
2021-10-05 10:42:00

Types

首先要说明什么是Type

Types可以看成是对数据的分类、一种约定,即我们用一个界来描述一类数据构成的集合,用不同的界区分不同的数据种类。对于untyped的语言,我们则可以看成是只有唯一一种包罗万象的type

类型实际上有很多作用,可以进行针对性的优化、可以提供部分代码的信息、可以作为接口分离各模块的逻辑、可以保证程序的正确执行.....

如果写过Coq的话,还会知道类型可以与逻辑系统中的元素建立对应关系,从而可以利用类型推导工具来进行定理的证明(虽然我感觉这是绕了一个大弯,毕竟类型系统本来就应该看成一类特殊的逻辑系统),也就是Curry-Howard morphism

这一节讲的就是在\(\lambda\)-calculus中引入type

Errors

分为trapped errors和untrapped errors

trapped意思是就是出现使得程序停止的错误(除0)

untrapped意思是程序虽然继续运行,但是状态被破坏了(回想注入攻击)

forbidden-error则指的是一个untrapped errors的超集

Safe & Well-behaved

我们称一个程序是safe的,当且仅当它执行过程中不会出现untrapped error。如果一个语言的所有合法程序都是safe的,那么我们就称这个语言是safe的

我们称一个程序是well-behave的,当且仅当它在执行过程中不会出现forbidden-errors。

于是就可以定义type safe了。如果一个语言经过了type system检查的程序都是safe的,那么我们称这个语言就是type safe的

实践中通常不明确定义forbidden-errors、well-behave以及safe,一般混着来,不过不影响理解

Annotations

把变量 \(x\) 的类型为 \(\tau\) 记作 \(x:\tau\)

把"以类型 \(\tau_1\) 作为输入、\(\tau_2\) 作为输出"的函数 \(f\) 的类型记作 \(f:\tau_1\rightarrow \tau_2\)

有些变量的类型由环境决定,例如表达式 \(f\;1\),在 \(f:\text{int}\rightarrow\text{int}\)\(f:\text{int}\rightarrow\text{double}\) 两种情况下的类型就不一样,因此需要引入环境的概念,用函数 \(\Gamma:\text{Types}^\text{var}\) 表示,意思是给free variable分配类型,也可以理解成假设

在Simply Typed \(\lambda\)-calculus里,类型仅由基本类型以及通过函数对基本类型进行复合两种方法得到。很显然类型的数量是可数的

以及若干推导规则:

  1. \(\Gamma,x:\tau_1\vdash e:\tau_2\Rightarrow \Gamma,x_1:\tau_1\vdash \lambda x.e:\tau_1\rightarrow\tau_2\),意思是函数的类型由参数类型和返回值类型确定。

  2. $,x:x:$,意思是在相同环境下,同名变量有相同类型

  3. \(\Gamma,e_1:\tau_1\rightarrow\tau_2,e_2:\tau_1\vdash e_1\;e_2:\tau_2\),意思是给一个函数传入参数就可以得到返回值的类型

可以发现,一个类型系统类似一个逻辑系统,我们有若干公理(变量类型)、推导规则,并且我们希望所有的命题都可以通过推理得到,也都有相应的语义含义

Soundness & Completeness

也有叫Consistency的

如果一段代码能给所有表达式标上类型:即每个表达式的类型都可以在类型系统中推导得到,那么就称类型系统accept了这段代码

我们称类型系统sound,当且仅当所有通过的代码都不会出错

我们称类型系统complete,当且仅当所有不会出错的代码都能通过检查

很快就能反应过来对于递归可枚举的图灵完备的语言而言,这个问题是不可判定的。因此通常的做法是牺牲completeness追求soundness,意思是也许你的做法是对的,但我们建议用其它可以通过检查的方法来写;并且所有通过的代码都必须是安全的

在Simply Typed \(\lambda\)-calculus中,不出错就意味着:

如果 \(\vdash M:\tau\) 并且 \(M\overset *\rightarrow M'\),那么就有 \(\vdash M':\tau\)

并且要么 \(M'\) 是一个value,要么 \(M'\) 可以继续规约。这里value通常就定义为normal form

上面两条分别对应了下面的两个定理

一个证明上述type system不complete的例子如下:

\((\lambda x.(x\; (\lambda y.y))(x\;3))(\lambda z.z)\)

关键就在于对于同一个 \(x\),它既作用于 \((\lambda y.y)\),又作用于 \(3\),因此我们不能推导得到 \(x\) 的类型,因此也就无法通过类型系统的检查

但是稍微规约一下就可以得到 \((\lambda y.y)\;3=3\),最终是可以停止并得到值的,意思是这段代码不会出错

Progress TH

如果 \(\vdash e:\tau\),那么要么 \(e\) 是value,要么存在 \(e\rightarrow e'\)

证明这个只需要对推导次数进行归纳就可以了

引理1:如果一个拥有 \(\tau_1\rightarrow\tau_2\) 类型的表达式 \(e\) 是value,那么它一定是 \(\lambda x.E\) 的形式

引理1的证明:观察类型推导的规则就可以发现,能够给出 \(\tau_1\rightarrow\tau_2\) 类型的规则只有一条。反证即可说明

Progress的证明:

base case 是很简单的,这里就不写了

设当推导次数 \(n=k\) 时命题成立,分类讨论第 \(k+1\) 次推导时表达式 \(e\) 的结构:

  1. 常量,此时 \(e\) 是value,符合;

  2. \(e=x\),此时环境为空,不可能有 \(\vdash x:\tau\)

  3. \(e=\lambda x.E\),此时 \(e\) 是value,符合;

  4. \(e=e_1\;e_2\),分类讨论 \(e_1,e_2\)

  5. \(e_1\) 不是value,那么由归纳假设,存在 \(e_1'\) 使得 \(e_1\rightarrow e_1'\),符合;

  6. \(e_1\) 是value,\(e_2\) 不是value,那么同理存在 \(e_2\rightarrow e_2'\),符合;

  7. \(e_1,e_2\) 都是value,根据引理有 \(e_1=\lambda x.E\),于是有 \(e_1\;e_2\rightarrow E[e_2/x]\),符合;

由数学归纳法得命题对任意自然数次的推理成立

关键在于为什么需要引理,以及何时使用引理

Preservation TH

意思是如果 \(\vdash e:\tau\)\(e\rightarrow e'\),那么有 \(\vdash e':\tau\)

引理2:若 \(\Gamma,x:\sigma\vdash E:\tau\),且 \(\Gamma\vdash e:\sigma\),那么 \(\Gamma\vdash E[e/x]:\tau\)

证明比较简单,只需要对 \(e\) 推导归纳再分类讨论就好了

仍然对推理次数进行归纳,同样省去base case

\(e\) 的结构分类讨论:

  1. \(e\) 是常量,那么不存在 \(e'\)

  2. \(e=x\),那么不存在 \(e'\)

  3. \(e=\lambda x.E\),那么不存在 \(e'\)

  4. \(e=e_1\;e_2\),那么就有 \(\vdash e_1\;e_2:\tau\),并且 \(\vdash e_1:\sigma\rightarrow \tau,e_2:\sigma\)

  5. 存在 \(e_1\rightarrow e_1'\),那么就有 \(e\rightarrow e_1'\;e_2\)。根据归纳假设有 \(\vdash e_1':\sigma\rightarrow\tau\),又根据类型推导规则有 \(\vdash e_1'\;e_2:\tau\),归纳步骤成立;

  6. 存在 \(e_2\rightarrow e_2'\),那么就有 \(e\rightarrow e_1\;e_2'\)。根据归纳假设有 \(\vdash e_2':\sigma\),再根据类型推导规则有 \(\vdash e_1\;e_2':\tau\)

  7. \(e_1,e_2\) 都是normal form,那么根据引理1有 \(e_1=\lambda x.E\) 的形式,于是 \(e=e_1\;e_2=\lambda x.E\; e_2\)。此时显然有 \(e\rightarrow E[e_2/x]\),根据引理2有 \(\vdash E[e_2/x]:\tau\)

于是就证完了