Lectures onType Theory
Chapter 4
Chapter 4Core route

Hindley–Milner Type Inference

In the simply typed calculus of chapter 2, every abstraction carries its domain. The identity function on natural numbers and the identity function on functions are therefore two different terms, λx:Nat.x:NatNat,λx:NatNat.x:(NatNat)(NatNat), and a program that needs the identity at seven types must contain seven copies of it. The defect is not the annotation itself but the granularity of the discipline: nothing in the body x depends on the domain, yet the typing rules force a choice once and for all. A replacement discipline must remove that defect while keeping typechecking mechanical. Three demands constrain it.

  1. One unannotated definition of the identity must serve every use, each use at the type that its position demands.

  2. The programmer writes no types at all; the types are computed.

  3. The computed answer must be principal: a single most general description of which every other valid description is a specialization, so that the algorithm never commits to an accidental choice.

Using the unannotated definition λx.x once at the two types displayed above is parametric polymorphism, or simply polymorphism in this chapter: one definition is reused at several types.

The let-language

Definition 3.1 — Terms of the let-language

Fix the countably infinite supply of variables x,y,z, of section 2.1. Raw terms of the let-language are given by e::=xe1e2λx.elet x=e1 in e2, where λx.e binds x in e, and let x=e1 in e2 binds x in e2 only.

Two differences from section 2.1 matter. Abstractions carry no domain: the discipline must recover it. And a new binding form let appears. For evaluation, let x=e1 in e2 behaves as the application (λx.e2)e1; for typing it will behave differently, and that difference is the entire subject of the chapter.

Convention 3.2 — Binding

Use the binding construction of section 2.1 for this grammar. Thus a term is an alpha-equivalence class, substitution e[a/x] is capture avoiding, and bound variables may be freshened away from any finite set. The new let clause repeats the abstraction clause, with its binding confined to the body e2. Concretely, fv(let x=e1 in e2)=fv(e1)(fv(e2){x}), and substitution descends into both e1 and e2, except that it stops under the body when the let binder is the variable being replaced. Before descending under either λ or let, rename its binder when necessary to avoid capturing a variable free in the substituting term. These are precisely the operations of section 2.1; their compatibility with alpha-equivalence continues to hold because the new clause has the same binding shape as abstraction.

Convention 3.3 — The arithmetic context

Programs are typed under an initial context of constants. Constants are ordinary variables that the programs themselves never bind. The arithmetic context is Γ0:=zero:Nat, succ:NatNat, true:Bool, false:Bool. The later list and reference fragments state each additional constructor and constant before using it; no additional constant is implicit in the core environment.

With let, one identity definition can be instantiated independently at Nat and at NatNat. Write Pid:=let id=λx.x in (idsucc)(idzero). One definition of the identity; two uses. The outer use applies id to succ, so there it must have type (NatNat)(NatNat); the inner use applies it to zero, so there it must have type NatNat. A single simple type cannot do both jobs. The discipline we construct assigns id a type scheme: one type together with a finite list of variables that each use may replace independently. For identity, the type is αα and the list contains α. The outer use replaces both occurrences by NatNat; the inner use replaces both by Nat.

Monotypes, type schemes, and the declarative system

Monotypes and substitutions

Definition 3.4 — Type variables and monotypes

Fix a countably infinite supply of type variables, placeholders that substitution may replace by types, enumerated once and for all as α0,α1,α2,; we write α,β,γ,δ, for successive members of this enumeration in hand calculations, restarting the display names only when a calculation explicitly begins a new run. Fix also a signature: a set of type constructors F, each with an arity. Monotypes, the types containing no scheme quantifier, are inductively defined by τ::=αF(τ1,,τn)(F of arity n). The ambient signature consists of Nat and Bool of arity 0 and of arity 2, written infix and associating to the right, so αβγ means α(βγ). As in definition 2.1, monotypes are finite trees: distinct constructors produce distinct monotypes, and each constructor is injective in its arguments. The set ftv(τ) of variables occurring in τ is defined by structural recursion.

Monotypes contain no binders. Consequently substitution for type variables is plain structural recursion, with no renaming and no capture; this simplicity is load-bearing for everything that follows.

Definition 3.5 — Substitutions

A substitution is a function S from type variables to monotypes such that the domain dom(S):={αα[S]α} is finite. We write [τ1/α1,,τk/αk] for the substitution sending each αi to τi (the αi distinct) and every other variable to itself, and id for the identity substitution. Application to a monotype is structural: F(τ1,,τn)[S]:=F(τ1[S],,τn[S]). Set ftv(S):=αdom(S)ftv(α[S]) and vars(S):=dom(S)ftv(S). Composition is written S;T and is defined by τ[S;T]:=(τ[S])[T]. It is again a substitution, and is written in diagrammatic order: first apply S, then T. This is the reverse of the usual function notation TS. For example, if S=[β/α] and T=[Nat/β], then

α[S;T]=(α[S])[T]=β[T]=Nat.

For every monotype τ, unfolding the definition twice gives τ[(S;T);R]=((τ[S])[T])[R]=τ[S;(T;R)]. The first and last expressions agree for variables by the definition of composition and for constructor applications by structural recursion. Thus ; is associative. The same calculation with id gives its two unit laws. For a set X of type variables, S=XT means γ[S]=γ[T] for every γX. A direct structural induction gives, for every τ, ftv(τ[S])=γftv(τ)ftv(γ[S]), so in particular S=ftv(τ)T implies τ[S]=τ[T].

Lemma 4.6 — Extensionality of type substitution

If γ[S]=γ[T] for every γftv(τ), then τ[S]=τ[T].

Proof of Lemma 4.6 — Extensionality of type substitution

Proof. Induct on the finite tree τ. The variable case is the hypothesis. At F(τ1,,τn), every variable of a component belongs to ftv(τ), so the induction hypotheses give τi[S]=τi[T] for every i; congruence of F gives the conclusion. ◻

Type schemes

Definition 3.6 — Type schemes

A type scheme is an expression σ::=τα.σ, that is, a monotype under a finite (possibly empty) prefix of quantifiers; we abbreviate α1.αk.τ as α¯.τ. The quantifier binds its variable in the body. Alpha-equivalence and freshening of the prefix are obtained by the same construction as in convention 3.2, and scheme means an alpha-class. ftv(α¯.τ):=ftv(τ)α¯. Substitution acts on free variables only: choosing the prefix fresh for vars(S), (α¯.τ)[S]:=α¯.τ[S]. The quantifier prefix is outermost only: the expression (α.αα)Nat is not a scheme, because may not occur under . This exclusion is a design decision, not an oversight; section 3.10 shows exactly which programs it abandons and what it buys.

Definition 3.7 — Contexts

HM contexts Γ are formed as in definition 2.4, except that declarations now attach schemes: Γ::=Γ,x:σ with the declared variables distinct. We write Γ(x) for the scheme declared for x, ftv(Γ) for the union of the ftv of its schemes, and Γ[S] for the context with S applied to every scheme.

Definition 3.8 — Instances

Let σ=α¯.τ0 with α¯=α1,,αk. A monotype τ is an instance of σ, written στ, if τ=τ0[ρ1/α1,,ρk/αk] for some monotypes ρ1,,ρk. For schemes, define σ1σ2iffevery instance of σ2 is an instance of σ1, read “σ1 is at least as general as σ2.” The relation is reflexive and transitive by construction, and for a monotype τ (whose sole instance is τ itself), στ holds exactly when στ. Thus the two similar symbols have different right-hand sorts: σττ is one monotype instance of σ,σ1σ2every monotype instance of σ2 is one of σ1.

For example, with σ=α.αα: σNatNat,σ(ββ)(ββ),σNatBool, the last because (αα)[ρ/α] always has equal domain and codomain. Also σβ.(ββ)(ββ): each instance of the right-hand scheme has the form (ρρ)(ρρ), which is (αα)[ρρ/α]. The converse fails, so is a preorder of generality. It becomes an order only after quotienting schemes that have the same monotype instances; this identifies alpha-renamed schemes and schemes differing only by vacuous quantifiers. The quotient matters when principal answers are compared: principality determines the set of instances, not a unique printed quantifier prefix.

Exercise 3.1

★★☆ Decide each of the following, giving the witnessing substitution or the obstruction: (a) αβ.αβγ.γγ; (b) γ.γγαβ.αβ; (c) α.αδδδ; (d) α.αδδ.δδ. For (d), first alpha-rename the quantified δ on the right to a fresh ϵ. Instances substitute only for quantified variables, so the free δ remains fixed on the left.

The semantic definition of is the right notion to use, but a syntactic test is needed to compute with it.

Lemma 3.9 — Characterization of generality

Let σ1=α¯.τ1 and σ2=β¯.τ2, after alpha-renaming the second prefix so that β¯ftv(σ1)=. Then σ1σ2 if and only if τ2=τ1[ρ¯/α¯] for some monotypes ρ¯.

Proof of Lemma 3.9 — Characterization of generality

Proof. () Suppose τ2=τ1[ρ¯/α¯] and let τ2[μ¯/β¯] be an instance of σ2. Then τ2[μ¯/β¯]=τ1[ρ¯/α¯][μ¯/β¯]=τ1[ρ¯[μ¯/β¯]/α¯], where the second equality is checked on the variables of τ1: a quantified αi is sent by both sides to ρi[μ¯/β¯], and a free γftv(σ1) is fixed by [ρ¯/α¯] and then by [μ¯/β¯], since β¯ avoids ftv(σ1). The result is an instance of σ1.

() The body τ2 is itself an instance of σ2 (instantiate each βj by βj). So σ1τ2, which is the displayed condition. ◻

Corollary 4.11 — Free variables decrease with generality

If σ1σ2, then ftv(σ1)ftv(σ2).

Proof of Corollary 4.11 — Free variables decrease with generality

Proof. Use the witnessing instance substitution from lemma 3.9. It changes only variables quantified by σ1; each free variable of σ1 remains free in the resulting body and is not captured by the freshly renamed prefix of σ2. ◻

The freshness hypothesis prevents a bound variable of σ2 from capturing a free variable of σ1 while the two bodies are compared. It costs nothing: a finite quantifier prefix can always be alpha-renamed away from the finite set ftv(σ1).

The second immediate consequence is stability under substitution.

Lemma 3.10 — Stability of generality

For every substitution S: if στ then σ[S]τ[S], and if σ1σ2 then σ1[S]σ2[S].

Proof of Lemma 3.10 — Stability of generality

Proof. Choose an alpha-equivalent representative σ=α¯.τ0 with α¯ fresh for vars(S), so σ[S]=α¯.τ0[S]. Let τ=τ0[ρ¯/α¯]. We claim τ0[ρ¯/α¯][S]=τ0[S][ρ¯[S]/α¯]. Check on the variables of τ0: each αi goes to ρi[S] on both sides (S fixes αi, which is outside vars(S)); a variable γα¯ goes on the left to γ[S], and on the right to γ[S][ρ¯[S]/α¯]=γ[S], since ftv(γ[S]) avoids α¯ (either γdom(S) and ftv(γ[S])ftv(S), or γ[S]=γα¯). By (3.2), τ[S] is an instance of σ[S].

For the second claim, standardize both schemes apart. Choose alpha-equivalent representatives σ1=α¯.τ1 and σ2=β¯.τ2 whose prefixes are disjoint and avoid vars(S), and with β¯ also fresh for ftv(σ1). By lemma 3.9, τ2=τ1[ρ¯/α¯],τ2[S]=τ1[S][ρ¯[S]/α¯], where the second equality is (3.2) applied to the freshened prefix α¯. Since σ1[S]=α¯.τ1[S], σ2[S]=β¯.τ2[S]. Moreover, ftv(σ1[S])(ftv(σ1)dom(S))ftv(S), and β¯ avoids the set on the right. Therefore lemma 3.9 applies in the converse direction. ◻

Lemma 3.11 — Quantifier introduction

If σ1σ2 and αftv(σ1), then σ1α.σ2.

Proof of Lemma 3.11 — Quantifier introduction

Proof. Let τ be an instance of α.σ2. Choose an alpha-equivalent representative σ2=β¯.τ2 with β¯ fresh for ftv(σ1), for α, and for the finitely many variables of τ; then τ=τ2[μ/α,μ¯/β¯] for some μ,μ¯ with β¯ftv(μ)=. Put S:=[μ/α]. Then τ=τ2[S][μ¯/β¯], checked on the variables of τ2 as before, using β¯ftv(μ)=. So τ is an instance of σ2[S]; by lemma 3.10, σ1[S]σ2[S], and σ1[S]=σ1 because αftv(σ1). Hence τ is an instance of σ1. ◻

Generalization

A scheme records which parts of a type a definition does not depend on. Generalization quantifies exactly the variables of the inferred monotype that the context does not fix.

Definition 3.12 — Generalization

For a context Γ and monotype τ, let α¯=ftv(τ)ftv(Γ), listed by the fixed enumeration of type variables, and define GenΓ(τ):=α¯.τ. Directly from the definitions, GenΓ(τ)τ and ftv(GenΓ(τ))=ftv(τ)ftv(Γ)ftv(Γ). The fixed enumeration is only a deterministic printing convention; permuting the prefix does not change the scheme’s instances.

The exclusion of ftv(Γ) is the entire content. The tempting simpler operation—quantify all variables of τ—breaks the connection between a variable’s occurrences inside τ and its occurrences in the surrounding assumptions. For example, under Γ=f:αβ, the naive result GenΓ?(αβ)=αβ.αβ would let two uses of the same f instantiate its domain and codomain differently. The context declares f:αβ once, so both uses must instantiate that same monotype. Therefore define the quantified set as ftv(τ)ftv(Γ); neither α nor β is quantified in the example.

If ftv(Γ)ftv(Γ), then the variables quantified by GenΓ(τ) form a subset of those quantified by GenΓ(τ), and therefore GenΓ(τ)GenΓ(τ). For a substitution S, alpha-freshening the surviving quantified variables away from S gives GenΓ(τ)[S]GenΓ[S](τ[S]).

A context with more free type variables pins down more of τ and therefore permits fewer quantifiers. This is why the generality arrow in the next lemma points from GenΓ to GenΓ, not conversely.

Lemma 3.13 — Monotonicity

If ftv(Γ)ftv(Γ), then GenΓ(τ)GenΓ(τ).

Proof of Lemma 3.13 — Monotonicity

Proof. Write GenΓ(τ)=α¯.τ and GenΓ(τ)=β¯.τ; then β¯α¯, and β¯ avoids ftv(GenΓ(τ))ftv(Γ). The identity substitution on α¯ exhibits the condition of lemma 3.9. ◻

Lemma 3.14 — Generalization under substitution

For every S, Γ, τ: GenΓ(τ)[S]  GenΓ[S](τ[S]).

Proof of Lemma 3.14 — Generalization under substitution

Proof. Let α¯=ftv(τ)ftv(Γ) and choose fresh variables α¯ avoiding vars(S)ftv(Γ)ftv(τ). With π:=[α¯/α¯] and T:=π;S, GenΓ(τ)[S]=α¯.τ[T], by the definition of substitution on schemes. Now let τ[S][μ¯/β¯] be an arbitrary instance of GenΓ[S](τ[S]), where β¯=ftv(τ[S])ftv(Γ[S]). Define a further substitution N on the primed prefix by αi[N]:=αi[S][μ¯/β¯]. We claim τ[T][N]=τ[S][μ¯/β¯], which exhibits the instance as an instance of GenΓ(τ)[S] and finishes the proof. Check the claim on each γftv(τ) using (3.1). If γ=αiα¯: the left side gives αi[T][N]=αi[S][N]=αi[N] (as αi is fresh for S), which is αi[S][μ¯/β¯], the right side at αi. If γα¯, then γftv(Γ), so ftv(γ[S])ftv(Γ[S]), which avoids β¯; hence the right side fixes γ[S], while the left side gives γ[T][N]=γ[S][N]=γ[S], since ftv(γ[S]) contains no primed variable. ◻

Standardizing the variables to be generalized apart before substitution produces a context-equivalent substitution T for which generalization recovers the renamed prefix. This is the converse-looking companion to lemma 3.14: that lemma fixes S, so generalization can become less general, whereas the fresh renaming below changes S away from the context while preserving its action on the context.

Lemma 3.15 — Fresh renaming before substitution

For every S, Γ, τ there is a substitution T such that

  1. T=ftv(Γ)S, so in particular Γ[T]=Γ[S];

  2. GenΓ[S](τ[T])  GenΓ(τ)[S].

Proof of Lemma 3.15 — Fresh renaming before substitution

Proof. Take α¯, α¯, π, and T=π;S as in the proof of lemma 3.14. Claim 1 holds because π fixes ftv(Γ). For claim 2, we saw GenΓ(τ)[S]=α¯.τ[T]. Every αi is free in τ[T] (it is αi[T] with αiftv(τ), by (3.1)) and is absent from ftv(Γ[S])ftv(Γ)ftv(S) by freshness. Hence the prefix of GenΓ[S](τ[T]) contains all of α¯, possibly more, and an instance of α¯.τ[T] becomes an instance of GenΓ[S](τ[T]) by instantiating the extra quantified variables by themselves. ◻

Exercise 3.2

★★☆ Compute GenΓ(τ) for: (a) Γ=Γ0 and τ=(αα)αα; (b) Γ=Γ0,f:αβ and τ=α(βγ)γ; (c) Γ=Γ0,f:α.αβ and τ=αβ. In (c), first compute ftv(Γ); the quantified α of the assumption does not belong to it. Then verify instance (2) of lemma 3.15 concretely for (b) with S=[Nat/α].

The declarative system

Definition 3.16 — Typing

The judgment Γe:σ, for Γ a context of schemes, e a term of the let-language, and σ a scheme, is inductively defined by

(x:σ)Γ
Γx:σ
Var
Γe:σσσ
Γe:σ
Inst
Γe:σαftv(Γ)
Γe:α.σ
Gen
Γ,x:τ1e:τ2
Γλx.e:τ1τ2
Lam
Γe1:τ2τΓe2:τ2
Γe1e2:τ
App
Γe1:σΓ,x:σe2:τ
Γlet x=e1 in e2:τ
Let

Here τ,τ1,τ2 range over monotypes and σ,σ over schemes: an abstraction binds its variable at a monotype, an application consumes monotypes, and only let may bind a variable at a scheme. Binder freshness and context formation are metalevel side conditions as in definition 2.5.

The asymmetry between Lam and Let is the design. A λ-bound variable stands for an argument that will arrive at run time from an unknown caller, so its type is a single (possibly variable-containing) monotype, recorded in the context and visible to the side condition of Gen. A let-bound variable stands for a definition that is textually present, so the system may first close over everything the context does not fix (Gen), attach the resulting scheme, and let every use instantiate it independently (Inst).

Example 3.17 — The repeated identity, typed

We derive Γ0Pid:Nat. Write σid:=α.αα and Γ1:=Γ0,id:σid. First the definition:

(x:α)(Γ0,x:α)
Γ0,x:αx:α
Var
Γ0λx.x:αα
Lam
Γ0λx.x:σid
Gen

The side condition αftv(Γ0) holds because Γ0 contains no type variables. Next the two uses, each an Var step followed by Inst at its own instance:

Γ1id:σidσid(NatNat)(NatNat)
Γ1id:(NatNat)(NatNat)
Inst
Γ1id:σidσidNatNat
Γ1id:NatNat
Inst

Two App steps type the body: Γ1idsucc:NatNat from the first instance and Γ1succ:NatNat (Var); then Γ1idzero:Nat from the second instance and zero; then Γ1(idsucc)(idzero):Nat. Finally Let discharges the definition:

Γ0λx.x:σidΓ1(idsucc)(idzero):Nat
Γ0Pid:Nat
Let

One binding; two incompatible uses; no annotations. Demand 1 of the introduction is met.

Lemma 3.18 — Weakening

If Γe:σ and Γ,Δ is a context extension, then Γ,Δe:σ.

Proof of Lemma 3.18 — Weakening

Proof. First alpha-rename every variable bound by a Gen step away from the finite set ftv(Δ). Then induct on the derivation. Variable membership persists under extension, every term-forming rule reuses its induction hypotheses, and each Gen side condition remains true because its quantified variable occurs in neither Γ nor Δ. ◻

Lemma 3.19 — Term substitution

If Γa:σ and Γ,x:σe:σ, then Γe[a/x]:σ.

Proof of Lemma 3.19 — Term substitution

Proof. Induct on the second derivation after freshening all term binders away from x and the free variables of a. At a Var leaf for x, use the first premise; at any other variable, reuse the declaration from Γ. Rules Inst and Gen reapply directly. In the latter case its side condition says that the quantified type variable is absent from ftv(Γ,x:σ), hence also from ftv(Γ) after x is discharged. Lambda and application follow from their induction hypotheses, using lemma 3.18 to carry the first premise under a newly bound variable. The let case is the same: weaken the typing of a under the let binder, apply the induction hypothesis to both premises, and reapply Let. If that binder is also called x, freshen it first as permitted by convention 3.2. ◻

Definition 4.22 — Pure call-by-value dynamics

For the variable, abstraction, application, and let grammar of definition 3.1, values are abstractions and evaluation contexts are E::=[]EevElet x=E in e. The root contractions are (λx.b)v0b[v/x],let x=v in e0e[v/x], and the one-step relation is their compatible closure under E. This definition concerns the closed base let-language; primitive constants and algebraic data receive their constructor and eliminator rules at convention 3.3, section 3.8.

Now the boundary case. Why can a λ-bound variable not be used at two types? Suppose we try to type λf.e so that f is applied to zero and to true inside e. Rule Lam forces a context Γ,f:τf with one monotype τf; inside the body, any attempt to apply Gen to a variable of τf is blocked by the side condition. For example, from Γ,f:αβf:αβ one would need αftv(Γ,f:αβ) to introduce α, but the displayed condition is false because α occurs in the declaration of f. Hence Gen does not apply. So both uses of f must share the single monotype τf, and no monotype is simultaneously an arrow out of Nat and an arrow out of Bool. Indeed, inversion on the two application derivations would give τf=NatA and τf=BoolB for some monotypes A,B. Injectivity of the arrow constructor would then give Nat=Bool, contradicting disjointness of type constructors.

Exercise 3.3

★★☆ (a) Derive Γ0λx.λy.x:αβ.αβα. (b) Derive Γ0let id=λx.x in idid:ββ; the two uses of id take the instances (ββ)(ββ) and ββ. (c) Show that the judgment of (b) can be strengthened by one final Gen to the scheme β.ββ.

From programs to equations

The declarative system says which typings are derivable; it does not compute one. In example 3.17 the derivation contains chosen instances. Constraint generation instead assigns fresh unknowns and records the equations forced by typing rules. Its representative input is the twice-application combinator: twice:=λf.λx.f(fx).

Definition 4.23 — Constraint generation

For this let-free calculation, write Γce:τE to mean that e has provisional type τ provided the finite equation list E is solved. If EE denotes list concatenation and every variable called fresh is new for the whole calculation, write τρ for a deferred equation requiring the two monotypes to become literally equal under a solution. The generating rules are

Γ(x)=τ
Γcx:τ()
C-Var
Γ,x:αce:τEα fresh
Γcλx.e:ατE
C-Lam
Γce1:τ1E1Γce2:τ2E2β fresh
Γce1e2:βE1E2(τ1τ2β)
C-App

Here Γ(x)=τ in C-Var means a monotype declaration. The constraint fragment has no let, so it never consults a polymorphic scheme without first instantiating it.

This constraint generator is not Algorithm W: it collects the complete list and solves it once. The batch output is therefore an equation list and a solution, not the pair of a threaded substitution and monotype returned by W.

Build the derivation from the outside in, under Γ0. Rule C-Lam binds f at an unknown monotype αf and then x at an unknown αx; the working context is Γ:=Γ0,f:αf,x:αx. The body is f(fx), an application, so C-App demands that the type of the function part be an arrow whose domain is the type of the argument part. Working on the inner application fx first: f has type αf (C-Var); x has type αx; C-App applies only if αf is an arrow with domain αx. We do not know that; we record it, inventing a fresh unknown β1 for the codomain: αfαxβ1,givingΓcfx:β1(αfαxβ1). The outer application f(fx) likewise demands that αf be an arrow with domain β1; invent β2: αfβ1β2,Γcf(fx):β2(αfαxβ1,αfβ1β2). Discharging the two C-Lam steps, the whole term has type αfαxβ2, provided the equation set Etwice:=(αfαxβ1, αfβ1β2) has a solution: a substitution making both equations literal identities. After applying a solution S, the two right-hand sides must coincide: (αxβ1)[S]=(β1β2)[S]. By injectivity of , αx[S]=β1[S],β1[S]=β2[S]. Taking S=[αx/β1, αx/β2, (αxαx)/αf] solves Etwice and turns the conditional typing into an actual derivation of Γ0twice:(αxαx)αxαx, and one Gen, alpha-renaming the quantified αx to α for display, yields the scheme α.(αα)αα.

Was the solution forced? Not entirely. The substitution S=[Nat/αx,Nat/β1,Nat/β2,(NatNat)/αf] also solves Etwice and yields the less useful (NatNat)NatNat. Demand 3 of the introduction is exactly the demand that we always take the most general solution, of which every other is a substitution instance. The remaining problem is to construct such a solution whenever the equation set is solvable.

Now a failure. Consider the self-application λt.tt. Binding t at an unknown γ, the application tt demands that γ be an arrow with domain γ: γγδ. No substitution can solve this: applying any S makes the right side an arrow one of whose immediate subtrees is the left side, and a finite tree cannot be a proper subtree of itself. Repeating that subtree occurrence would produce an infinite descending path in a finite tree. Note precisely what failed. If t were let-bound to twice, each of the two occurrences would receive its own instance of the scheme of twice, and no single γ would be forced to equal an arrow over itself; the let-bound term therefore generates distinct variables for its two occurrences. The λ-bound version shares one monotype and dies on the equation above.

The unifier’s contract on an equation list E is exact: success returns a substitution that satisfies every equation and through which every other such substitution factors on the variables occurring in E. Failure means that no satisfying substitution exists. The notation and the lexicographic triple of natural numbers that decreases at every recursive call are fixed below.

Exercise 3.4

★★☆ Carry out the walk for compose:=λf.λg.λx.f(gx): introduce unknowns αf,αg,αx, extract two equations, solve them by hand, and conclude with the scheme αβγ.(βγ)(αβ)αγ after Gen.

Unification

Unification solves finite equations between constructor trees. Constructor injectivity justifies decomposition, constructor disjointness justifies a head clash, and finiteness of the trees justifies the occurs check. No property specific to the arrow constructor is used.

Definition 3.20 — Equation problems and unifiers

An equation problem is a finite list E=(τ1ρ1,,τnρn). A substitution S solves E, written SE, when τi[S]=ρi[S] for every i. Put vars(E):=i(ftv(τi)ftv(ρi)). A solution U is a most general unifier, or MGU, when every solution S factors through it: for some substitution T, S=vars(E)U;T. Equality only on vars(E) is intentional; the problem says nothing about the action of a substitution elsewhere. For substitutions on a variable set X, put U below S when S=XU;T for some T. This relation is a preorder, and an MGU is a least solution in it. The factor T need not be unique, so this is a factorization property rather than a uniqueness claim. The glyph records this equation-by-equation satisfaction. The postfix action E[R] applies R to both monotypes in every equation of E, preserving list order. We always pass a list to unify; in particular, unify((τρ)) is a call on the singleton equation list.

Lemma 3.21 — Occurs check

If αftv(τ) and τα, then the equation ατ has no solution.

Proof of Lemma 3.21 — Occurs check

Proof. The occurrence of α in τ is proper, so for every substitution S, the finite tree α[S] occurs as a proper subtree of τ[S]. If α[S]=τ[S], a finite tree would be a proper subtree of itself. Equivalently, following the proper-subtree occurrence repeatedly would give an infinite descending path through one finite tree. Both are impossible. ◻

Definition 3.22 — The unification algorithm

The partial function unify(E) returns at most one substitution for an input and is undefined when it rejects that input. It repeatedly examines the first equation. Before selecting a recursive clause, it normalizes the orientation of that equation by the nonrecursive rewrite U-Orient below. It then tests U-Delete, U-Eliminate, and U-Decompose in that order, followed by the two failure clauses. Thus U-Delete takes priority over decomposition of a reflexive compound equation.

U-Done. Return the identity on the empty problem: unify()=id.

U-Delete. Delete a reflexive equation: unify(ττ,E)=unify(E). U-Orient. If the first equation has the form F(τ¯)α, replace it in place by αF(τ¯) and select the applicable clause below. This rewrite does not invoke unify. U-Eliminate. If ατ and αftv(τ), drop the first equation ατ, put R=[τ/α], and apply R to every remaining equation: unify(ατ,E)=let U=unify(E[R]) in R;U.

U-Decompose. Replace an equation with equal head constructor by its component equations: unify(F(τ1,,τk)F(ρ1,,ρk),E)=unify(τ1ρ1,,τkρk,E).

U-Clash. Fail on F(τ¯)G(ρ¯) when FG.

U-Occurs. Fail on ατ when ατ and αftv(τ).

These are the only failure clauses. The list order is immaterial to the result’s universal property, but fixing left-to-right traversal makes the implementation deterministic.

Example 3.23 — Unifying the type of twice

Apply U-Eliminate to the first equation of Etwice: R1=[αxβ1/αf]. The remaining equation becomes αxβ1β1β2. Decomposition gives αxβ1 and β1β2. Eliminating in that order yields R2=[β1/αx],R3=[β2/β1]. The returned composite R1;R2;R3 sends αf to β2β2 and sends αx,β1 to β2. This follows the left-to-right argument order in U-Decompose. It differs from the hand solution above only by renaming the one surviving parameter αx to β2.

Lemma 3.24 — Termination

Definition 3.22 terminates on every finite equation problem.

Proof of Lemma 3.24 — Termination

Proof. Orientation is a finite preprocessing step and makes no recursive call. Order the arguments of recursive calls lexicographically by (|vars(E)|,total number of constructor nodes in E,|E|). Elimination removes α from every remaining equation; the occurs condition ensures that substituting τ cannot put it back, so the first component decreases. Decomposition removes the two equal outer constructor nodes and preserves the variables, so the second component decreases. Deleting a reflexive equation cannot increase either of the first two components. If the variable count drops, the first component decides the comparison; if it is unchanged but constructor nodes drop, the second does. Only when both are unchanged is the shorter equation list needed. Thus the triple decreases in the delete case as well. Delete, decompose, and eliminate exhaust the recursive clauses; the lexicographic order on triples of natural numbers is well founded. ◻

Lemma 3.25 — Elimination factorization

Let R=[τ/α] with αftv(τ). A substitution S solves (ατ,E) iff SE[R] and S=vars(ατ,E)R;S.

Proof of Lemma 3.25 — Elimination factorization

Proof. If S solves the displayed equation, then α[S]=τ[S]. Hence, on α, α[R;S]=τ[S]=α[S], while on every γα, γ[R;S]=γ[S]. Thus S=R;S on all problem variables. Applying this equality to both sides of each equation in E shows SE[R].

Conversely, α[R]=τ=τ[R], the last equality using αftv(τ). Thus every substitution after R solves ατ. If S=R;S on the problem variables and SE[R], then S also solves every equation of E: for each side ρ occurring in E, agreement on the problem variables gives ρ[S]=ρ[R;S]=ρ[R][S], and the equation in E[R] identifies the final expressions. ◻

Theorem 3.26 — Unification is sound and principal

For every finite problem E:

  1. if unify(E)=U, then U is an MGU of E;

  2. if the algorithm fails, E has no solution;

  3. if unify(E)=U and γvars(E), then γ[U]=γ; moreover, for δvars(E), ftv(δ[U])vars(E).

Proof of Theorem 3.26 — Unification is sound and principal

Proof. Induct according to the terminating recursion of lemma 3.24.

We prove both support assertions in item 3 simultaneously. U-Done returns the identity, through which every substitution factors. U-Delete preserves exactly the same solutions. Its recursive variable set is a subset of the old one; the recursive support assertions therefore fix any variable lost with the deleted equation and introduce no new one. For U-Decompose, injectivity of the common constructor says that a substitution solves the old head equation iff it solves every new component equation; the two problems have the same variables, so both assertions pass through the recursive call. Orientation also preserves the variable set. A head-constructor clash has no solution by disjointness. The occurs-check failure has no solution by lemma 3.21.

It remains to examine U-Eliminate. Its three obligations are solution transport,factor extension,support preservation. Put R=[τ/α] and suppose the recursive call returns an MGU U of E[R]. By lemma 3.25, R;U solves the original problem. Let S be any other solution. The same lemma gives SE[R] and S=R;S on the old problem variables. Since U is most general for E[R], there is T0 with S=U;T0 on its variables. Extend T0 to a substitution T by setting γ[T]=γ[S] for each γftv(τ)vars(E[R]). The recursive support assertions say that U fixes those added variables and that none of them occurs in δ[U] for δvars(E[R]). Thus changing T0 there does not disturb the old factorization, and now S=U;T on vars(E[R])ftv(τ). Every old variable other than α lies in that union. Therefore, for every old variable γα, γ[R]=γ, hence γ[S]=γ[R][U;T]. For α, α[R][U;T]=definitionofRτ[U;T]=factorizationonftv(τ)τ[S]=Sατα[S]; the middle equality follows from the extended factorization on ftv(τ). Thus S=R;U;T on the original problem variables, proving that R;U is most general. This also proves that failure of the recursive call implies failure of the original problem. Finally, R changes only α and introduces only variables already in the original problem. The recursive result fixes variables outside vars(E[R]) and introduces no outside variable in the image of a recursive problem variable. Hence R;U fixes every variable outside vars(E) and maps each old problem variable to a type using only old problem variables. This proves both remaining support assertions. ◻

Corollary 4.31 — Mutual factorization of most general unifiers

If U and V are MGUs of the same problem E, then each factors through the other on vars(E).

Proof of Corollary 4.31 — Mutual factorization of most general unifiers

Proof. Because V is a solution and U is most general, there is R with V=vars(E)U;R. Interchanging U and V gives the reverse factorization. Thus the two represent the same element of the solution preorder. No uniqueness-up-to-renaming conclusion follows: that stronger statement would require an extra rule choosing one printed substitution from each mutually factoring class. ◻

Exercise 3.5

★★☆ Run unify on ((αβ)γ(NatBool)δ, δNat). Write every intermediate equation list and the returned composite substitution. Then replace the second equation by δδNat and identify the precise failure rule. A complete calculation is given in appendix B.

Algorithm W

Constraint generation and unification can now be fused. The fusion matters: after inferring the function part of an application, its substitution must be applied to the context before the argument is inferred. Otherwise the two walks can assign incompatible meanings to the same context variable.

The unthreaded attempt already fails on twice. After the inner application has learned U1=[βγ/α], suppose the outer application still uses the stale α. It solves αγδ independently of αβγ; the returned result then leaves δ unconstrained and suggests the spurious type (βγ)βδ. Applying U1 before the second walk instead forces βγ=γδ, hence one common type. The let clause needs the same threading: generalizing over stale Γ could quantify a variable that the first substitution has already fixed in Γ[S1].

Definition 3.27 — Fresh instantiation

For σ=α1αk.τ, let fresh(σ) be τ[β1/α1,,βk/αk]. Index the fixed variable enumeration by natural numbers. A run of W carries a counter initialized to one more than the greatest index occurring in its input (and to zero when the input contains no type variable). The variables βi are consumed at successive counter values. The counter only increases and is never rewound between sibling calls. Thus holes below the initial maximum are deliberately skipped, and a hand trace never reuses a name that appeared earlier in the run.

Definition 3.28 — Algorithm W

The partial algorithm W(Γ,e) returns a pair (S,τ). Every type variable described as fresh below is consumed from the supply of definition 3.27 and is fresh for the entire run. W(Γ,x)=(id,fresh(Γ(x))),W(Γ,λx.e)=let α be fresh,(S,τ)=W((Γ,x:α),e),in (S,α[S]τ),W(Γ,e1e2)=let (S1,τ1)=W(Γ,e1),(S2,τ2)=W(Γ[S1],e2),β be fresh,C=(τ1[S2]τ2β),U=unify(C),in (S1;S2;U,β[U]),W(Γ,let x=e1 in e2)=let (S1,τ1)=W(Γ,e1),σ=GenΓ[S1](τ1),Γ1=(Γ[S1],x:σ),(S2,τ2)=W(Γ1,e2),in (S1;S2,τ2). The variable clause fails when xdom(Γ); an application fails when either recursive call or unification fails. Every recursive W call is on a strict subterm, and lemma 3.24 terminates each unification call. Hence W always returns a pair or reports failure on a finite input term.

Mathematical diagramDiagram

Substitution flow in W’s application branch (top) and let branch (bottom). Gray boxes are computations; arrow labels are the contexts or equations passed between them.

In the upper row of figure 4.1, the argument is checked in Γ[S1], the function result is updated once more to τ1[S2], and U solves τ1[S2]τ2β. In the lower row, the same S1 first updates the context used by generalization; the body then sees exactly Γ[S1],x:σ. The diagram records these two equalities and does not replace any clause of definition 3.28.

Lemma 4.35 — Completion of a finite name matching

Let (a1,,an) and (b1,,bn) be lists without repetitions, and suppose that every variable in both lists avoids a finite set F. The ordered matching aibi extends to a permutation π of the type variables with finite support, and π fixes every variable in F.

Proof of Lemma 4.35 — Completion of a finite name matching

Proof. Draw the finite directed graph on AB, where A={a1,,an} and B={b1,,bn}, with one edge aibi for each i. Every vertex has at most one outgoing and at most one incoming edge because both lists have no repetitions. Each nontrivial connected component is therefore a directed cycle or a directed path. Keep every cycle. For each path, add one edge from its terminal vertex to its initial vertex. Fix every variable outside AB. The resulting map has exactly one incoming and one outgoing edge at every vertex of AB, agrees with every prescribed pair, and is the identity off that finite set. Since F(AB)=, it fixes F. ◻

Lemma 3.29 — Fresh-choice irrelevance

Run W on one input (Γ,e) with two legal supplies. The executions either both fail at corresponding clauses or both succeed. In the successful case, if their results are (S,τ) and (S,τ), there is a finite bijection π of type variables that fixes ftv(Γ), matches the variables consumed in the first run with those consumed in the second in allocation order, and satisfies S=π1;S;π,τ=τ[π],Γ[S]=Γ[S][π]. Consequently success, soundness, and the principal-pair property are independent of the chosen legal supply.

Proof of Lemma 3.29 — Fresh-choice irrelevance

Proof. First induct on the unification recursion. For every finite bijection π, renaming preserves the selected clause and unify(E[π])=π1;U;πwhenunify(E)=U; it also preserves both failure clauses. Renaming preserves constructor heads, reflexivity, and the occurs test. In U-Eliminate, the required calculation is [ρ[π]/α[π]]=π1;[ρ/α];π. The equations left after applying the current elimination substitution form the residual equation problem. The induction hypothesis applies to this renamed residual equation problem, and associativity assembles the returned composites.

The W induction is strengthened over arbitrary unconsumed suffixes of the two supplies and carries the ordered partial matching of variables consumed so far. At each allocation, append the pair of next variables. Injectivity of each supply keeps the matching one-to-one, and disjointness from ftv(Γ) keeps its domain and range away from the input variables. Whenever a total renaming is needed, complete the matching by lemma 4.35. A later completion agrees on every pair already prescribed, while later supply variables are absent from the current contexts, types, and equations; changing the closing edges therefore does not change an established equality. In the variable case, instantiation consumes corresponding finite prefixes, so structural substitution gives the claimed renamed type and the identity conjugacy equation. In the lambda case, extend the bijection by the binder pair and apply the induction hypothesis to the body under the two renamed contexts.

For application, the first induction hypothesis gives S1=π1;S1;π and τ1=τ1[π]. Its unconsumed suffixes, rather than the original supplies, are passed to the argument calls; the second induction hypothesis therefore extends the same π and gives the corresponding equations for S2 and τ2. Pair the result variables next. The two application equations are related by π, so unification equivariance gives U=π1;U;π. Associativity then yields S1;S2;U=π1;(S1;S2;U);π. It also shows that unification succeeds or fails in both runs.

For let, the definition of generalization gives GenΓ[S1](τ1)[π]=αGenΓ[S1][π](τ1[π]), because π carries the set difference of free variables bijectively to the set difference on the right. The body induction applies to these alpha-equivalent extended contexts and to the two unconsumed suffixes. This proves the three displayed conclusions on the input variables and the finite set of consumed names in every successful clause. Only finitely many names are consumed by a finite term. Apply lemma 4.35 to the two consumed prefixes. Its permutation agrees with every prescribed pair and fixes ftv(Γ); variables outside the prefixes occur in neither execution. The preceding equations therefore hold for this completed permutation. The same lockstep induction, stopped at the failing clause, proves corresponding failure. ◻

The application clause is best read as a derivation. The first call says e1 has provisional type τ1 after S1. The second call may learn more, so it sees Γ[S1] and changes the function type to τ1[S2]. A fresh β stands for the result. Unification enforces τ1[S2]=τ2β, and its substitution U is applied to every accumulated answer.

Example 3.30 — W on the identity

For λx.x, choose fresh α. The variable call under Γ,x:α returns (id,α), so the lambda call returns (id,αα). At a let binding, generalization turns this into α.αα before the body is examined. This is not a guessed annotation; it is the output of two clauses.

Example 4.38 — A complete W pipeline before its proof

Run W on twice=λf.λx.f(fx). Give (f) fresh type α and (x) fresh type β. The inner application introduces γ and solves αβγ,U1=[βγ/α]. The outer application must use the substituted function type. With fresh result δ, it solves α[U1]=βγγδ,U2=[δ/β,δ/γ]. Therefore W(Γ0,twice)=(U1;U2,(δδ)δδ). Since δftv(Γ0), a surrounding let generalizes it to δ.(δδ)δδ. Thus W maps the term to a substitution, a monotype, and a generalized scheme. Soundness must show that the scheme types the original term; principality must factor every competing typing through the returned substitution.

The syntax-directed judgment

Definition 3.31 — Syntax-directed HM typing

The judgment Γse:τ has one rule for each term constructor:

Γ(x)τ
Γsx:τ
S-Var
Γ,x:τ1se:τ2
Γsλx.e:τ1τ2
S-Lam
Γse1:τ2τΓse2:τ2
Γse1e2:τ
S-App
Γse1:τ1Γ,x:GenΓ(τ1)se2:τ2
Γslet x=e1 in e2:τ2
S-Let

Lemma 3.32 — Type substitution

If Γe:σ, then Γ[S]e:σ[S]. If Γse:τ, then Γ[S]se:τ[S].

Proof of Lemma 3.32 — Type substitution

Proof. For the declarative judgment, induct on the derivation. Before a Gen case, alpha-rename its quantified variable away from vars(S). The side condition then remains true after applying S, and Gen rebuilds the conclusion. Inst uses lemma 3.10. Every other case applies S to all monotypes in the displayed rule.

The syntax-directed assertion needs one extra piece of bookkeeping at a let. First standardize the finite derivation apart: whenever an S-Let premise has inferred type τ1, rename the variables in G=ftv(τ1)ftv(Γ) away from vars(S) and from all variables used outside that premise. These variables are bound by GenΓ(τ1) in the body context, so this changes only the representative of that scheme and the matching first-premise derivation; it changes neither the surrounding context nor the conclusion.

Now induct on the standardized derivation. Variable, lambda, and application cases use instance stability and the induction hypotheses. In the let case the freshness of G gives the alpha-equality GenΓ(τ1)[S]=αGenΓ[S](τ1[S]). Indeed, S fixes every member of G, and every other free variable of τ1 occurs in Γ, so all variables introduced by its image under S occur in Γ[S] and are not generalized. The two induction hypotheses therefore produce exactly the premises of S-Let, with the body declaration identified by (3.3). ◻

Lemma 3.33 — More general contexts

Suppose Γ and Γ have the same declared term variables and Γ(x)Γ(x) for each x. If Γse:τ with height h, then Γse:τ with the same height h.

Proof of Lemma 3.33 — More general contexts

Proof. Induct on the syntax-directed derivation. In S-Var, transitivity of replaces the old declaration. Lambda and application are immediate. More-general schemes have no additional free variables (corollary 4.11), so ftv(Γ)ftv(Γ). Hence GenΓ(τ1)GenΓ(τ1) by lemma 3.13. In the let case, context monotonicity replaces the latter declaration by the former before applying the induction hypothesis to the body. Every case replaces only declarations and reapplies the same final rule, so the derivation height is unchanged. ◻

Theorem 3.34 — Declarative and syntax-directed typing agree

For every monotype τ, Γe:τΓse:τ. More generally, if Γe:σ, then there is Γse:τ such that GenΓ(τ)σ.

Proof of Theorem 3.34 — Declarative and syntax-directed typing agree

Proof. From right to left, replace S-Var by Var followed by Inst. In S-Let, obtain Γe1:GenΓ(τ1) by applying Gen once for every generalized variable, then use Let. The other rules are already declarative rules.

For the converse, normalize a declarative derivation by induction on its last rule, proving the stronger statement about schemes. At a Var leaf, present the declared scheme as α¯.τ0 and replace its prefix by fresh variables β¯ absent from Γ. Rule S-Var gives the monotype τ0[β¯/α¯], whose generalization over Γ is alpha-equivalent to the declared scheme. Inst composes the old generality witness with its premise σσ. In a Gen case, lemma 3.11 adds the quantified variable to the target scheme; its freshness follows from the rule side condition.

For Lam, the induction output for the premise has some result ρ with GenΓ,x:τ1(ρ)τ2. Choose its witnessing instance substitution Q, so ρ[Q]=τ2. The domain of Q consists of variables generalized over the premise context, hence Q fixes that context. The syntax-directed part of lemma 3.32 gives the required premise at τ2, and S-Lam applies. In an application, the two premise outputs may be specialized to the rule’s displayed monotypes τ2τ and τ2: standardize their generalized variables apart, take the two witnesses from lemma 3.9, apply lemma 3.32 separately, and then apply S-App. Each witness has domain among variables generalized over Γ, hence fixes Γ; standardizing the two domains apart therefore leaves both specialized premises under the same context Γ.

For Let, let the induction output for its first premise be Γse1:ρ1,GenΓ(ρ1)σ. The second induction output is initially under Γ,x:σ. By lemma 3.33, replace that declaration by the more-general GenΓ(ρ1) and apply S-Let. Its result type is the body output ρ2. Finally, lemma 3.13 gives GenΓ(ρ2)GenΓ,x:σ(ρ2), so the body’s induction witness composes to the required witness for the let conclusion. When that conclusion is already a monotype, specialize the final syntax-directed derivation exactly as in the lambda case. ◻

Theorem 4.43 — Pure HM safety

In the base let-language of definition 4.22:

  1. preservation has the form Γe:τeeΓe:τ;

  2. if e:τ, then e is a value or there is an e with ee.

Proof of Theorem 4.43 — Pure HM safety

Proof. For preservation, first use theorem 3.34 to replace the declarative monotype derivation by a syntax-directed one. Induct on the step derivation. At each compatible frame, inversion of the syntax-directed derivation exposes the child typing. Translate that child to declarative typing, apply the induction hypothesis, translate the result back, and rebuild the unique syntax-directed rule for the frame. At beta, inversion gives Γ,x:τ1b:τ2 and Γv:τ1; at let, it gives Γv:σ and Γ,x:σe:τ. In both root cases lemma 3.19 types the contractum. Finally translate the rebuilt syntax-directed derivation to the claimed declarative judgment.

For progress, translate to syntax-directed typing and induct on that derivation. An abstraction is a value. In an application, first step the function, then the argument; when both are values, the value grammar forces the function to be an abstraction, so beta applies. In a let, step its definition or, when that definition is a value, contract the let. The variable case is impossible under the empty context. ◻

The progress clause is restricted to the closed, constant-free base language. For every diagnostic under Γ0, extend the value grammar by the primitive function succ, the Boolean constants, and the numeral judgment Xzero numNZn numsucc n numNS. Numerals and Booleans are final values. Thus succ n is final exactly when n num; no value or contraction rule applies to succ true, so that closed term is stuck. Thus the primitive signature itself distinguishes Nat from Bool: the former admits succ, while the latter does not.

Correctness and principal types

Theorem 3.35 — Soundness of W

If W(Γ,e)=(S,τ), then Γ[S]e:τ.

Proof of Theorem 3.35 — Soundness of W

Proof. Induct on e, following definition 3.28.

For a variable, fresh(Γ(x)) is an instance of its scheme, so Var followed by Inst applies. In the lambda case the induction hypothesis is Γ[S],x:α[S]e:τ; Lam gives the returned type α[S]τ.

For e1e2, the induction hypotheses give Γ[S1]e1:τ1,Γ[S1;S2]e2:τ2. Apply lemma 3.32 with S2 to the first derivation and then with U to both. Since U solves W’s equation τ1[S2]τ2β, τ1[S2;U]=τ2[U]β[U]. Rule App therefore returns β[U] under Γ[S1;S2;U].

For a let, the first induction hypothesis and repeated Gen give Γ[S1]e1:σ,σ=GenΓ[S1](τ1). Apply type substitution S2 to this declarative derivation, obtaining Γ[S1;S2]e1:σ[S2]. The second induction hypothesis is Γ[S1;S2],x:σ[S2]e2:τ2. These are precisely the two premises of declarative Let. The returned context and type are Γ[S1;S2] and τ2. ◻

Lemma 4.45 — Extending an MGU factorization off its problem

Let U be the MGU returned for an equation list E, and suppose a solution Q of E factors as U;R on vars(E). For any finite protected set P, the residual R can be extended so that Q=vars(E)PU;R without changing it on the variables of E.

Proof of Lemma 4.45 — Extending an MGU factorization off its problem

Proof. For each γPvars(E) set γ[R]:=γ[Q] and retain R elsewhere. The unifier fixes every variable outside its equation problem by theorem 3.26(3), so γ[U;R]=γ[R]=γ[Q] there. On vars(E) the original factorization is unchanged. ◻

The protected set has a concrete role even in a one-node application. Take Γ=Γ0,f:α, the term fzero, and protect P={α}. Suppose the target specialization is T=[NatBool/α]. Both recursive calls return the identity; the application equation has MGU U=[Natβ/α], and the residual R=[Bool/β] satisfies T={α}U;R,β[R]=Bool. Protecting α is exactly what retains this equality after the recursive calls have returned.

For a substitution Q and a finite set P, write fvQ(P):=γPftv(γ[Q]). This is the set of variables in the Q-images of P; the parenthesized subscript is not substitution or term instantiation.

Lemma 4.46 — Protected principal-pair induction

Let P be a finite set of type variables and suppose Γ[T]se:τ. Choose every variable generated during this W run fresh for Pvars(T) and for the finite target derivation, as permitted by lemma 3.29. Then W succeeds; writing its result as (S,τ), there is a substitution R such that T=ftv(Γ)PS;R,τ=τ[R].

Proof of Lemma 4.46 — Protected principal-pair induction

Proof. Induct on the height of the syntax-directed derivation under Γ[T], generalized over Γ,T,P, while maintaining both the displayed factorization and agreement on P. By lemma 3.29, every generated variable lies outside ftv(Γ)P. Extend each MGU factor outside its equation variables by lemma 4.45. In the application case, protect fvS1(P)ftv(τ1) during the second recursive call. Its factor then satisfies τ1[S2;R2]=τ1[R1], so the target substitution solves W’s final application equation.

Variable. The target τ is an instance of Γ[T](x), while W’s τ replaces the quantified prefix of Γ(x) by variables fresh for T and Γ. Extend T by sending those fresh variables to the monotypes used in the target instance. The resulting R agrees with T on ftv(Γ). Because the generated variables avoid P and W’s variable substitution is the identity, extend R by γ[R]=γ[T] for the remaining γP; this does not change τ[R] and gives the protected agreement. Thus τ=τ[R].

Abstraction. The target derivation has a premise Γ[T],x:ρ1se0:ρ2 and τ=ρ1ρ2. W chose fresh α. Extend T by αρ1 and apply the induction hypothesis to the recursive call on Γ,x:α. It gives R with ρ2=τ0[R] and, because α occurs in the enlarged context, ρ1=α[S;R]. Hence τ=ρ1ρ2=(α[S]τ0)[R], the returned lambda type.

Application. The target derivation has an intermediate type ρ: Γ[T]se1:ρτ and Γ[T]se2:ρ. The first induction hypothesis factors T through S1 as S1;R1 and gives τ1[R1]=ρτ. Regard the second target premise as a derivation under Γ[S1;R1] and apply the second induction hypothesis, adding fvS1(P)ftv(τ1) to the protected set. It factors R1 through S2 as S2;R2 on both ftv(Γ[S1]) and fvS1(P)ftv(τ1), and gives τ2[R2]=ρ. Extend R2 to send the fresh result variable β to τ. Freshness gives βftv(τ1[S2])ftv(τ2) and keeps β out of every inherited protected image, so this extension changes none of the equalities already obtained. Then R2 solves τ1[S2]τ2β. Indeed, protected agreement gives τ1[S2;R2]=τ1[R1]=ρτ, while τ2[R2]=ρ and β[R2]=τ. Hence unification cannot fail by theorem 3.26; let its MGU be U. Its universal property factors R2=U;R3 on the variables of this equation, and lemma 4.45 extends R3 on the inherited protected variables and on ftv(Γ[S1;S2])fvS1;S2(P) outside the equation. At each equality above, agreement on the displayed free-variable set extends from variables to the containing types by lemma 4.6. The two factorization stages and the MGU stage have the following proof state: stagestate after the stagee1Γ; ftv(Γ)P;T=S1;R1, τ1[R1]=ρτe2Γ[S1]; ftv(Γ[S1])fvS1(P)ftv(τ1);R1=S2;R2,τ2[R2]=ρUΓ[S1;S2]; ftv(Γ[S1;S2])fvS1;S2(P);R2=U;R3,β[U;R3]=τ Substituting each residual factor into the preceding row gives the chain T=ftv(Γ)PfirstinductionhypothesisS1;R1=ftv(Γ)PsecondinductionhypothesisS1;S2;R2=ftv(Γ)PMGUfactorizationS1;S2;U;R3. Thus substitution composition gives T=S1;S2;U;R3 on ftv(Γ)P and τ=β[U;R3], exactly the returned pair.

Let. The target premises have the form Γ[T]se1:ρ1,Γ[T],x:σse2:τ,σ=GenΓ[T](ρ1). The first induction hypothesis gives T=S1;R1 on ftv(Γ)P and ρ1=τ1[R1]. Put σ=GenΓ[S1](τ1), as W does. Lemma 3.14 gives σ[R1]GenΓ[S1;R1](τ1[R1])=σ. Thus lemma 3.33 turns the second target premise into one under (Γ[S1],x:σ)[R1]. The transported derivation has the same height by lemma 3.33, hence still has height strictly below the target let derivation. The height induction therefore applies to it. Apply the second induction hypothesis with protected set fvS1(P). It factors R1 through S2 as S2;R2 on the body context and on fvS1(P); it also gives τ=τ2[R2]. Thus T=S1;S2;R2onftv(Γ)P, which is the required factorization of W’s let pair. ◻

Theorem 3.36 — Principal-pair theorem

If Γ[T]se:τ, then W(Γ,e) succeeds. Writing its result as (S,τ), there is a substitution R such that T=ftv(Γ)S;R,τ=τ[R]. Consequently, if a declarative typing of e exists under Γ[T], then W succeeds and its result has this factorization property for a syntax-directed monotype instance of that typing.

Proof of Theorem 3.36 — Principal-pair theorem

Proof. Apply lemma 4.46 with P=; fresh-choice irrelevance transports its convenient run back to the fixed supply. For the final consequence, use theorem 3.34 to obtain a syntax-directed monotype derivation from the declarative one, then apply the first assertion. ◻

Corollary 3.37 — Principal schemes in a closed signature

If ftv(Γ)= and W(Γ,e)=(S,τ), then Γ[S]=Γ and GenΓ(τ) is a principal type scheme for e: it types e, and it is at least as general as every other scheme assignable to e under Γ.

Proof of Corollary 3.37 — Principal schemes in a closed signature

Proof. The context equality is immediate because S has no free context variable to change. Soundness followed by repeated Gen types e at the displayed scheme. Given any other scheme, choose a fresh monotype instance as follows: present it as β¯.ρ with β¯ fresh for GenΓ(τ), and regard its body ρ as the generic instance obtained by replacing the prefix with those fresh variables. Rule Inst types that body; theorem 3.34 converts it to a syntax-directed monotype derivation, and the principal-pair theorem expresses ρ as τ[R]. Every variable of τ is generalized over the closed context. By lemma 3.9, the generic instance of the competing scheme factors through GenΓ(τ), so the latter is at least as general as the competing scheme. ◻

Corollary 4.49 — Principal generalization after an open-context run

Suppose W(Γ,e)=(S,τ). Under the resulting context Γ[S], the scheme GenΓ[S](τ) is principal. More precisely, it types e; and whenever Γ[S]se:τ, there is a substitution R which fixes Γ[S] and satisfies τ=τ[R].

Proof of Corollary 4.49 — Principal generalization after an open-context run

Proof. Soundness and repeated Gen give the displayed typing. Apply theorem 3.36 to the competing typing with T=S. It yields S=ftv(Γ)S;R,τ=τ[R]. The first equality says exactly that Γ[S]=Γ[S;R]=Γ[S][R]. Thus R fixes every free parameter of the resulting context, and the equations τ=τ[R] and R|ftv(Γ[S])=id satisfy the instance condition of lemma 3.9. Hence τ is an instance of GenΓ[S](τ). For a competing scheme, instantiate its quantified prefix by fresh monotypes, apply this argument to the resulting monotype, and generalize the fresh variables again. ◻

For an open Γ, the substitution returned by W may act on ftv(Γ), so the resulting context in this corollary cannot generally be replaced by the original one. The closedness hypothesis of corollary 3.37 gives Γ[S]=Γ.

Exercise 3.6

★★☆ Run W on the previously unsolved term λf.λx.f(f(fx)). Give each fresh variable, all three application equations and their unifiers, the returned monotype, and the final generalized scheme. Explain why the additional application preserves the same principal scheme as twice. A complete calculation is given in appendix B.

A complete inference trace

The batch calculation of section 3.3 collects all equations and solves them once. Algorithm W instead solves and threads substitutions while traversing the program. On a bound definition used polymorphically, take Ptwice:=let t=twice in tt. The bound calculation shows exactly the substitution threading hidden by a batch solve. Give f the fresh type α and x the fresh type β. In the inner application fx, both variable calls return the identity. A fresh γ stands for the result, so W calls unify((αβγ))=U1,U1=[βγ/α]. The inner application therefore returns (U1,γ). For the outer application f(fx), the first recursive call again gives provisional function type α, while the second returns (U1,γ). The application clause must apply that second substitution to the provisional function type before unifying: α[U1]=βγγδ, where δ is the fresh result variable. Left-to-right decomposition and elimination give U2=[δ/β,δ/γ]. Thus the body returns (U1;U2,δ). The two lambda clauses reapply the same composite to their provisional domains, so W(Γ0,twice)=(S0,(δδ)δδ),S0=U1;U2=[(δδ)/α,δ/β,δ/γ]. This is the same principal monotype as the batch answer, up to the name of its surviving parameter. Unlike that answer, the calculation displays the two separate unification calls made by W and the nonidentity substitution U1 threaded from the argument call back into the function type.

Because S0 acts as the identity on Γ0, the let clause generalizes δ and extends the context by t:σt,σt:=δ.(δδ)δδ. Now infer the application tt. Its two variable calls both return the identity substitution, but they consume different fresh instance variables, ϵ and ζ: τf=(ϵϵ)ϵϵ,τa=(ζζ)ζζ. Thus Sf=Sa=id for these two inner calls. Introduce the fresh result variable η. The application clause asks unification to solve (ϵϵ)(ϵϵ)((ζζ)(ζζ))η. Decomposition first equates the domains and then the codomains: ϵϵ(ζζ)(ζζ),ϵϵη. The first equation decomposes once more. Both of its components say ϵζζ; after the first elimination, the second is deleted as reflexive. Substitution in the remaining equation then gives (ζζ)(ζζ)η. Rule U-Orient first moves η to the left; U-Eliminate then removes it. Thus the MGU is U3=[(ζζ)/ϵ,((ζζ)(ζζ))/η], and the body call returns (U3,η[U3]). The complete let clause therefore returns the pair W(Γ0,Ptwice)=(S0;U3,(ζζ)ζζ). In the outer let clause of definition 3.28, its first result is the substitution called S0 in this trace, and its second result is the substitution called U3 here. W itself returns this monotype; it does not generalize the final answer. Since Γ0 is closed and ζ is absent from it, corollary 3.37 gives the principal scheme of the program: ζ.(ζζ)ζζ. There is no circular typing here. The two occurrences of t were opened at different instances before equation (3.4) was solved. The unifier then discovered the particular relation between those instances required by the application.

The evidence reconstructed by inference

An implementation should be able to show what it inferred. The following small core records every instantiation and lambda domain without introducing first-class polymorphism.

Definition 4.50 — HM evidence core and erasure

Evidence terms are d::=xτ¯λ(x:τ).dd1d2let x:σ=d1 in d2. Here xτ¯ is a term constructor for explicit type application; square brackets remain reserved for metalevel substitution. In let x:α¯.τ1=d1 in d2, the prefix α¯ binds in τ1 and in every type occurrence in d1, including lambda annotations, let annotations, and angle-bracket type arguments. The term variable x binds only in d2. Evidence terms are identified up to consistent renaming of either kind of binder.

Define erasure by |xτ¯|=x,|λ(x:τ).d|=λx.|d|,|d1d2|=|d1||d2|,|let x:σ=d1 in d2|=let x=|d1| in |d2|. Type substitution on evidence is capture avoiding: it acts on every explicit type argument and annotation, stops at a let-bound scheme variable, and first alpha-renames a let prefix away from the substitution domain and range. It does not change erasure.

Let Θ be a finite scope of type variables. Write Θτ mono when ftv(τ)Θ, and call a context Γ well formed over Θ when ftv(Γ)Θ.

Definition 4.51 — Evidence checking

For a context Γ well formed over Θ, the judgment Θ;Γevd:τ is generated by the four rules

Γ(x)=α¯.τ0|α¯|=|ρ¯|Θρi mono (1i|ρ¯|)
Θ;Γevxρ¯:τ0[ρ¯/α¯]
Ev-Var
Θτ1 monoΘ;Γ,x:τ1evd:τ2
Θ;Γevλ(x:τ1).d:τ1τ2
Ev-Lam
Θ;Γevd1:τ1τ2Θ;Γevd2:τ1
Θ;Γevd1d2:τ2
Ev-App
α¯=ftv(τ1)ftv(Γ)α¯Θ=Θα¯;Γevd1:τ1Θ;Γ,x:α¯.τ1evd2:τ2
Θ;Γevlet x:α¯.τ1=d1 in d2:τ2
Ev-Let

The set difference in Ev-Let is listed in the fixed variable order of definition 3.12. Its freshness premise is achieved by alpha-renaming the prefix; it prevents a locally generalized variable from escaping into the ambient type scope.

Lemma 4.52 — Evidence substitution

If Θ;Γevd:τ, dom(S)Θ, and ftv(α[S])Θ for every αΘ, then, after freshening let prefixes away from vars(S), Θ;Γ[S]evd[S]:τ[S]and|d[S]|=|d|.

Proof of Lemma 4.52 — Evidence substitution

Proof. Induct on the checking derivation. Rule Ev-Var uses the substitution commutation calculation (3.2). Rules Ev-Lam and Ev-App reapply to the induction hypotheses. In Ev-Let, first alpha-rename α¯ away from the domain and range of S. Substitution then fixes the prefix, acts on the free variables inherited from Γ in both premises, and preserves the displayed set difference. Reapply Ev-Let. Erasure equality follows from the four structural clauses of definition 4.50. ◻

Lemma 4.53 — Evidence over a prescribed scope

If Γse:τ and ftv(Γ)ftv(τ)Θ, then some evidence term d satisfies |d|=eandΘ;Γevd:τ.

Proof of Lemma 4.53 — Evidence over a prescribed scope

Proof. Induct on its height, generalized over Γ,τ,Θ. The variable case uses the instance witness from S-Var in Ev-Var; the lambda case copies the domain from S-Lam into Ev-Lam. At an application, write its premise types as ρτ and ρ. The variables of ρ need not occur in the conclusion. Let G send every variable of ftv(ρ)Θ to Nat and fix all others. Since G fixes Γ and τ, the syntax-directed construction in lemma 3.32 preserves their heights and changes them to Γse1:ρ[G]τ,Γse2:ρ[G], and ftv(ρ[G])Θ. Apply the two induction hypotheses and then Ev-App.

At a let, put α¯=ftv(τ1)ftv(Γ) and alpha-rename this prefix away from Θ. Every free type variable of the definition premise lies in Θα¯, while the quantified prefix is absent from the free variables of the body context. Apply the induction hypothesis to the definition over Θα¯, apply it to the body over Θ, and finish with Ev-Let. Each constructor used here erases to the source constructor in the corresponding premise. ◻

Theorem 4.54 — Checked reconstruction from W

If W(Γ,e)=(S,τ) in the pure HM signature and Θ=ftv(Γ[S])ftv(τ), then there is an evidence term d such that |d|=eandΘ;Γ[S]evd:τ.

Proof of Theorem 4.54 — Checked reconstruction from W

Proof. By theorem 3.35, W’s result has a declarative derivation Γ[S]e:τ. The declarative-to-syntax-directed half of theorem 3.34 gives Γ[S]se:τ. The definition of Θ covers exactly the free variables of this judgment, so lemma 4.53 supplies the required evidence. Its erasure equation is part of that lemma’s conclusion. ◻

For Ptwice, W reconstructs, up to renaming of type variables, let t:δ.(δδ)δδ=λ(f:δδ).λ(x:δ).f(fx) intζζtζ. The first occurrence has type ((ζζ)(ζζ))(ζζ)(ζζ), and the second has its domain type (ζζ)(ζζ). Hence the core application checks without another guess. The angle-bracketed arguments are static records of independent HM instantiations; they are not run-time type applications.

Exercise 3.7

★★☆ Run W on let c=λf.λg.λx.f(gx) in cc. First verify that the definition has principal scheme αβγ.(βγ)(αβ)αγ. Write two fresh instances of that scheme, solve the application equation, and give an explicitly instantiated core term of the grammar above.

Algebraic data and pattern branches

Definition 4.55 — List syntax and typing

Extend the type-constructor signature by the unary constructor List. The generic substitution and unification clauses recurse through this constructor exactly as they recurse through .

The unifier never used a special property of arrows beyond constructor injectivity and disjointness. Algebraic data therefore enters in two separate pieces: a new type constructor for unification, and term rules for constructors and case analysis.

Add the term forms nil,cons e1 e2,case e of {nile0,cons h te1. The last form binds h,t in e1. The schemes nil:α.List(α),cons:α.αList(α)List(α). are a compact reading of the constructor interface, but the constructors are term forms rather than variables looked up in an implicit context. Their exact declarative and syntax-directed rules are

Γnil:List(τ)
ListNil
Γe1:τΓe2:List(τ)
Γcons e1 e2:List(τ)
ListCons
Γsnil:List(τ)
S-ListNil
Γse1:τΓse2:List(τ)
Γscons e1 e2:List(τ)
S-ListCons

Here τ is a monotype; the nil rules may choose any such monotype. Declaratively, case analysis has the rule

Γe:List(τ)Γe0:ρΓ,h:τ,t:List(τ)e1:ρ
Γcase e of {nile0;cons h te1}:ρ
ListCase

Its syntax-directed counterpart is

Γse:List(τ)Γse0:ρΓ,h:τ,t:List(τ)se1:ρ
Γscase e of {nile0;cons h te1}:ρ
S-ListCase

The pattern variables are monomorphic.

Polymorphism remains attached to definitions, not to values extracted from an unknown list.

Definition 4.56 — List constructor clauses of W

The constructor clauses of W are equally explicit. For fresh α, W(Γ,nil)=(id,List(α)). For cons, compute (S1,τ1)=W(Γ,e1),(S2,τ2)=W(Γ[S1],e2),U=unify((τ2List(τ1[S2]))), and return (S1;S2;U,List(τ1[S2;U])).

For a constructor calculation, take Γ=x:α. The declarative rules give

(x:α)Γ
Γx:α
Var
Γnil:List(α)
ListNil
Γcons x nil:List(α)
ListCons

W returns (id,α) for the head and (id,List(β)) for the tail, with fresh β. The cons equation List(β)List(α) decomposes to βα, whose MGU is [α/β]; the constructor clause therefore returns ([α/β],List(α)).

For example, under Γ=d:α,xs:List(α), the body of headOr has the complete declarative derivation

Γxs:List(α)Γd:αΓ,h:α,t:List(α)h:α
Γcase xs of {nild;cons h th}:α
ListCase

Definition 4.57 — List case clause of W

Here is the corresponding W clause, written in the order in which its substitutions are learned. Choose a fresh element type α and compute (S0,τ0)=W(Γ,e),U=unify((τ0List(α))),(S1,ρ0)=W(Γ[S0;U],e0),(S2,ρ1)=W((Γ[S0;U;S1],h:α[U;S1],t:List(α[U;S1])),e1),V=unify((ρ0[S2]ρ1)). It returns (S0;U;S1;S2;V,ρ1[V]).

Definition 4.56, Definition 4.57 give one W clause for each of nil, cons, and case. Every recursive call receives the substitutions returned by all earlier calls in its clause; the final unification in the case clause makes both branches produce one result type.

Theorem 4.58 — Sound and principal list inference

After adding the displayed constructor and case clauses, theorem 3.35 and the protected conclusion of lemma 4.46 hold for the language with lists. Hence the conclusions of theorem 3.36 and corollary 3.37 also hold for that language.

Proof of Theorem 4.58 — Sound and principal list inference

Proof. There are three new term forms. For nil soundness, apply S-ListNil at the fresh element type. For cons soundness, the two recursive induction hypotheses, transported successively by S2 and U, have types e1:τ1[S2;U],e2:List(τ1[S2;U]); apply S-ListCons. In the protected induction, a target nil typing has type List(ξ). Send W’s fresh α to ξ and leave the inherited protected variables as prescribed; this factors the identity answer. For a target cons typing, apply the first recursive induction hypothesis to its head, then the second while protecting the updated head type. The residual factor makes τ2=List(τ1[S2]) true after substitution, so MGU factorization supplies U; associativity gives the returned three-stage factorization and its list result type.

For case soundness, the three recursive induction hypotheses type the scrutinee and both branches under the contexts shown in the W clause. Apply each later substitution to each earlier derivation. Because U solves τ0List(α), the updated scrutinee has type List(α[U;S1;S2;V]). The two pattern declarations have that element type. Because V solves ρ0[S2]ρ1, both branches have type ρ1[V]. Rule S-ListCase gives exactly the returned context and type.

For the protected induction, invert a target case derivation under Γ[T]. For some target types ξ,χ, its premises are Γ[T]se:List(ξ),Γ[T]se0:χ, Γ[T],h:ξ,t:List(ξ)se1:χ. Choose the W element variable α fresh for the target derivation and let T^ agree with T everywhere except that α[T^]=ξ. Freshness gives Γ[T^]=Γ[T] and T^=ftv(Γ)PT. Apply the first induction hypothesis to the scrutinee with protected set P{α}. It returns S0,τ0 and a factor R0 such that T^=ftv(Γ)P{α}S0;R0,τ0[R0]=List(ξ). The scrutinee call receives the supply after α was consumed, so S0 fixes α. Agreement at the added protected variable therefore gives α[R0]=α[S0;R0]=α[T^]=ξ. Together with the last equality, this says that R0 solves τ0List(α). Let U be its MGU. MGU factorization, extended on ftv(Γ[S0])fvS0(P), gives R0=U;RU there. Therefore the nil target premise may be read under Γ[S0;U;RU]. Its induction hypothesis returns S1,ρ0 and R1 with RU=ftv(Γ[S0;U])fvS0;U(P)S1;R1,ρ0[R1]=χ.

Protect additionally the variables of α[U;S1] and ρ0. The preceding factorizations identify the target pattern context with the W pattern context after R1: α[U;S1;R1]=ξ,List(α[U;S1;R1])=List(ξ). The cons-branch induction hypothesis consequently returns S2,ρ1 and R2 such that R1=S2;R2on the inherited and added protected variables,ρ1[R2]=χ. Protected agreement also gives ρ0[S2;R2]=ρ0[R1]=χ; hence R2 solves the final equation ρ0[S2]ρ1. If V is its MGU, extend its factorization to obtain R2=V;R3 on every inherited protected variable. Put P0=P{α},P1=fvS0;U(P),P2=fvS0;U;S1(P)ftv(α[U;S1])ftv(ρ0). These are the protected arguments supplied to the three recursive induction hypotheses. The proof state is stagestate after the stageeΓ; P0;τ0[R0]=List(ξ)Uτ0List(α);R0=U;RU, α[U;RU]=ξe0Γ[S0;U]; P1;RU=S1;R1, ρ0[R1]=χe1Γ[S0;U;S1],h:α[U;S1],t:List(α[U;S1]); P2;R1=S2;R2,ρ1[R2]=χVρ0[S2]ρ1;R2=V;R3, ρ1[V;R3]=χ Associativity now assembles the five stages: T=ftv(Γ)PT^=ftv(Γ)PS0;R0=ftv(Γ)PS0;U;RU=ftv(Γ)PS0;U;S1;R1=ftv(Γ)PS0;U;S1;S2;R2=ftv(Γ)PS0;U;S1;S2;V;R3. Finally ρ1[V;R3]=χ, which is the protected conclusion for the returned list-case pair.

It remains to connect these syntax-directed calculations to declarative principal schemes. Extend the simultaneous induction of theorem 3.34. The nil rules translate one-for-one at the same chosen element monotype. In the cons and case directions, specialize the induction outputs to the monotypes displayed in definition 4.55 and reapply the corresponding rule; in the reverse direction, replace each syntax-directed premise by its declarative induction hypothesis. Outer Inst and Gen are handled by the unchanged scheme-strengthened cases of that induction. Thus declarative and syntax-directed monotype typing remain equivalent after all three list forms are added. Combining this bridge with the nil, cons, and case calculations above proves every cited conclusion. ◻

Example 3.38 — A defaulted head

Consider headOr:=λd.λxs.case xs of {nild;cons h th}. Give d type α and xs provisional type β. The clause above chooses a fresh element type γ, so the scrutinee equation is βList(γ). The nil branch has type α and the cons branch has type γ, so the branch equation is αγ. Renaming the surviving variable for display, an MGU therefore yields headOr:α.αList(α)α. The unused tail t contributes no equation; absence of a use is not an error.

Type inference and exhaustiveness are different questions. In the core syntax above, a list case contains both branches by grammar. A surface language may permit a partial match, but then a separate coverage check must reject a missing constructor before translating the surface case into its internal core term. Typing alone cannot do that job: the partial phrase case xs of cons h th has perfectly consistent type equations and still has no result on nil.

Definition 4.60 — List contractions

The two core reductions are case nil of {}0e0,case (cons v w) of {}0e1[v/h,w/t].

The nil contraction selects e0; the cons contraction substitutes the stored head and tail into e1. Together they select a branch for each closed list constructor, which is the coverage needed by safety.

Exercise 3.8

★★☆ Run the displayed list clause on the body of headOr under d:α,xs:β. Record, in order, the scrutinee equation, the nil-branch result, the pattern context used for the cons branch, the final branch equation, and the composite substitution. Then place the body under its two lambda binders and derive the principal scheme stated in example 3.38. The calculation must use only the list extension proved in theorem 4.58; no recursive-binding construct is part of this exercise.

References and polymorphic let

Extend the type-constructor signature by the unary constructor Ref. Substitution and unification again use their generic constructor clauses.

Pure evaluation cannot make one use of a let-bound value alter what another use will see. A mutable cell can. Add the type constructor Ref, the type Unit, and term forms ref e,!e,e1:=e2,unit, with the expected monomorphic rules

Γunit:Unit
Unit
Γe:τ
Γref e:Ref(τ)
Ref
Γe:Ref(τ)
Γ!e:τ
Deref
Γe1:Ref(τ)Γe2:τ
Γe1:=e2:Unit
Assign

Definition 4.61 — Reference roots for the counterexample

A run-time state is a pair (μ,e), where the finite store μ maps locations to closed values. Call-by-value compatibility closes the following root steps: (μ,ref v)(μ[v],)(dom(μ)),(μ,!)(μ,μ()),(μ,:=v)(μ[v],unit). For the program below, the required context frames are refE, E:=e, v:=E, !E, and let x=E in e. The complete value and context grammars are given with store typing in definition 4.68.

Naively retaining unrestricted let-generalization makes the following closed program typable: Pref:=let r=ref(λx.x) inlet u=r:=(λx.succ x) in(!r)true. The first definition is assigned a provisional type Ref(αα). Naive generalization gives r:α.Ref(αα). The assignment instantiates this scheme at Nat, while the final dereferencing instantiates it at Bool. Thus the program receives type Bool.

At run time, however, ref(λx.x) allocates one location . The assignment overwrites that same location with λx.succ x. The final line therefore reduces to succ true, which is stuck. The false step was not unification; it was treating one allocated cell as though each use created a fresh instance. This diagnostic uses the conventional run-time clauses for succ and true. It is not an instance of the constant-free pure safety theorem above; that theorem applies only after a chosen primitive signature supplies typing and dynamics for its constants.

Definition 4.62 — Reference and cumulative signatures

Write Lref for pure HM extended by Unit, Ref, locations, unit, allocation, dereference, assignment, and the rules in this section. Write Llist+ref for Lref further extended by the complete list delta of section 3.8: the ListNil, ListCons, and ListCase rules, their three syntax-directed counterparts, the three W clauses, and the two constructor contractions. The value restriction below replaces the let rule in either signature. Results stated for both signatures use the reference cases verbatim; the cumulative signature additionally uses the list cases of theorem 4.58.

Definition 3.39 — Conservative value restriction

A generalizable form is a variable, constant, abstraction, or fully applied data constructor whose arguments are generalizable forms. This is a static grammar, distinct from the closed run-time values below. In either signature of definition 4.62, retain Var, Inst, Gen, Lam, and App from definition 3.16, delete its unrestricted Let, and replace that rule

Γv:τ1Γ,x:GenΓ(τ1)e2:τ2
Γlet x=v in e2:τ2
Let-Gen
Γe1:τ1Γ,x:τ1e2:τ2
Γlet x=e1 in e2:τ2
Let-Mono

In Let-Gen, the metavariable v ranges over generalizable forms. The first rule applies only when its definition is such a form; the second applies to every expression. An implementation normally chooses Let-Gen when permitted and otherwise chooses Let-Mono. Although Gen remains available, it cannot reintroduce polymorphism at a nongeneralizable binding: Let-Mono records only the monotype τ1 in the body context, so any temporary generalization of its first premise must be instantiated back to that monotype before the rule applies.

In Llist+ref, retain the three declarative, syntax-directed, and W list clauses named in definition 4.62. The corresponding reference additions to W are exact. The generalizable-form let clause is the old let clause. For a nongeneralizable definition, after (S1,τ1)=W(Γ,e1) call the body under Γ[S1],x:τ1; if it returns (S2,τ2), return (S1;S2,τ2). For the four new term forms: W(Γ,unit)=(id,Unit),W(Γ,ref e)=let (S,τ)=W(Γ,e) in (S,Ref(τ)),W(Γ,!e)=let (S,τ)=W(Γ,e),U=unify((τRef(α))) in (S;U,α[U]),W(Γ,e1:=e2)=let (S1,τ1)=W(Γ,e1),(S2,τ2)=W(Γ[S1],e2),U=unify((τ1[S2]Ref(τ2)))in (S1;S2;U,Unit), where α is fresh. The substitution order is the same as in application: the right operand of assignment sees everything learned from the left one.

Extend the syntax-directed judgment of definition 3.31 by the following rules:

Γsv:τ1Γ,x:GenΓ(τ1)se2:τ2
Γslet x=v in e2:τ2
S-Let-Gen
Γse1:τ1Γ,x:τ1se2:τ2
Γslet x=e1 in e2:τ2
S-Let-Mono

where the first rule requires a generalizable form and the second requires a nongeneralizable expression. The four store-form rules are

Γsunit:Unit
S-Unit
Γse:τ
Γsref e:Ref(τ)
S-Ref
Γse:Ref(τ)
Γs!e:τ
S-Deref
Γse1:Ref(τ)Γse2:τ
Γse1:=e2:Unit
S-Assign

Lemma 4.64 — Context generality for value-restricted typing

Suppose Γ and Γ have the same declared term variables and Γ(x)Γ(x) for each x. Fix either signature in definition 4.62. If its syntax-directed judgment derives Γse:τ, then it also derives Γse:τ.

Proof of Lemma 4.64 — Context generality for value-restricted typing

Proof. Extend the induction of lemma 3.33. Unit has no premises, and reference, dereference, and assignment reapply their rules to the induction hypotheses. In S-Let-Mono, the bound declaration is the same monotype in the two body contexts. In S-Let-Gen, more-general outer declarations have no additional free type variables, so ftv(Γ)ftv(Γ). Therefore GenΓ(τ1)GenΓ(τ1) by lemma 3.13; use this relation for the bound declaration when applying the body induction hypothesis. For Llist+ref, nil has no premises, while cons and case reapply their rules to the induction hypotheses. These are all rules added to either syntax-directed judgment. ◻

Lemma 4.65 — Value-restricted syntax equivalence

For a monotype τ and either signature of definition 4.62, value-restricted declarative typing derives Γe:τ if and only if the syntax-directed judgment for the same signature derives Γse:τ.

Proof of Lemma 4.65 — Value-restricted syntax equivalence

Proof. For declarative-to-syntax-directed typing, induct on the declarative derivation with the same strengthened scheme conclusion as theorem 3.34. A Var leaf chooses a fresh monotype instance for S-Var; Inst composes the instance witness; Gen enlarges the generalized prefix; and Lam and App specialize the induction outputs to their displayed monotypes before reapplying S-Lam or S-App.

There are three declarative let cases after inspecting the right-hand side. A Let-Gen derivation specializes its two induction outputs to the displayed monotypes and rebuilds S-Let-Gen. A Let-Mono derivation whose right-hand side is nongeneralizable rebuilds S-Let-Mono. If the right-hand side of Let-Mono is instead a generalizable form, its body induction output initially has context Γ,x:τ1. Since GenΓ(τ1)τ1, lemma 4.64 transports that body derivation to Γ,x:GenΓ(τ1), and S-Let-Gen rebuilds the conclusion. Thus the deterministic syntax-directed choice represents both declarative rules even when Let-Mono was used at a generalizable right-hand side. Unit, reference, dereference, and assignment each have one term-forming rule, so their induction cases specialize the premise outputs to the monotypes displayed above and reapply that rule. Conversely, induct on the syntax-directed derivation: replace S-Var by Var followed by Inst, insert repeated Gen before Let-Gen, and reuse the declarative lambda, application, monomorphic-let, and four store-form rules. In Llist+ref, the nil, cons, and case rules translate one-for-one in both directions. These cases exhaust both extended systems. ◻

Lemma 4.66 — Protected induction for the value-restricted signature

Let P be finite and suppose the complete syntax-directed reference signature Lref, or the cumulative signature Llist+ref, derives Γ[T]se:τ. Use a legal supply disjoint from Pvars(T) and from the variables of that finite derivation. The extended W succeeds; if it returns (S,τ), some R satisfies T=ftv(Γ)PS;R,τ=τ[R].

Proof of Lemma 4.66 — Protected induction for the value-restricted signature

Proof. Induct on the syntax-directed derivation, generalized over Γ,T,P. The pure cases are exactly lemma 4.46. For Llist+ref, the three list cases are the constructor and five-stage case calculations in theorem 4.58. It remains to calculate the new let and store forms.

S-Let-Mono. Inversion gives target premises Γ[T]se1:ρ1,Γ[T],x:ρ1se2:τ. The first induction hypothesis returns (S1,τ1) and R1 with T=ftv(Γ)PS1;R1,τ1[R1]=ρ1. Thus the body premise is a derivation under (Γ[S1],x:τ1)[R1]. Apply the second induction hypothesis while protecting fvS1(P)ftv(τ1). It returns (S2,τ2) and R2 such that R1=ftv(Γ[S1])fvS1(P)ftv(τ1)S2;R2,τ2[R2]=τ. Extensionality and associativity now give T=ftv(Γ)PS1;S2;R2, which is the returned factorization. The S-Let-Gen case is the pure let case because W selects it exactly when the right-hand side belongs to the generalizable-form grammar.

S-Assign. Inversion supplies a target type ρ with Γ[T]se1:Ref(ρ),Γ[T]se2:ρ. The first induction hypothesis gives T=ftv(Γ)PS1;R1,τ1[R1]=Ref(ρ). Read the second target premise under Γ[S1][R1], protect fvS1(P)ftv(τ1), and apply the second induction hypothesis: R1=S2;R2on that protected set,τ2[R2]=ρ. Consequently τ1[S2;R2]=τ1[R1]=Ref(ρ)=Ref(τ2[R2]), so R2 solves W’s equation τ1[S2]Ref(τ2). If U is its MGU, factor R2=U;R3 on the equation variables and extend R3 on the inherited protected set by lemma 4.45. Put P1=fvS1(P)ftv(τ1). The proof state is stagestate after the stagee1Γ; ftv(Γ)P;T=S1;R1, τ1[R1]=Ref(ρ)e2Γ[S1]; ftv(Γ[S1])P1;R1=S2;R2, τ2[R2]=ρUΓ[S1;S2]; ftv(Γ[S1;S2])fvS1;S2(P);R2=U;R3τ1[S2;U;R3]=Ref(τ2[U;R3]) Composing the three residual factors gives T=ftv(Γ)PS1;S2;U;R3,Unit=Unit[R3].

S-Deref. Inversion gives Γ[T]se0:Ref(ρ). The one recursive induction hypothesis gives T=S;R0 on the inherited protected set and τ[R0]=Ref(ρ). Send W’s fresh α to ρ; then R0 solves τRef(α). MGU factorization gives R0=U;R, extended on the inherited protected variables, and α[U;R]=ρ. This is precisely the assignment calculation with its second recursive branch and the outer Ref on τ2 removed.

S-Unit and S-Ref. Unit returns (id,Unit); take R=T and preserve agreement on P. For reference construction, the recursive induction hypothesis gives τ[R]=ρ, and structural substitution gives Ref(τ)[R]=Ref(ρ). These cases exhaust the added forms and establish the strengthened claim. ◻

Theorem 4.67 — Principality with the conservative value restriction

For each syntax-directed signature Lref and Llist+ref, choose Let-Gen exactly for generalizable forms and Let-Mono otherwise. The corresponding extended algorithm W is sound and returns a principal pair whenever a typing exists in that same signature.

Proof of Theorem 4.67 — Principality with the conservative value restriction

Proof. For soundness, extend the induction of theorem 3.35. Unit uses S-Unit; reference construction reapplies S-Ref to its recursive typing. In dereference, type substitution by U changes the recursive result from τ to Ref(α[U]), so S-Deref returns α[U]. For assignment, the two induction hypotheses, transported successively by S2 and U, have types e1:τ1[S2;U]=Ref(τ2[U]),e2:τ2[U]; S-Assign returns Unit. The generalized and monomorphic let clauses rebuild S-Let-Gen and S-Let-Mono, respectively. For Llist+ref, the three list cases are theorem 4.58; they are absent from Lref.

For completeness and principality, first use lemma 4.65 to obtain a syntax-directed target. That equivalence normalizes a declarative Let-Mono with a generalizable right-hand side to S-Let-Gen, so the target and W select the same branch. Apply lemma 4.66 with P=, then use the same final context-generality argument as in theorem 3.36. The returned factorization is therefore principal among all value-restricted typings. ◻

This conservative restriction is smaller than the nonexpansive classes used by mature ML implementations. A nonexpansive expression is one whose evaluation is certified not to allocate fresh mutable state or perform another effect before returning; generalizable forms constitute a simple conservative subclass. The smaller rule exposes the proof idea without an effect analysis. The offending expression ref(λx.x) is not a generalizable form, so r receives the single monotype Ref(αα). The assignment forces α=Nat; the final use then asks for α=Bool. Declaratively there is no monotype satisfying both uses; algorithmically W exposes the clash NatBool and rejects it by constructor disjointness.

Definition 4.68 — Run-time typing over a store

The run-time safety result uses exactly the cumulative signature Llist+ref, the states and three reference roots of definition 4.61, and the two list contractions of section 3.8. Its closed values are v::=λx.eunitnilcons v v. Thus variables may be generalizable forms but are not closed run-time values. The compatible closure uses the complete call-by-value context grammar B::={nile0;cons h te1},E::=[]EevElet x=E in eref E!EE:=ev:=Econs E econs v Ecase E of B. For every root step (μ,r)(μ,r), compatibility gives (μ,Er)(μ,Er). The hole is the base context; the displayed alternatives determine which typing rule is rebuilt around an induction hypothesis. The letter E distinguishes evaluation contexts from the equation lists E used by unification. A store typing Σ maps each location to one monotype, and ftv(Σ) is the union of their free variables. Extend typing by the run-time rule dom(Σ)ΓΣ:Ref(Σ()), and write μ:Σ when the domains agree and Σμ():Σ() for every stored location. During reduction, list α¯=ftv(τ)(ftv(Γ)ftv(Σ)) in the fixed variable order of definition 3.12 and put GenΓ,Σ(τ):=α¯.τ.

The run-time judgment lifts the pure rules to ΓΣe:σ, but replaces Gen and the generalizable-form let rule by

ΓΣe:σαftv(Γ)ftv(Σ)
ΓΣe:α.σ
GenΣ
ΓΣv:τ1Γ,x:GenΓ,Σ(τ1)Σe2:τ2
ΓΣlet x=v in e2:τ2
Let-GenΣ

Rule Let-Mono and every nongeneralizing rule of Llist+ref are lifted unchanged. At run time, Let-GenΣ ranges over every closed value in the displayed grammar, including a location. A location cannot acquire polymorphic store contents: if Σ()=τ, then ftv(τ)ftv(Σ), so GenΓ,Σ(Ref(τ)) has no quantifier contributed by the cell type. Thus every run-time generalization, whether written as a scheme judgment or used at a generalizable-form binding, is relative to both Γ and Σ: variables fixed by a store cell are not generalized merely because the location is not a source-level variable. When Σ=, GenΓ,Σ(τ)=GenΓ(τ), and the side condition of GenΣ becomes the side condition of Gen. The let grammars remain different: compile time admits the generalizable forms of definition 3.39, whereas run time admits the closed values displayed above.

Write Γs,Σe:τ for the corresponding run-time syntax-directed judgment. It lifts every nongeneralizing syntax-directed rule of Llist+ref, and adds the two rules

dom(Σ)
Γs,Σ:Ref(Σ())
S-Loc
Γs,Σv:τ1Γ,x:GenΓ,Σ(τ1)s,Σe2:τ2
Γs,Σlet x=v in e2:τ2
S-Let-GenΣ

Here v ranges over the closed run-time values displayed above. The judgment has no separate Inst or GenΣ rule; its variable rule chooses a monotype instance directly. Thus every conclusion of this judgment is a monotype.

Lemma 4.69 — Run-time context generality

Suppose Γ and Γ declare the same term variables and Γ(x)Γ(x) for each x. If ΓΣe:σ, then ΓΣe:σ.

Proof of Lemma 4.69 — Run-time context generality

Proof. Induct on the run-time declarative derivation. Variable and instantiation use transitivity of ; every nongeneralizing rule reapplies to its induction hypotheses. More-general declarations have no additional free type variables, so ftv(Γ)ftv(Γ). Hence a GenΣ side condition valid for Γ is valid for Γ. At Let-GenΣ, lemma 3.13 gives GenΓ,Σ(τ1)GenΓ,Σ(τ1), where the fixed store variables are included in both forbidden sets. Use this relation for the bound declaration in the body induction hypothesis. The monomorphic let and location cases add no side condition. ◻

Lemma 4.70 — Run-time syntax equivalence

For every monotype τ, ΓΣe:τΓs,Σe:τ.

Proof of Lemma 4.70 — Run-time syntax equivalence

Proof. Use the simultaneous induction of theorem 3.34, with ftv(Γ) replaced by ftv(Γ)ftv(Σ) in every generalization case. A declarative Inst composes its instance witness with the one returned by the induction hypothesis; GenΣ enlarges only the outer prefix. Each nongeneralizing term form translates one-for-one. For Let-GenΣ, the strengthened scheme conclusion identifies the body declaration with GenΓ,Σ(τ1). A Let-Mono derivation with a nonvalue definition uses the unchanged monotype declaration. If its definition is a closed value, then GenΓ,Σ(τ1)τ1, so lemma 4.69 transports the body premise before S-Let-GenΣ is rebuilt. The location rule translates to S-Loc, and the list and reference rules are the cases already established in lemma 4.65. Conversely, replace the syntax-directed variable rule by Var followed by Inst, translate S-Let-GenΣ directly to Let-GenΣ, and reuse each displayed declarative term-forming rule. These cases exhaust both run-time judgments. ◻

Lemma 4.71 — Run-time canonical forms

For a closed run-time value v:

  1. if Σv:τ1τ2, then v is an abstraction;

  2. if Σv:Ref(τ), then v is a location;

  3. if Σv:List(τ), then v is nil or cons v1 v2.

Proof of Lemma 4.71 — Run-time canonical forms

Proof. Apply lemma 4.70. The resulting syntax-directed derivation for an arrow can end only in the abstraction rule, one for a reference only in the location rule, and one for a list only in nil or cons; constructor disjointness excludes the remaining value forms. ◻

Lemma 4.72 — Weakening by fresh store entries

If ΓΣe:σ and ΣΣ, then ΓΣe:σ.

Proof of Lemma 4.72 — Weakening by fresh store entries

Proof. Induct on the run-time typing derivation. The location and nongeneralizing cases are immediate because every old location retains its monotype. Before a GenΣ case, alpha-rename its quantified variable throughout the premise and conclusion away from ftv(Σ); the renamed side condition then holds for Σ. Before a Let-GenΣ case, alpha-rename the whole prefix of GenΓ,Σ(τ1), together with the corresponding free variables in the value-typing premise, away from ftv(Σ). Those variables were absent from Γ and Σ, so type-variable renaming preserves both premises. With the fresh presentation, GenΓ,Σ(τ1) is alpha-identical to the old scheme, and the two induction hypotheses reconstruct the rule. These are the only cases in which enlarging the store typing changes a side condition. ◻

Lemma 4.73 — Store-indexed value substitution

If ΓΣv:σ, Γ,x:σΣe:σ, and v is a run-time value, then ΓΣe[v/x]:σ.

Proof of Lemma 4.73 — Store-indexed value substitution

Proof. Freshen every term binder away from x and the free variables of v, then induct on the second derivation. A variable leaf for x is the first premise; every other variable keeps its declaration. The location, unit, and nil rules contain no term premise. Lambda, application, reference, dereference, assignment, cons, and list case reapply their rule to the induction hypotheses; in the case rule, freshening the two pattern binders prevents capture.

Rules Inst and GenΣ reapply directly. The latter’s side condition is unchanged because substitution changes terms but neither Γ nor Σ. In Let-Mono, apply the induction hypothesis to the definition and body and rebuild the rule. In Let-GenΣ, its definition is a run-time value. Substitution of the run-time value v for a variable in another run-time value again yields a run-time value: abstractions use capture avoidance, unit and nil are unchanged, cons recurses on its two value arguments, and a location remains a location. The last case is permitted by the explicit run-time clause above and is not classified as a source constant or data constructor.

It remains to transport the body declaration. Freshen the inner binder to y. The two induction hypotheses give ΓΣv1[v/x]:τ1,Γ,y:GenΓ,x:σ,Σ(τ1)Σe2[v/x]:τ2. Removing x:σ can only enlarge the generalized prefix, so GenΓ,Σ(τ1)GenΓ,x:σ,Σ(τ1). Apply lemma 4.69 to the second displayed derivation and then rebuild Let-GenΣ with the first. These cases cover the complete run-time judgment, including both let rules and every list/reference form. ◻

Theorem 3.40 — Safety with the value restriction

If μ:Σ and Σe:τ for a closed run-time expression typed by definition 3.39, definition 4.68, then either e is a value or there are ΣΣ, μ:Σ, and e such that (μ,e)(μ,e)andΣe:τ. Here the subscript Σ records the location rule and the free type variables of the store in generalization.

Proof of Theorem 3.40 — Safety with the value restriction

Proof. Progress. Apply lemma 4.70 to the closed monotype typing, then induct on the resulting syntax-directed derivation. Lambda is a value. Application first uses the induction hypothesis on its next call-by-value child; an arrow-typed value is an abstraction by lemma 4.71, so the remaining application is a beta redex. A let with a nonvalue definition steps that definition, and either let rule contracts once its definition is a value. List constructors and their case eliminator use the two constructor contractions of section 3.8; unit is a value. For the store forms, by lemma 4.71, a closed value of reference type has the form . From μ:Σ and Σ()=τ we obtain Σμ():τ, so dereference steps to a term of the required type. Assignment uses the same lookup and replaces the cell by a value already typed at τ. Allocation extends both μ and Σ by one matching entry.

Preservation. Use lemma 4.70 to put the source monotype typing in syntax-directed form, then induct on the store-step derivation. This supplies the premises of the unique rule for each outer term form. At a root step, allocation uses lemma 4.72 for every old cell and extends Σ by the inferred monotype of the allocated value; the location rule types the fresh result. Dereferencing uses the defining clause of μ:Σ, and assignment preserves that clause because the new value has exactly Σ(). At a monomorphic let redex, lemma 4.73 applies. At a generalized let redex, its premise gives v:τ1, and the rule side condition permits repeated GenΣ, now excluding ftv(Γ)ftv(Σ). Hence ΓΣv:GenΓ,Σ(τ1), so the same substitution lemma discharges the let binder. Equivalently, the type-substitution lemma may instantiate this scheme while fixing both the context and the store typing. No allocation or assignment occurs inside the value. Because the generalized variables exclude ftv(Σ), a location’s stored monotype cannot be duplicated at incompatible instances. At beta and either let contraction, lemma 4.73 applies to the displayed run-time premises; its induction never changes Σ, so the fixed store-typing premise is preserved.

For the nil case contraction, inversion of S-ListCase gives the nil branch directly at the result type. For the cons contraction, inversion gives ΓΣv:ρ,ΓΣw:List(ρ),Γ,h:ρ,t:List(ρ)Σe1:τ. Freshen h,t, weaken the two value typings under the other pattern declaration, and apply lemma 4.73 first to h and then to t. The result is ΓΣe1[v/h,w/t]:τ, exactly the contractum typing.

For a compatible step, induct on E. The child induction hypothesis may enlarge Σ to Σ. In a unary frame ref[] or ![], reapply its rule directly. In every frame with a fixed sibling, first transport that sibling from Σ to Σ by lemma 4.72. For example, in []:=e2, the child retains type Ref(ρ) under Σ; weakening gives Σe2:ρ, so S-Assign reconstructs Unit. In v1:=[], weaken the fixed premise v1:Ref(ρ) instead. Application and cons use the same two calculations for their left and right frames.

For let x=[] in e2, first weaken the body premise under x:τ1. If the reduct in the hole is not a value, rebuild S-Let-Mono. If it is a value, use GenΓ,Σ(τ1)τ1 and lemma 4.69 to transport the weakened body premise before rebuilding S-Let-GenΣ. For the case frame, weaken both fixed branch premises; the two pattern declarations retain their monotypes. Store weakening also transports the nonstepping argument in Ee, the already evaluated function in vE, and the corresponding cons arguments. These frames exhaust the context grammar in definition 4.68, so allocation inside any frame preserves the whole expression typing under the enlarged store typing. ◻

Exercise 3.9

★★☆ Give the complete failed W calculation for Pref under definition 3.39. Identify the equation that first fixes the cell’s argument type to Nat and the later equation that clashes with Bool. Then verify that let id=λx.x in still generalizes. A complete calculation is given in appendix B.

Where rank-one inference stops

HM quantifiers occur only at the outer edge of a let-bound scheme. This restriction is visible in three increasingly strong examples.

First, a lambda parameter cannot be assumed polymorphic. The term λf.let n=fzero in ftrue would need two instances of f, one with domain Nat and one with domain Bool. Rule Lam gives f one monotype, and W therefore produces incompatible equations for that single arrow domain. If a language allowed the rank-two annotation f:α.αα, the body would be typable; HM deliberately cannot express that parameter type. Here rank two means that a quantified type occurs to the left of one arrow: the universal in the displayed parameter type is nested one level inside the type of the enclosing function. HM schemes have universals only at the outside and are therefore rank one.

Second, polymorphic values are not first-class data. HM can bind the identity to a scheme and use it, but it cannot put the scheme itself to the left of an arrow. Thus a function of intended type (α.αα)Nat is outside the grammar of definition 3.6, even when its body merely applies the argument at Nat.

Third, HM instantiation is predicative: quantified variables are replaced by monotypes, and a monotype contains no . An explicitly typed calculus may instead instantiate the polymorphic identity at its own polymorphic type, (Λα.λ(x:α).x)α.αα(Λα.λ(x:α).x). Read Λα.d here as a static type abstraction binding the type variable α in d, and dτ as explicit type application, which substitutes τ for that bound variable and erases before ordinary evaluation. This Λ binds a type variable, not the individual-variable proof notation used in first-order logic. This is an impredicative use. It requires a calculus with explicit type abstractions and applications. Abstracting an operation whose inputs are themselves types additionally requires kinds, classifiers that separate well-formed type-level functions from term-level functions. Neither explicit impredicative instantiation nor type-level abstraction belongs to HM. The gain would be expressiveness. The cost is that the inference problem solved by W would no longer describe the whole language: annotations or a stronger, more limited inference discipline become necessary.

Exercise 3.10

★☆☆ For each of the three examples above, point to the precise HM grammar or typing rule that blocks it. For the first two, mark where an explicit type abstraction or type application would have to occur in an extended syntax. Do not claim that the resulting annotations are inferred; they are part of the input.

Principal inference and its boundary

For input (Γ,e), Algorithm W traverses e, generates fresh type unknowns, solves the resulting equations by most-general unification, and generalizes exactly the variables outside ftv(Γ). Theorem 3.35 types the returned pair, while theorem 3.36, corollary 3.37 show that every other typing factors through its substitution, and the returned generalization is at least as general as every scheme assignable to e under the resulting context. In the pure calculus, distinct uses of a let-bound variable instantiate its scheme independently. With references, allocation makes those uses share one cell, so theorem 4.67 generalizes only the stated generalizable forms, a strict conservative subclass of nonexpansive expressions. Rank-one schemes still exclude polymorphic arguments and impredicative instantiation.

Bibliographic notes.

The presentation and notation are adapted to the proofs above, but the principal result is the theorem of Damas and Milner: they write σ>σ with the more general scheme on the left [DM82]. The relation keeps that orientation while reserving > for arithmetic. Their algorithm is stated in Section 6, soundness is Proposition 4, and completeness is the theorem of Section 7 [DM82]. Damas gives the fuller proof as Theorems 2 and 3 of Chapter II, Sections 4–5, of his thesis [Dam85]. The imperative counterexample and conservative value restriction belong to the subsequent development of imperative polymorphism; Wright proves type soundness for the corresponding discipline [Wri95].

Suggested first pass.

Begin with exercise 3.11, exercise 3.12, exercise 3.13. These three problems rehearse the let clause, mutual factorization, and the operational reason for the value restriction. Continue with the two boundary problems, and leave the three-star implementation as the final synthesis.

Exercise 3.11

★★☆ Work under Γ0,f:φ, where φ is an unquantified monotype variable. Infer W’s principal pair for let k=λx.f in kzero. State separately the variables inherited from the type of f and those created while typing k. Verify directly that generalizing αφ at the let quantifies α but not φ. This is the smallest useful test of the let clause in definition 3.28.

Exercise 3.12

★★☆ Run unification on (αβγ, βNat, γδ) in two different legal equation orders. The printed substitutions need not be identical. Prove that each factors through the other on the four problem variables, and conclude from principality that they describe the same solutions.

Exercise 3.13

★★☆ Type, under the value restriction, let r=ref(λx.x) in let s=r in s. Which let may generalize? Explain why r and s still denote one cell and must share one monomorphic reference type.

Exercise 3.14

★☆☆ Show that Nat and α.Nat have exactly the same instances although they are not alpha-equivalent. Deduce that principal schemes are unique up to mutual generality, not necessarily as literal scheme syntax. What simple convention removes the vacuous-prefix example?

Exercise 3.15

★★☆ The expression (λf.f)(λx.x) is not a generalizable form but reduces without effects to the identity. Analyze let i=(λf.f)(λx.x) in let n=izero in itrue. Locate the rejection under Let-Mono. Then explain why an effect analysis could accept this program without accepting Pref: which absence of allocation or mutation must it certify?

Exercise 4.16

★★★ Practical project.hm-inferencer Implement Algorithm W and the first-order unifier exactly as printed in definition 3.28, definition 3.22 for the pure variable, lambda, application, and nonrecursive-let language. Initialize the fresh supply above every type variable in the input context, and implement the capture-avoiding scheme action of definition 3.6. For every named call unify(E)=U, check that each equation of E[U] is reflexive, as guaranteed by theorem 3.26. Compare each successful W result with a named expected scheme by checking mutual generality, and print the inferred scheme in the fixed variable order of definition 3.12. The acceptance corpus must infer α.(αα)αα for twice, infer the trace result of section 3.7 up to alpha-renaming, and reject λx.xx by the occurs check. It must also run the open context f:α0λx.f, returning α1.α1α0, and the range-collision scheme substitution (α1.α0)[α1/α0] without capture. For each success, record the inferred scheme and the two generality-test booleans. For the named unifier replay, record the original E, the returned U, and that every member of E[U] is reflexive. For a failure, record the selected failure clause and its head equation. Finally report the initial and final fresh counters and the quantified prefixes before and after the range-collision action. These fields make the output independently replayable rather than a list of unsubstantiated PASS lines. The finite runs illustrate theorem 3.26, theorem 3.35, theorem 3.36; they do not prove those theorems.

Search the book

Type to search the local edition.