.
类型:语法导向、反演与唯一性
. 类型:语法导向、反演与唯一性
定义算术语言的类型判断与良类型词项,利用语法导向性质证明类型反演与类型唯一性.
1 类型判断
类型系统是在词项的抽象语法上定义的一组规则; 它说明哪些词项与类型之间的关系可以成立. 先区分三个概念:类型关系是词项与类型之间的关系, 类型判断是形如 的陈述, 类型规则则规定怎样证明这样的判断.
这个定义首先强调“存在某个 ”:良类型只要求有一个类型,不要求事先指定是哪一个; 满足 ,因此是良类型的(它不具有 并不影响这一点). 这里固定的是项,寻找的是类型; 类型居留性反过来固定类型,问是否存在具有该类型的项(三种问题的区分与常见误解见 类型检查、类型推理与类型居留性 ).
同时,它要求“有一棵推导”:和 一样, 成立当且仅当能用 类型与类型判断 中七条规则的实例搭出一棵以它为根的有限树. 说词项良类型,就是说能写出这样一棵树; 说它不良类型,就是说无论怎样搭都搭不出来. 后者是关于所有可能推导的否定命题, 不能仅凭尝试构造某棵推导树失败就断言它不良类型; 要证明它不良类型,需要分析类型规则及其前提,排除任何合法推导成立的可能(下面的例子会演示这一点).
此外,良类型属于静态性质:在目前讨论的语言中, 类型与类型判断 中的类型判断只依赖项的结构与类型规则, 无需执行 一步规约 中的 规约. 因此不必运行词项就能判断它是否良类型,这构成了静态类型检查的基础 (与动态检查的对照见 静态类型与动态类型 ). 至于类型判断与求值这两套规则之间是否协调一致,还要靠 类型安全 的类型安全定理来保证.
规则的读法与 一步规约 相同: 横线上是前提,横线下是结论,、 是元变量( 元变量 ). 这里还要注意类型与词项是不同的语法类别: 只能代换为 或 , 则代换为词项. T-If 的两个分支可以同为 或同为 ,但同一次规则应用中的 必须取同一个类型.
类型规则同样是归纳定义,因此也可以对类型推导使用规则归纳法, 原理与 规则归纳法 相同. 这种横线上写前提、横线下写结论的记号来自形式逻辑,与自然演绎相近, 直接刻画判断的可推导性(至于检查器按什么顺序执行,还需另行规定).
2 反演:从结论倒推前提
先看这组规则的结论. 每条规则的结论都形如“某个项 : 某个类型”, 结论左边的项有一个最外层构造子(、、 等),这就是它的形状. 把七条规则按结论左边的形状列出来:
| 项的形状 | 结论里出现这个形状的规则 | 条数 |
| T-True | 1 | |
| T-False | 1 | |
| T-Zero | 1 | |
| T-If | 1 | |
| T-Succ | 1 | |
| T-Pred | 1 | |
| T-IsZero | 1 |
这张表的第三列全是 :本文的每种项形状恰好对应一条候选定型规则. 这组规则是语法导向的: 可以依据项的语法形状确定应尝试哪条规则,不必猜测最后一步. 一般来说,语法导向不要求每个项都有规则可选,更不保证规则的前提都成立; 没有匹配规则或前提搭不上,都可能导致拒绝. Harper 在 Type Systems for Programming Languages §1.2 中的表述是“至多一条规则”,不要误解为“必须恰好只有一条”.
作为对照,如果再加一条规则 (“自然数可以当布尔值用”), 它的结论左边是任意项 ,不按项的形状限制. 仅看 的语法形状,既不能排除 T-Zero,也不能排除这条转换规则, 最后一步不再由形状唯一决定. 具体要推出 时,最后用的是转换规则,它的前提 再由 T-Zero 给出.
语法导向带来很方便的推理方式: 知道了 成立,就能断定推导的最后一步用的是哪条规则, 于是那条规则的前提也都成立. 例如,知道 成立,查表可知最后一步必然是 T-Succ, 于是立刻得到 且 ,不用知道推导的其余细节. 良类型与不良类型 里的两个例子也是这样做的: 每一步都只有一条规则可选.
反演利用规则定义的最小关系,穷尽结论可能来自的规则,把“某个判断存在推导”拆成任何合法推导都必须满足的前提信息 (这与直接把单条推导规则的蕴涵方向倒过来不同). 保型证明例如从 pred (succ nv) : T 开始, 必须先知道 、 succ nv : Nat,再继续取出 nv : Nat, 才能证明删去外层构造以后类型还在. 没有反演,就不能凭外观擅自断言子项有哪些类型; 增加子类型规则时这个推理尤其需要重证,下面的附注会给出原因.
语法导向并不单凭“每种形状只有一条规则”就让输出类型自动唯一: T-If 的 还要从分支中确定. 下面的定理补上这个保证,说明
type_of
type_of
用单个类型就能表示规则给出的结果,无需返回类型集合. 没有唯一性,单个返回值可能只挑出了众多类型之一. 例如带子类型的 Kotlin 中,
Int
Int
表达式也可以出现在需要
Number
Number
或
Any
Any
的位置; 那样的系统通常要另外区分推断出的类型和它能充当的所有类型,不能照搬本文的结论.
3 练习:类型
Exercises (4)
Exercise 4.1. 从结论搭起一棵类型推导
为 写出完整的类型推导树,并指出推导中有没有哪一步需要先运行这个项. 使用 类型与类型判断 .Hint
最外层用 T-If;条件从 T-IsZero 开始,两个分支都应得到 .Solution
每个叶子都是 T-Zero,两个分支具有同一个类型 . 定型过程中没有执行任何 规约,搭这棵推导完全不依赖条件最终求值成什么.
Exercise 4.2. 严格证明项无类型
证明下面三项对任何 都不能得到 : 、 、. 每项给出无法满足的具体前提,并判断其中是否有实际能规约到值的项.Solution
若 有类型, 最后只能用 T-Succ,要求内部 为 ; T-If 随即要求 ,与 T-False 的结论不符. 若 有类型, T-If 要求条件为 ,但 的类型只能是 . 若 有类型, 外层 T-IsZero 要求内层为 ,但内层 T-IsZero 的结论只能是 .
这三种分析排除了所有可能的最后规则,因此是不存在推导的严格证明. 第一个项却可以规约到 :实际没有执行坏分支. 这再次说明“不良类型”并不等于“这次运行必定出错”.
Exercise 4.3. 反演不需要看见整棵推导
设 、、 是任意项. 已知 , 但没有拿到它的推导树. 说明此时仍能确定的 、、、 的类型信息, 并逐次标明使用 反演 的哪一条.Hint
先反演 T-If,再连续反演 T-Pred 和 T-Succ; 整项的 由其中一个分支确定.Solution
T-If 给出 、、. T-Pred 给出 以及 ; T-Succ 再给出 .代回得到 .
因而一定有 、、、. 这只提取已有推导必须满足的前提, 并没有假定 或 已经求值,也不需要猜原推导的其他细节.