Algorithm W
Algorithm W is a syntax-directed way to construct proofs of these judgments and compute principal types. ([Wikipedia]1)
Algorithm W takes an environment and an expression and returns:
W(Γ, e) = (S, τ)meaning: under substitution S, e has monotype τ in environment SΓ.
Core machinery:
- Fresh type variables:
newvar(). - Substitutions
S: finite maps from type variables to types; applied everywhere. - Instantiation:
inst(∀α₁…αₙ. τ)replaces eachαᵢwith a fresh type variable. - Generalization:
Gen(Γ, τ), as above. - Unification:
mgu(τ₁, τ₂)gives the most general unifier substitution that makesτ₁andτ₂equal, or fails with a type error. ([Wikipedia]1)
Variable
Section titled “Variable”W(Γ, x): let σ = Γ(x) τ = inst(σ) in (Id, τ)- Look up
x’s scheme, instantiate to a monotype.
Abstraction λx. e
Section titled “Abstraction λx. e”W(Γ, λx. e): let α = newvar() (S, τ) = W(Γ[x ↦ α], e) in (S, S α -> τ)xstarts out monomorphic with typeα.- Body may refine
αviaS. - Final function type uses
S α.
Application e1 e2
Section titled “Application e1 e2”W(Γ, e1 e2): let (S1, τ1) = W(Γ, e1) (S2, τ2) = W(S1 Γ, e2) β = newvar() S3 = mgu(S2 τ1, τ2 -> β) in (S3 ∘ S2 ∘ S1, S3 β)- Infer function and argument types.
- Force
τ1to be “a function fromτ2to β”. - Unification (
mgu) emits the equality constraints and solves them.
Let-binding let x = e1 in e2
Section titled “Let-binding let x = e1 in e2”W(Γ, let x = e1 in e2): let (S1, τ1) = W(Γ, e1) σ = Gen(S1 Γ, τ1) (S2, τ2) = W(S1 Γ [x ↦ σ], e2) in (S2 ∘ S1, τ2)- Infer the binding’s type.
- Generalize to a scheme (binds the free type vars of
τ1). - Use that scheme when inferring the body.
This explicit threading of substitutions is what distinguishes W from Milner’s earlier “Algorithm J”; W is designed to be easier to reason about formally. ([Wikipedia]1)
Example: let id = λx. x in (id 3, id True)
Section titled “Example: let id = λx. x in (id 3, id True)”This showcases:
- Type variables as unknowns,
- Generalization and binding,
- Re-instantiation,
- Unification.
let structure
Section titled “let structure”Expression:
e = let id = λx. x in (id 3, id True)We start from an environment where literals have known types:
Γ0 = { 3 : Int, True : Bool }We compute W(Γ0, e) via the let rule:
(S1, τ1) = W(Γ0, λx. x)σ = Gen(S1 Γ0, τ1)(S2, τ2) = W(S1 Γ0 [id ↦ σ], (id 3, id True))- Result:
(S2 ∘ S1, τ2)
W(Γ0, λx. x)
Section titled “W(Γ0, λx. x)”Abstraction case:
-
Fresh
αforx. -
Extend
Γ0toΓ1 = Γ0[x ↦ α]. -
Infer the body
x:-
W(Γ1, x):Γ1(x) = α(monotype; think scheme∀. α).inst(α) = α.- So
(Id, α).
-
-
For the lambda:
(S = Id, τ_body = α)- Function type =
α -> α
So:
W(Γ0, λx. x) = (Id, α -> α)Thus S1 = Id, τ1 = α -> α.
generalize id
Section titled “generalize id”Generalize τ1 against S1 Γ0 (still Γ0):
free(τ1) = {α}free(Γ0) = ∅- So
Gen(Γ0, α -> α) = ∀α. α -> α
Set:
σ_id = ∀α. α -> αΓ' = Γ0[id ↦ σ_id]This is exactly where α becomes bound in the type scheme.
infer (id 3, id True)
Section titled “infer (id 3, id True)”We treat (e1, e2) as an expression whose type is τ1 × τ2:
- Compute
W(Γ', id 3)→(S_left, τ_left) - Compute
W(S_left Γ', id True)→(S_right, τ_right) - Pair type is
S_right τ_left × τ_right.
W(Γ', id 3)
Section titled “W(Γ', id 3)”Application:
-
First,
W(Γ', id):Γ'(id) = ∀α. α -> α- Instantiate:
β -> βwith freshβ. (Id, β -> β)
-
Then,
W(Γ', 3):- Literal:
(Id, Int)
- Literal:
-
Application:
-
Fresh
γfor result. -
Unify
β -> βwithInt -> γ:- Domain:
β ~ Int - Codomain:
β ~ γ
- Domain:
-
So substitution
S3 = { β ↦ Int, γ ↦ Int }.
-
The type of id 3 is Int, with substitution S_left = S3.
W(S_left Γ', id True)
Section titled “W(S_left Γ', id True)”The generalized scheme for id is still ∀α. α -> α (schemes aren’t rewritten by substitutions; they’re re-instantiated).
-
W(S_left Γ', id):- Instantiation again, but with fresh
δ:δ -> δ.
- Instantiation again, but with fresh
-
W(S_left Γ', True):- Literal:
(Id, Bool).
- Literal:
-
Application:
-
Fresh
εfor result. -
Unify
δ -> δwithBool -> ε:δ ~ Boolδ ~ ε
-
Substitution
S' = { δ ↦ Bool, ε ↦ Bool } = S_right.
-
So id True has type Bool.
Pair type
Section titled “Pair type”Combine substitutions (S_pair = S_right ∘ S_left) and types:
- Left:
Int - Right:
Bool
So the whole expression has type:
(Int, Bool)That’s the principal type inferred by Algorithm W.
Where Algorithm W says “no”: λx. x x
Section titled “Where Algorithm W says “no”: λx. x x”As a quick sanity check: λx. x x should not be typable in HM.
Sketch:
-
Give
xtypeα. -
In body
x x:xas function ⇒ typeα.xas argument ⇒ typeα.- Application asks to unify
αwithα -> β(for some β).
-
This triggers an occurs check:
αappears insideα -> β. Unifying would yield an infinite typeα = α -> β, which HM forbids.
So Algorithm W rejects λx. x x with a type error during unification.
Footnotes
Section titled “Footnotes”-
https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system “Hindley–Milner type system” ↩ ↩2 ↩3