Hindley-Milner(HM)类型推导是 ML 系语言(OCaml、Haskell、Rust)类型系统的理论基础。它能在无需类型注解的情况下自动推导出程序中最通用的类型。本文从零实现一个完整的 HM 类型推导器。
一、类型表示
HM 类型系统包含类型变量、基本类型和函数类型。使用 Union-Find 实现类型变量的合一。
type Type struct {
Kind TypeKind
TVar *TypeVar
Args []*Type // 函数参数类型
Ret *Type // 函数返回类型
}
type TypeVar struct {
ID int
Instance *Type // 被实例化为的具体类型
}
type TypeKind int
const (
TyVar TypeKind = iota // 类型变量
TyInt // int
TyBool // bool
TyString // string
TyArrow // T1 -> T2
)
二、合一算法
合一是 HM 算法的核心,用于判断两个类型是否兼容,并在兼容时建立类型变量的绑定关系。
func Unify(t1, t2 *Type) error {
t1 = Prune(t1)
t2 = Prune(t2)
if t1.Kind == TyVar && t1.TVar != t2.TVar {
if OccursIn(t1.TVar, t2) {
return fmt.Errorf("infinite type: %v occurs in %v", t1, t2)
}
t1.TVar.Instance = t2
return nil
}
if t2.Kind == TyVar {
return Unify(t2, t1)
}
if t1.Kind == TyArrow && t2.Kind == TyArrow {
if err := Unify(t1.Args[0], t2.Args[0]); err != nil {
return err
}
return Unify(t1.Ret, t2.Ret)
}
if t1.Kind == t2.Kind {
return nil
}
return fmt.Errorf("type mismatch: %v vs %v", t1, t2)
}
func Prune(t *Type) *Type {
if t.Kind == TyVar && t.TVar.Instance != nil {
t.TVar.Instance = Prune(t.TVar.Instance)
return t.TVar.Instance
}
return t
}
三、类型推导规则
HM 算法使用类型环境 Γ 记录变量到类型的映射,通过递归遍历 AST 推导每个表达式的类型。
type TypeEnv struct {
parent *TypeEnv
bindings map[string]*Type
}
func Infer(env *TypeEnv, expr Expr) (*Type, error) {
switch e := expr.(type) {
case *IntExpr:
return NewType(TyInt), nil
case *VarExpr:
if t, ok := env.Lookup(e.Name); ok {
return t, nil
}
return nil, fmt.Errorf("unbound variable: %s", e.Name)
case *LambdaExpr:
argType := NewTypeVar()
newEnv := env.Extend(e.Arg, argType)
retType, err := Infer(newEnv, e.Body)
if err != nil {
return nil, err
}
return NewArrowType(argType, retType), nil
case *AppExpr:
fnType, err := Infer(env, e.Fn)
if err != nil {
return nil, err
}
argType, err := Infer(env, e.Arg)
if err != nil {
return nil, err
}
retType := NewTypeVar()
if err := Unify(fnType, NewArrowType(argType, retType)); err != nil {
return nil, err
}
return retType, nil
case *LetExpr:
valType, err := Infer(env, e.Value)
if err != nil {
return nil, err
}
// 泛化:将自由类型变量提升为多态类型
scheme := Generalize(env, valType)
newEnv := env.Extend(e.Name, scheme)
return Infer(newEnv, e.Body)
}
return nil, fmt.Errorf("unknown expression")
}
四、Let 多态
HM 算法的精髓在于 Let 多态:let 绑定的变量可以被泛化为多态类型,每次使用时实例化为新的类型变量。这使得 let id = λx.x in (id 1, id true) 可以通过类型检查。
func Generalize(env *TypeEnv, t *Type) *Scheme {
freeVars := t.FreeTypeVars()
envVars := env.FreeTypeVars()
quantified := setDifference(freeVars, envVars)
return &Scheme{Vars: quantified, Type: t}
}
func Instantiate(scheme *Scheme) *Type {
subst := make(map[int]*Type)
for _, tv := range scheme.Vars {
subst[tv.ID] = NewTypeVar()
}
return ApplySubst(subst, scheme.Type)
}
五、实际应用与局限
HM 算法在 OCaml、Haskell 和 Rust 的类型推导中发挥核心作用。但它也有局限:不支持子类型、不支持高阶类型(需要 System F),且错误信息往往难以理解。现代语言在 HM 基础上做了大量扩展。
← 返回文章列表