形式语义04 Types
Published:
\newcommand\norm[1]{\left\lVert#1\right\rVert} \newcommand\abs[1]{\left\lvert#1\right\rvert}
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里,类型仅由基本类型以及通过函数对基本类型进行复合两种方法得到。很显然类型的数量是可数的
以及若干推导规则:
$\Gamma,x:\tau_1\vdash e:\tau_2\Rightarrow \Gamma,x_1:\tau_1\vdash \lambda x.e:\tau_1\rightarrow\tau_2$,意思是函数的类型由参数类型和返回值类型确定。
$\Gamma,x:\tau\vdash x:\tau $,意思是在相同环境下,同名变量有相同类型
$\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$ 的结构:
常量,此时 $e$ 是value,符合;
$e=x$,此时环境为空,不可能有 $\vdash x:\tau$
$e=\lambda x.E$,此时 $e$ 是value,符合;
- $e=e_1\;e_2$,分类讨论 $e_1,e_2$
- $e_1$ 不是value,那么由归纳假设,存在 $e_1’$ 使得 $e_1\rightarrow e_1’$,符合;
- $e_1$ 是value,$e_2$ 不是value,那么同理存在 $e_2\rightarrow e_2’$,符合;
- $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$ 的结构分类讨论:
$e$ 是常量,那么不存在 $e’$;
$e=x$,那么不存在 $e’$
$e=\lambda x.E$,那么不存在 $e’$
- $e=e_1\;e_2$,那么就有 $\vdash e_1\;e_2:\tau$,并且 $\vdash e_1:\sigma\rightarrow \tau,e_2:\sigma$
- 存在 $e_1\rightarrow e_1’$,那么就有 $e\rightarrow e_1’\;e_2$。根据归纳假设有 $\vdash e_1’:\sigma\rightarrow\tau$,又根据类型推导规则有 $\vdash e_1’\;e_2:\tau$,归纳步骤成立;
- 存在 $e_2\rightarrow e_2’$,那么就有 $e\rightarrow e_1\;e_2’$。根据归纳假设有 $\vdash e_2’:\sigma$,再根据类型推导规则有 $\vdash e_1\;e_2’:\tau$
- $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$
于是就证完了
