Calculating Type Arguments [blog/lti/calc_args]
Calculating Type Arguments [blog/lti/calc_args]
通过前述的约束生成步骤,我们已经成功地将一个非构造性的最优解搜索问题, 转化为了一个具体、有形的产物:一个约束集 $C$。 这个约束集,凝聚了为了使整个多态应用类型正确,所有待定类型参数 $\overline{X}$ 必须满足的全部边界条件。 对于每一个未知变量 $X_i$,我们都得到了一个形如 $S_i \lt: X_i \lt: T_i$ 的合法区间。
至此,算法的第一阶段「我们对未知数了解了什么?」已经圆满完成。 然而,我们的工作尚未结束。一个约束区间,例如 $\mathbb{Z} \lt: X \lt: \mathbb{R}$, 本身可能允许多个合法的解(如 $\mathbb{Z}$ 或 $\mathbb{R}$)。 我们最终必须为每一个 $X_i$ 挑选出一个具体的类型值,以完成对内核语言项的最终构造。
这就引出了算法的最后一个,也是画龙点睛的一步:我们应依据何种准则,从每个变量的合法区间中做出最终的选择? 答案,必须回归到我们的初衷,即 App-InfSpec 规约中所声明的最优性要求: 我们所做的选择,必须能使整个应用表达式获得唯一的、最小的结果类型。 因此,算法的最后一步,便是要设计一个选择策略, 它能利用我们已经辛勤收集到的约束集 $C$, 并结合函数原始的返回类型 $R$, 来计算出一组能最终最小化 $R$ 的具体类型参数。 这便是本节「参数计算」的核心任务。
为了计算关于 $R$ 的具体类型参数,还有一个至关重要的操作:变量代换导致的极性变化。
一个类型变量 $X$ 在另一个类型表达式 $R$ 中的极性,描述了当该变量的类型变大或变小时,整个表达式的类型会如何相应地变化。 现在我们对极性的考虑主要集中在函数类型之上,
只需要关注变量在函数类型中的位置即可(在箭头的左边还是右边),
当然,需要考虑到嵌套的函数结构。建议读者在此稍作停顿,考虑一下该算法的设计。
当然你也可以直接展开下面的代码块来查看具体实现。 1. Polarity [blog/lti/polarity]
1.1. Variance Code [blog/lti/variance_code]
pub fn variance(self : Type, va : Var) -> Variance {
letrec go = (ty : Type, polarity : Bool) => match ty {
TyVar(v) if v == va => if polarity { Covariant } else { Contravariant }
TyVar(_) | TyTop | TyBot => Constant
TyFun(_, params, ret) => {
let param_variance = params
.map(p => go(p, !polarity))
.fold(init=Constant, combine_variance)
combine_variance(param_variance, go(ret, polarity))
}
}
and combine_variance = (v1 : Variance, v2 : Variance) => match (v1, v2) {
(Constant, v) | (v, Constant) => v
(Covariant, Covariant) | (Contravariant, Contravariant) => v1
(Covariant, Contravariant) | (Contravariant, Covariant) => Invariant
(Invariant, _) | (_, Invariant) => Invariant
}
go(self, true)
}
为了最小化返回类型 $R$,我们的选择策略变得显而易见:
- 若 $X_i$ 在 $R$ 中是协变的,我们应为 $X_i$ 选择其合法区间内的最小值,即其约束的下界。
- 若 $X_i$ 在 $R$ 中是逆变的,我们应为 $X_i$ 选择其合法区间内的最大值,即其约束的上界。
- 若 $X_i$ 在 $R$ 中是不变的,则为了保证结果的唯一性与可比较性,其合法区间必须是一个「点」,即其约束的上下界必须相等。
上述策略被形式化为一个计算最小代换(minimal substitution) $\sigma_{CR}$ 的算法。给定一个可满足的约束集 $C$ 和返回类型 $R$:
对于 $C$ 中的每一个约束 $S \lt: X_i \lt: T$:
- 若 $R$ 在 $X_i$ 上是协变或常数的,则 $\sigma_{CR}(X_i) = S$。
- 若 $R$ 在 $X_i$ 上是逆变的,则 $\sigma_{CR}(X_i) = T$。
- 若 $R$ 在 $X_i$ 上是不变的,且 $S=T$,则 $\sigma_{CR}(X_i) = S$。
- 在其他所有情况下(尤其是不变变量的约束区间 $S \neq T$ 时),$\sigma_{CR}$ 未定义。
当 $\sigma_{CR}$ 未定义时,算法宣告失败,这精确地对应了 App-InfSpec
中无法找到唯一最优解的情形。
2. Solving Constraints [blog/lti/solve_code]
pub fn solve(self : Constraints, ty : Type) -> Map[Var, Type]? {
let f = (vs : (Var, Subtype)) => {
let { l, r } = vs.1
let v = match ty.variance(vs.0) {
Constant | Covariant => Some(l)
Contravariant => Some(r)
Invariant => if l == r { Some(l) } else { None }
}
v.map(v => (vs.0, v))
}
let i = self.inner().iter().filter_map(f)
guard i.count() == self.inner().size() else { None }
Some(Map::from_iter(i))
}
至此,我们已经完整地定义了一个由「变量消去」、「约束生成」和「参数计算」三步构成的、完全可执行的算法。但我们如何确保这个具体的算法,其行为与那个非构造性的、声明式的 其等价性的证明,浓缩于一个核心命题中,该命题断言: 证明概要: 第一部分(算法的正确性):
为证明 $\sigma_{CR}$ 是最优的,我们任取另一个满足约束的合法代换 $\sigma'$,并需要证明 $\sigma_{CR}(R) \lt: \sigma'(R)$。
考虑构建一个从 $\sigma_{CR}$ 到 $\sigma'$ 的「代换链」,每一步只改变一个变量的值。例如,$\sigma_0 = \sigma_{CR}$,$\sigma_1 = \sigma_0[X_1 \mapsto \sigma'(X_1)]$,…,$\sigma_n = \sigma'$。
接着,我们证明在此路径的每一步中,结果类型都是单调不减的,即 $\sigma_{i-1}(R) \lt: \sigma_i(R)$。这一步的证明,直接依赖于前述的极性定义。例如,若 $R$ 在 $X_i$ 上是协变的,我们的算法选择了下界 $S$,而 $\sigma'(X_i)$ 必然大于等于 $S$,因此根据协变的定义,结果类型必然「变大」。同理可以证明其他情况下该论断也成立。
最终,通过传递性,我们得出 $\sigma_{CR}(R) \lt: \sigma'(R)$,证明了 $\sigma_{CR}$ 的最优性。 第二部分(算法失败的完备性):
当算法失败时,必然是因为某个不变变量 $X_i$ 的约束区间 $[S, T]$ 中 $S \neq T$。
我们采用反证法:假设此时仍然存在一个最优解 $\sigma$。由于 $S \neq T$,我们总能找到另一个合法的代换 $\sigma'$,使得 $\sigma(X_i)$ 与 $\sigma'(X_i)$ 不同。但由于 $R$ 在 $X_i$ 上是不变的,$\sigma(R)$ 与 $\sigma'(R)$ 将变得不可比较,这与「$\sigma$ 是最优解(即比所有其他解都小)」的假设相矛盾。
因此,在这种情况下,最优解必然不存在。 3. Proof Sketch [blog/lti/proof_eq]
App-InfSpec
规约完全一致?
这一核心命题证明了我们设计的这套具体算法与 App-InfSpec
规约之间的等价性。
它允许我们最终用一个完全算法化的规则 App-InfAlg
来取代那个不可执行的规约:
$$ \frac{ \begin{array}{ccc} \Gamma \vdash f : \forall \overline{X}.\overline{T} \to R & \Gamma \vdash \overline{e} : \overline{S} & |\overline{X}| > 0 \\ \emptyset \vdash_X \overline{S} \lt: \overline{T} \Rightarrow \overline{D} & C = \bigwedge \overline{D} & \sigma = \sigma_{CR} \end{array} }{ \Gamma \vdash f(\overline{e}) : \sigma R } \quad (\text{App-InfAlg}) $$