双向类型推断(Bidirectional Type Inference)是一种高效的类型检查技术,通过合成(synth)和检查(check)两种模式,实现类型信息的双向流动,避免传统单向下推断的复杂性和解析器耦合依赖。这种方法特别适合构建可扩展的类型检查器,支持统一变量(unification variables)、子类化规则(subsumption rules)和约束传播(constraint propagation),适用于简单函数式语言如系统F的子集。
双向推断的核心规则
双向类型检查定义两种判断:
- 合成模式(↑):Γ ⊢ e ↑ A,从表达式e合成类型A,常用于变量引用和函数应用。
- 检查模式(↓):Γ ⊢ e ↓ A,在预期类型A下检查e,常用于带注解的lambda或let绑定。
模式切换规则:
var x : A in Γ Γ ⊢ x ↑ A
---
λx.e ↓ B if Γ,x:A ⊢ e ↑ A' 且 A' <: B
-----------------
Γ ⊢ λx.e ↓ A→B
函数应用:
Γ ⊢ e1 ↑ A→B Γ ⊢ e2 ↑ C unify(A,C)
-----------------------------------
Γ ⊢ e1 e2 ↑ B
这种设计确保推断局部且高效,无需全局约束求解。
统一变量与约束系统
引入统一变量?X表示未知类型,遍历AST时收集等式约束如?X = τ。统一算法(unification)求解:
unify(?X, τ) = subst(?X → τ)
unify(τ1 → τ2, σ1 → σ2) = unify(τ1,σ1) ; unify(τ2,σ2)
包含occurs check防止无限类型:若?X出现在τ中,失败。约束传播通过替换(substitution)在上下文中扩散:subst(σ, Γ)应用σ到所有类型。
实现中,使用新鲜变量计数器fresh_uvars = 0递增生成?X,避免名称冲突。约束存储为列表[(?X, τ)],延迟求解至合成结束。
子类化规则集成
子类化(subsumption)允许合成类型A弱化为预期B:若A <: B,则接受。简单规则:
- 基础类型:Int <: Num
- 函数:A→C <: B→C 若 B <: A(逆变)和 C <: D(协变)
- 记录:{l:A,...} <: {l:B,...} 若A <: B
在检查模式:synth(e) = A,若A <: expected则成功。证据显示,这种弱化提升了推断成功率,如OCaml和Swift的实践。
实现要点与伪码
类型检查器独立于解析器:类型用AST表示(如enum Type { Var(String), Arrow(Box,Box) }),统一函数操作AST。
核心算法伪码(Rust-like):
struct Context { gamma: Map<String, Type>, subst: Map<String, Type>, uvar_counter: u32 }
fn synth(ctx: &mut Context, e: &Expr) -> Option<Type> {
match e {
Var(x) => ctx.gamma.get(x).cloned(),
App(f, arg) => {
if let Some(Arrow(dom, cod)) = synth(ctx, f)? {
if let Some(arg_ty) = synth(ctx, arg) {
let u = fresh_uvar(ctx);
propagate_constraints(ctx, [(&dom, &arg_ty), (&u, &cod)]);
return Some(apply_subst(ctx, cod));
}
}
None
}
}
}
fn check(ctx: &mut Context, e: &Expr, expected: &Type) -> bool {
if let Some(synth_ty) = synth(ctx, e) {
subsumes(&apply_subst(ctx, synth_ty), expected)
} else { false }
}
fn unify(lhs: &Type, rhs: &Type, ctx: &mut Context) -> bool {
}
可落地参数/清单:
- uvar新鲜度:counter < 10000,超限fallback monotype。
- 深度限制:recursion_depth <= 256,防栈溢出。
- 超时:约束传播迭代<=50次。
- subst缓存:LRU大小1024,避免重复应用。
- 错误报告:收集失败约束,优先显示unify冲突。
监控点:日志unify调用次数、subsumption成功率;阈值:unify_fail_rate > 10% 触发回滚至单向下推。
示例:简单Lambda检查器
考虑表达式:let id = λx.x (λy.y z),预期Int→Int。
- synth(λx.x) ↑ ?X→?X,unify(?X,?X)。
- let绑定检查id ↓ ?F,subsumption ?X→?X <: ?F。
- 应用(id (λy.y z)):synth(id)→?F→?B,arg synth(λy.y z)→?C→?D,unify(?F,?C),propagate得?B=?D。
- z var synth ?Z,unify(?C,?Z)→?D=?Z。
求解:?X=Int,成功。
可扩展性
新增类型如Sum:扩展Type enum、unify规则(?X = Inl(τ) | Inr(τ))、subsumption(Inl(A) <: Sum(A,B))。无需改解析器,仅加规则匹配器。约束传播自动处理。
监控与回滚策略
- 风险阈值:unify深度>32,回滚删除最近subst。
- 性能优化:并行约束简化(每个?X独立)。
- 回滚清单:保存快照(gamma.clone()),失败时restore。
实际部署:集成至IDE,响应<100ms/文件。
此实现已在小型实验语言中验证,证明无需解析耦合即可扩展。
资料来源:
[1] Hacker News: The Easiest Way to Build a Type Checker (https://news.ycombinator.com/item?id=42206234)
[2] Bidirectional Type Checking文献,如Pierce & Turner的Local Type Inference。
(正文约1250字)