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, 𝜆𝑥:𝖭𝖺𝗍.𝑥:𝖭𝖺𝗍→𝖭𝖺𝗍,𝜆𝑥:𝖭𝖺𝗍→𝖭𝖺𝗍.𝑥:(𝖭𝖺𝗍→𝖭𝖺𝗍)→(𝖭𝖺𝗍→𝖭𝖺𝗍), 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𝑥 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.
One unannotated definition of the identity must serve every use, each use at the type that its position demands.
The programmer writes no types at all; the types are computed.
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 𝜆𝑥.𝑥 once at the two types displayed above is parametric polymorphism, or simply polymorphism in this chapter: one definition is reused at several types.
Fix the countably infinite supply of variables 𝑥,𝑦,𝑧,… of section 2.1. Raw terms of the let-language are given by 𝑒::=𝑥∣𝑒1𝑒2∣𝜆𝑥.𝑒∣𝗅𝖾𝗍𝑥=𝑒1𝗂𝗇𝑒2, where 𝜆𝑥.𝑒 binds 𝑥 in 𝑒, and 𝗅𝖾𝗍𝑥=𝑒1𝗂𝗇𝑒2 binds 𝑥 in 𝑒2 only.
Two differences from section 2.1 matter. Abstractions carry no domain: the discipline must recover it. And a new binding form 𝗅𝖾𝗍 appears. For evaluation, 𝗅𝖾𝗍𝑥=𝑒1𝗂𝗇𝑒2 behaves as the application (𝜆𝑥.𝑒2)𝑒1; for typing it will behave differently, and that difference is the entire subject of the chapter.
Use the binding construction of section 2.1 for this grammar. Thus a term is an alpha-equivalence class, substitution 𝑒[𝑎/𝑥] is capture avoiding, and bound variables may be freshened away from any finite set. The new 𝗅𝖾𝗍 clause repeats the abstraction clause, with its binding confined to the body 𝑒2. Concretely, fv(𝗅𝖾𝗍𝑥=𝑒1𝗂𝗇𝑒2)=fv(𝑒1)∪(fv(𝑒2)∖{𝑥}), and substitution descends into both 𝑒1 and 𝑒2, except that it stops under the body when the let binder is the variable being replaced. Before descending under either 𝜆 or 𝗅𝖾𝗍, 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.
Programs are typed under an initial context of constants. Constants are ordinary variables that the programs themselves never bind. The arithmetic context is Γ0:=𝗓𝖾𝗋𝗈:𝖭𝖺𝗍,𝗌𝗎𝖼𝖼:𝖭𝖺𝗍→𝖭𝖺𝗍,𝗍𝗋𝗎𝖾:𝖡𝗈𝗈𝗅,𝖿𝖺𝗅𝗌𝖾:𝖡𝗈𝗈𝗅. 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 𝖭𝖺𝗍 and at 𝖭𝖺𝗍→𝖭𝖺𝗍. Write 𝑃𝗂𝖽:=𝗅𝖾𝗍𝗂𝖽=𝜆𝑥.𝑥𝗂𝗇(𝗂𝖽𝗌𝗎𝖼𝖼)(𝗂𝖽𝗓𝖾𝗋𝗈). One definition of the identity; two uses. The outer use applies 𝗂𝖽 to 𝗌𝗎𝖼𝖼, so there it must have type (𝖭𝖺𝗍→𝖭𝖺𝗍)→(𝖭𝖺𝗍→𝖭𝖺𝗍); the inner use applies it to 𝗓𝖾𝗋𝗈, so there it must have type 𝖭𝖺𝗍→𝖭𝖺𝗍. A single simple type cannot do both jobs. The discipline we construct assigns 𝗂𝖽 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 𝖭𝖺𝗍→𝖭𝖺𝗍; the inner use replaces both by 𝖭𝖺𝗍.
Monotypes, type schemes, and the declarative system
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𝐹, each with an arity. Monotypes, the types containing no scheme quantifier, are inductively defined by 𝜏::=𝛼∣𝐹(𝜏1,…,𝜏𝑛)(𝐹ofarity𝑛). The ambient signature consists of 𝖭𝖺𝗍 and 𝖡𝗈𝗈𝗅 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.
A substitution is a function 𝑆 from type variables to monotypes such that the domaindom(𝑆):={𝛼∣𝛼[𝑆]≠𝛼} is finite. We write [𝜏1/𝛼1,…,𝜏𝑘/𝛼𝑘] for the substitution sending each 𝛼𝑖 to 𝜏𝑖 (the 𝛼𝑖 distinct) and every other variable to itself, and id for the identity substitution. Application to a monotype is structural: 𝐹(𝜏1,…,𝜏𝑛)[𝑆]:=𝐹(𝜏1[𝑆],…,𝜏𝑛[𝑆]). Set ftv(𝑆):=⋃𝛼∈dom(𝑆)ftv(𝛼[𝑆]) and vars(𝑆):=dom(𝑆)∪ftv(𝑆). Composition is written 𝑆;𝑇 and is defined by 𝜏[𝑆;𝑇]:=(𝜏[𝑆])[𝑇]. It is again a substitution, and is written in diagrammatic order: first apply 𝑆, then 𝑇. This is the reverse of the usual function notation 𝑇∘𝑆. For example, if 𝑆=[𝛽/𝛼] and 𝑇=[𝖭𝖺𝗍/𝛽], then
𝛼[𝑆;𝑇]=(𝛼[𝑆])[𝑇]=𝛽[𝑇]=𝖭𝖺𝗍.
For every monotype 𝜏, unfolding the definition twice gives 𝜏[(𝑆;𝑇);𝑅]=((𝜏[𝑆])[𝑇])[𝑅]=𝜏[𝑆;(𝑇;𝑅)]. 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 𝑋 of type variables, 𝑆=𝑋𝑇 means 𝛾[𝑆]=𝛾[𝑇] for every 𝛾∈𝑋. A direct structural induction gives, for every 𝜏, ftv(𝜏[𝑆])=⋃𝛾∈ftv(𝜏)ftv(𝛾[𝑆]), so in particular 𝑆=ftv(𝜏)𝑇 implies 𝜏[𝑆]=𝜏[𝑇].
Proof of Lemma 4.6 — Extensionality of type substitution
Proof. Induct on the finite tree 𝜏. The variable case is the hypothesis. At 𝐹(𝜏1,…,𝜏𝑛), every variable of a component belongs to ftv(𝜏), so the induction hypotheses give 𝜏𝑖[𝑆]=𝜏𝑖[𝑇] for every 𝑖; congruence of 𝐹 gives the conclusion. ◻
A type scheme is an expression 𝜎::=𝜏∣∀𝛼.𝜎, that is, a monotype under a finite (possibly empty) prefix of quantifiers; we abbreviate ∀𝛼1.⋯∀𝛼𝑘.𝜏 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(𝑆), (∀¯𝛼.𝜏)[𝑆]:=∀¯𝛼.𝜏[𝑆]. The quantifier prefix is outermost only: the expression (∀𝛼.𝛼→𝛼)→𝖭𝖺𝗍 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.
HM contextsΓ are formed as in definition 2.4, except that declarations now attach schemes: Γ::=⋅∣Γ,𝑥:𝜎 with the declared variables distinct. We write Γ(𝑥) for the scheme declared for 𝑥, ftv(Γ) for the union of the ftv of its schemes, and Γ[𝑆] for the context with 𝑆 applied to every scheme.
Let 𝜎=∀¯𝛼.𝜏0 with ¯𝛼=𝛼1,…,𝛼𝑘. A monotype 𝜏 is an instance of 𝜎, written 𝜎≽𝜏, if 𝜏=𝜏0[𝜌1/𝛼1,…,𝜌𝑘/𝛼𝑘] for some monotypes 𝜌1,…,𝜌𝑘. For schemes, define 𝜎1⊒𝜎2iffeveryinstanceof𝜎2isaninstanceof𝜎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: 𝜎≽𝜏𝜏isonemonotypeinstanceof𝜎,𝜎1⊒𝜎2everymonotypeinstanceof𝜎2isoneof𝜎1.
For example, with 𝜎=∀𝛼.𝛼→𝛼: 𝜎≽𝖭𝖺𝗍→𝖭𝖺𝗍,𝜎≽(𝛽→𝛽)→(𝛽→𝛽),𝜎⋡𝖭𝖺𝗍→𝖡𝗈𝗈𝗅, 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.
★★☆ 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.
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 𝛼𝑖 is sent by both sides to 𝜌𝑖[¯𝜇/¯𝛽], 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 𝛽𝑗 by 𝛽𝑗). So 𝜎1≽𝜏2, which is the displayed condition. ◻
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.
Proof. Choose an alpha-equivalent representative 𝜎=∀¯𝛼.𝜏0 with ¯𝛼 fresh for vars(𝑆), so 𝜎[𝑆]=∀¯𝛼.𝜏0[𝑆]. Let 𝜏=𝜏0[¯𝜌/¯𝛼]. We claim 𝜏0[¯𝜌/¯𝛼][𝑆]=𝜏0[𝑆][¯𝜌[𝑆]/¯𝛼]. Check on the variables of 𝜏0: each 𝛼𝑖 goes to 𝜌𝑖[𝑆] on both sides (𝑆 fixes 𝛼𝑖, which is outside vars(𝑆)); a variable 𝛾∉¯𝛼 goes on the left to 𝛾[𝑆], and on the right to 𝛾[𝑆][¯𝜌[𝑆]/¯𝛼]=𝛾[𝑆], since ftv(𝛾[𝑆]) avoids ¯𝛼 (either 𝛾∈dom(𝑆) and ftv(𝛾[𝑆])⊆ftv(𝑆), or 𝛾[𝑆]=𝛾∉¯𝛼). By (3.2), 𝜏[𝑆] is an instance of 𝜎[𝑆].
For the second claim, standardize both schemes apart. Choose alpha-equivalent representatives 𝜎1=∀¯𝛼.𝜏1 and 𝜎2=∀¯𝛽.𝜏2 whose prefixes are disjoint and avoid vars(𝑆), and with ¯𝛽 also fresh for ftv(𝜎1). By lemma 3.9, 𝜏2=𝜏1[¯𝜌/¯𝛼],𝜏2[𝑆]=𝜏1[𝑆][¯𝜌[𝑆]/¯𝛼], where the second equality is (3.2) applied to the freshened prefix ¯𝛼. Since 𝜎1[𝑆]=∀¯𝛼.𝜏1[𝑆], 𝜎2[𝑆]=∀¯𝛽.𝜏2[𝑆]. Moreover, ftv(𝜎1[𝑆])⊆(ftv(𝜎1)∖dom(𝑆))∪ftv(𝑆), and ¯𝛽 avoids the set on the right. Therefore lemma 3.9 applies in the converse direction. ◻
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 𝑆:=[𝜇/𝛼]. Then 𝜏∗=𝜏2[𝑆][¯𝜇/¯𝛽], checked on the variables of 𝜏2 as before, using ¯𝛽∩ftv(𝜇)=∅. So 𝜏∗ is an instance of 𝜎2[𝑆]; by lemma 3.10, 𝜎1[𝑆]⊒𝜎2[𝑆], and 𝜎1[𝑆]=𝜎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.
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 Γ=𝑓:𝛼→𝛽, the naive result Gen?Γ(𝛼→𝛽)=∀𝛼𝛽.𝛼→𝛽 would let two uses of the same 𝑓 instantiate its domain and codomain differently. The context declares 𝑓:𝛼→𝛽 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 𝑆, alpha-freshening the surviving quantified variables away from 𝑆 gives GenΓ(𝜏)[𝑆]⊒GenΓ[𝑆](𝜏[𝑆]).
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.
Proof. Write GenΓ(𝜏)=∀¯𝛼.𝜏 and GenΓ′(𝜏)=∀¯𝛽.𝜏; then ¯𝛽⊆¯𝛼, and ¯𝛽 avoids ftv(GenΓ(𝜏))⊆ftv(Γ). The identity substitution on ¯𝛼 exhibits the condition of lemma 3.9. ◻
Proof of Lemma 3.14 — Generalization under substitution
Proof. Let ¯𝛼=ftv(𝜏)∖ftv(Γ) and choose fresh variables ¯𝛼′ avoiding vars(𝑆)∪ftv(Γ)∪ftv(𝜏). With 𝜋:=[¯𝛼′/¯𝛼] and 𝑇:=𝜋;𝑆, GenΓ(𝜏)[𝑆]=∀¯𝛼′.𝜏[𝑇], by the definition of substitution on schemes. Now let 𝜏[𝑆][¯𝜇/¯𝛽] be an arbitrary instance of GenΓ[𝑆](𝜏[𝑆]), where ¯𝛽=ftv(𝜏[𝑆])∖ftv(Γ[𝑆]). Define a further substitution 𝑁 on the primed prefix by 𝛼′𝑖[𝑁]:=𝛼𝑖[𝑆][¯𝜇/¯𝛽]. We claim 𝜏[𝑇][𝑁]=𝜏[𝑆][¯𝜇/¯𝛽], which exhibits the instance as an instance of GenΓ(𝜏)[𝑆] and finishes the proof. Check the claim on each 𝛾∈ftv(𝜏) using (3.1). If 𝛾=𝛼𝑖∈¯𝛼: the left side gives 𝛼𝑖[𝑇][𝑁]=𝛼′𝑖[𝑆][𝑁]=𝛼′𝑖[𝑁] (as 𝛼′𝑖 is fresh for 𝑆), which is 𝛼𝑖[𝑆][¯𝜇/¯𝛽], the right side at 𝛼𝑖. If 𝛾∉¯𝛼, then 𝛾∈ftv(Γ), so ftv(𝛾[𝑆])⊆ftv(Γ[𝑆]), which avoids ¯𝛽; hence the right side fixes 𝛾[𝑆], while the left side gives 𝛾[𝑇][𝑁]=𝛾[𝑆][𝑁]=𝛾[𝑆], since ftv(𝛾[𝑆]) contains no primed variable. ◻
Standardizing the variables to be generalized apart before substitution produces a context-equivalent substitution 𝑇 for which generalization recovers the renamed prefix. This is the converse-looking companion to lemma 3.14: that lemma fixes 𝑆, so generalization can become less general, whereas the fresh renaming below changes 𝑆 away from the context while preserving its action on the context.
Proof of Lemma 3.15 — Fresh renaming before substitution
Proof. Take ¯𝛼, ¯𝛼′, 𝜋, and 𝑇=𝜋;𝑆 as in the proof of lemma 3.14. Claim 1 holds because 𝜋 fixes ftv(Γ). For claim 2, we saw GenΓ(𝜏)[𝑆]=∀¯𝛼′.𝜏[𝑇]. Every 𝛼′𝑖 is free in 𝜏[𝑇] (it is 𝛼𝑖[𝑇] with 𝛼𝑖∈ftv(𝜏), by (3.1)) and is absent from ftv(Γ[𝑆])⊆ftv(Γ)∪ftv(𝑆) by freshness. Hence the prefix of GenΓ[𝑆](𝜏[𝑇]) contains all of ¯𝛼′, possibly more, and an instance of ∀¯𝛼′.𝜏[𝑇] becomes an instance of GenΓ[𝑆](𝜏[𝑇]) by instantiating the extra quantified variables by themselves. ◻
★★☆ Compute GenΓ(𝜏) for: (a) Γ=Γ0 and 𝜏=(𝛼→𝛼)→𝛼→𝛼; (b) Γ=Γ0,𝑓:𝛼→𝛽 and 𝜏=𝛼→(𝛽→𝛾)→𝛾; (c) Γ=Γ0,𝑓:∀𝛼.𝛼→𝛽 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 𝑆=[𝖭𝖺𝗍/𝛼].
The judgment Γ⊢𝑒:𝜎, for Γ a context of schemes, 𝑒 a term of the let-language, and 𝜎 a scheme, is inductively defined by
(𝑥:𝜎)∈Γ
Γ⊢𝑥:𝜎
Var
Γ⊢𝑒:𝜎𝜎⊒𝜎′
Γ⊢𝑒:𝜎′
Inst
Γ⊢𝑒:𝜎𝛼∉ftv(Γ)
Γ⊢𝑒:∀𝛼.𝜎
Gen
Γ,𝑥:𝜏1⊢𝑒:𝜏2
Γ⊢𝜆𝑥.𝑒:𝜏1→𝜏2
Lam
Γ⊢𝑒1:𝜏2→𝜏Γ⊢𝑒2:𝜏2
Γ⊢𝑒1𝑒2:𝜏
App
Γ⊢𝑒1:𝜎Γ,𝑥:𝜎⊢𝑒2:𝜏
Γ⊢𝗅𝖾𝗍𝑥=𝑒1𝗂𝗇𝑒2:𝜏
Let
Here 𝜏,𝜏1,𝜏2 range over monotypes and 𝜎,𝜎′ over schemes: an abstraction binds its variable at a monotype, an application consumes monotypes, and only 𝗅𝖾𝗍 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 𝗅𝖾𝗍-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).
We derive Γ0⊢𝑃𝗂𝖽:𝖭𝖺𝗍. Write 𝜎𝗂𝖽:=∀𝛼.𝛼→𝛼 and Γ1:=Γ0,𝗂𝖽:𝜎𝗂𝖽. First the definition:
(𝑥:𝛼)∈(Γ0,𝑥:𝛼)
Γ0,𝑥:𝛼⊢𝑥:𝛼
Var
Γ0⊢𝜆𝑥.𝑥:𝛼→𝛼
Lam
Γ0⊢𝜆𝑥.𝑥:𝜎𝗂𝖽
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:
Γ1⊢𝗂𝖽:𝜎𝗂𝖽𝜎𝗂𝖽⊒(𝖭𝖺𝗍→𝖭𝖺𝗍)→(𝖭𝖺𝗍→𝖭𝖺𝗍)
Γ1⊢𝗂𝖽:(𝖭𝖺𝗍→𝖭𝖺𝗍)→(𝖭𝖺𝗍→𝖭𝖺𝗍)
Inst
Γ1⊢𝗂𝖽:𝜎𝗂𝖽𝜎𝗂𝖽⊒𝖭𝖺𝗍→𝖭𝖺𝗍
Γ1⊢𝗂𝖽:𝖭𝖺𝗍→𝖭𝖺𝗍
Inst
Two App steps type the body: Γ1⊢𝗂𝖽𝗌𝗎𝖼𝖼:𝖭𝖺𝗍→𝖭𝖺𝗍 from the first instance and Γ1⊢𝗌𝗎𝖼𝖼:𝖭𝖺𝗍→𝖭𝖺𝗍 (Var); then Γ1⊢𝗂𝖽𝗓𝖾𝗋𝗈:𝖭𝖺𝗍 from the second instance and 𝗓𝖾𝗋𝗈; then Γ1⊢(𝗂𝖽𝗌𝗎𝖼𝖼)(𝗂𝖽𝗓𝖾𝗋𝗈):𝖭𝖺𝗍. Finally Let discharges the definition:
Γ0⊢𝜆𝑥.𝑥:𝜎𝗂𝖽Γ1⊢(𝗂𝖽𝗌𝗎𝖼𝖼)(𝗂𝖽𝗓𝖾𝗋𝗈):𝖭𝖺𝗍
Γ0⊢𝑃𝗂𝖽:𝖭𝖺𝗍
Let
One binding; two incompatible uses; no annotations. Demand 1 of the introduction is met.
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 Δ. ◻
Proof. Induct on the second derivation after freshening all term binders away from 𝑥 and the free variables of 𝑎. At a Var leaf for 𝑥, 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(Γ,𝑥:𝜎), hence also from ftv(Γ) after 𝑥 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 𝑎 under the let binder, apply the induction hypothesis to both premises, and reapply Let. If that binder is also called 𝑥, freshen it first as permitted by convention 3.2. ◻
For the variable, abstraction, application, and let grammar of definition 3.1, values are abstractions and evaluation contexts are E::=[]∣E𝑒∣𝑣E∣𝗅𝖾𝗍𝑥=E𝗂𝗇𝑒. The root contractions are (𝜆𝑥.𝑏)𝑣⇝0𝑏[𝑣/𝑥],𝗅𝖾𝗍𝑥=𝑣𝗂𝗇𝑒⇝0𝑒[𝑣/𝑥], 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 𝜆𝑓.𝑒 so that 𝑓 is applied to 𝗓𝖾𝗋𝗈 and to 𝗍𝗋𝗎𝖾 inside 𝑒. Rule Lam forces a context Γ,𝑓:𝜏𝑓 with one monotype 𝜏𝑓; inside the body, any attempt to apply Gen to a variable of 𝜏𝑓 is blocked by the side condition. For example, from Γ,𝑓:𝛼→𝛽⊢𝑓:𝛼→𝛽 one would need 𝛼∉ftv(Γ,𝑓:𝛼→𝛽) to introduce ∀𝛼, but the displayed condition is false because 𝛼 occurs in the declaration of 𝑓. Hence Gen does not apply. So both uses of 𝑓 must share the single monotype 𝜏𝑓, and no monotype is simultaneously an arrow out of 𝖭𝖺𝗍 and an arrow out of 𝖡𝗈𝗈𝗅. Indeed, inversion on the two application derivations would give 𝜏𝑓=𝖭𝖺𝗍→𝐴 and 𝜏𝑓=𝖡𝗈𝗈𝗅→𝐵 for some monotypes 𝐴,𝐵. Injectivity of the arrow constructor would then give 𝖭𝖺𝗍=𝖡𝗈𝗈𝗅, contradicting disjointness of type constructors.
★★☆ (a) Derive Γ0⊢𝜆𝑥.𝜆𝑦.𝑥:∀𝛼∀𝛽.𝛼→𝛽→𝛼. (b) Derive Γ0⊢𝗅𝖾𝗍𝗂𝖽=𝜆𝑥.𝑥𝗂𝗇𝗂𝖽𝗂𝖽:𝛽→𝛽; the two uses of 𝗂𝖽 take the instances (𝛽→𝛽)→(𝛽→𝛽) and 𝛽→𝛽. (c) Show that the judgment of (b) can be strengthened by one final Gen to the scheme ∀𝛽.𝛽→𝛽.
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: 𝗍𝗐𝗂𝖼𝖾:=𝜆𝑓.𝜆𝑥.𝑓(𝑓𝑥).
For this let-free calculation, write Γ⊢c𝑒:𝜏∣𝐸 to mean that 𝑒 has provisional type 𝜏 provided the finite equation list 𝐸 is solved. If 𝐸⋅𝐸′ 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
Γ(𝑥)=𝜏
Γ⊢c𝑥:𝜏∣()
C-Var
Γ,𝑥:𝛼⊢c𝑒:𝜏∣𝐸𝛼fresh
Γ⊢c𝜆𝑥.𝑒:𝛼→𝜏∣𝐸
C-Lam
Γ⊢c𝑒1:𝜏1∣𝐸1Γ⊢c𝑒2:𝜏2∣𝐸2𝛽fresh
Γ⊢c𝑒1𝑒2:𝛽∣𝐸1⋅𝐸2⋅(𝜏1≐𝜏2→𝛽)
C-App
Here Γ(𝑥)=𝜏 in C-Var means a monotype declaration. The constraint fragment has no 𝗅𝖾𝗍, 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 𝑓 at an unknown monotype 𝛼𝑓 and then 𝑥 at an unknown 𝛼𝑥; the working context is Γ:=Γ0,𝑓:𝛼𝑓,𝑥:𝛼𝑥. The body is 𝑓(𝑓𝑥), 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 𝑓𝑥 first: 𝑓 has type 𝛼𝑓 (C-Var); 𝑥 has type 𝛼𝑥; C-App applies only if 𝛼𝑓is an arrow with domain 𝛼𝑥. We do not know that; we record it, inventing a fresh unknown 𝛽1 for the codomain: 𝛼𝑓≐𝛼𝑥→𝛽1,givingΓ⊢c𝑓𝑥:𝛽1∣(𝛼𝑓≐𝛼𝑥→𝛽1). The outer application 𝑓(𝑓𝑥) likewise demands that 𝛼𝑓 be an arrow with domain 𝛽1; invent 𝛽2: 𝛼𝑓≐𝛽1→𝛽2,Γ⊢c𝑓(𝑓𝑥):𝛽2∣(𝛼𝑓≐𝛼𝑥→𝛽1,𝛼𝑓≐𝛽1→𝛽2). Discharging the two C-Lam steps, the whole term has type 𝛼𝑓→𝛼𝑥→𝛽2, provided the equation set 𝐸𝗍𝗐𝗂𝖼𝖾:=(𝛼𝑓≐𝛼𝑥→𝛽1,𝛼𝑓≐𝛽1→𝛽2) has a solution: a substitution making both equations literal identities. After applying a solution 𝑆, the two right-hand sides must coincide: (𝛼𝑥→𝛽1)[𝑆]=(𝛽1→𝛽2)[𝑆]. By injectivity of →, 𝛼𝑥[𝑆]=𝛽1[𝑆],𝛽1[𝑆]=𝛽2[𝑆]. Taking 𝑆=[𝛼𝑥/𝛽1,𝛼𝑥/𝛽2,(𝛼𝑥→𝛼𝑥)/𝛼𝑓] solves 𝐸𝗍𝗐𝗂𝖼𝖾 and turns the conditional typing into an actual derivation of Γ0⊢𝗍𝗐𝗂𝖼𝖾:(𝛼𝑥→𝛼𝑥)→𝛼𝑥→𝛼𝑥, and one Gen, alpha-renaming the quantified 𝛼𝑥 to 𝛼 for display, yields the scheme ∀𝛼.(𝛼→𝛼)→𝛼→𝛼.
Was the solution forced? Not entirely. The substitution 𝑆′=[𝖭𝖺𝗍/𝛼𝑥,𝖭𝖺𝗍/𝛽1,𝖭𝖺𝗍/𝛽2,(𝖭𝖺𝗍→𝖭𝖺𝗍)/𝛼𝑓] also solves 𝐸𝗍𝗐𝗂𝖼𝖾 and yields the less useful (𝖭𝖺𝗍→𝖭𝖺𝗍)→𝖭𝖺𝗍→𝖭𝖺𝗍. 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 𝜆𝑡.𝑡𝑡. Binding 𝑡 at an unknown 𝛾, the application 𝑡𝑡 demands that 𝛾 be an arrow with domain 𝛾: 𝛾≐𝛾→𝛿. No substitution can solve this: applying any 𝑆 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 𝑡 were 𝗅𝖾𝗍-bound to 𝗍𝗐𝗂𝖼𝖾, each of the two occurrences would receive its own instance of the scheme of 𝗍𝗐𝗂𝖼𝖾, 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 𝐸 is exact: success returns a substitution that satisfies every equation and through which every other such substitution factors on the variables occurring in 𝐸. 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.
★★☆ Carry out the walk for 𝖼𝗈𝗆𝗉𝗈𝗌𝖾:=𝜆𝑓.𝜆𝑔.𝜆𝑥.𝑓(𝑔𝑥): introduce unknowns 𝛼𝑓,𝛼𝑔,𝛼𝑥, extract two equations, solve them by hand, and conclude with the scheme ∀𝛼∀𝛽∀𝛾.(𝛽→𝛾)→(𝛼→𝛽)→𝛼→𝛾 after Gen.
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.
An equation problem is a finite list 𝐸=(𝜏1≐𝜌1,…,𝜏𝑛≐𝜌𝑛). A substitution 𝑆solves𝐸, written 𝑆⊧𝐸, when 𝜏𝑖[𝑆]=𝜌𝑖[𝑆] for every 𝑖. Put vars(𝐸):=⋃𝑖(ftv(𝜏𝑖)∪ftv(𝜌𝑖)). A solution 𝑈 is a most general unifier, or MGU, when every solution 𝑆 factors through it: for some substitution 𝑇, 𝑆=vars(𝐸)𝑈;𝑇. Equality only on vars(𝐸) is intentional; the problem says nothing about the action of a substitution elsewhere. For substitutions on a variable set 𝑋, put 𝑈 below 𝑆 when 𝑆=𝑋𝑈;𝑇 for some 𝑇. This relation is a preorder, and an MGU is a least solution in it. The factor 𝑇 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 𝐸[𝑅] applies 𝑅 to both monotypes in every equation of 𝐸, preserving list order. We always pass a list to 𝗎𝗇𝗂𝖿𝗒; in particular, 𝗎𝗇𝗂𝖿𝗒((𝜏≐𝜌)) is a call on the singleton equation list.
Proof. The occurrence of 𝛼 in 𝜏 is proper, so for every substitution 𝑆, the finite tree 𝛼[𝑆] occurs as a proper subtree of 𝜏[𝑆]. If 𝛼[𝑆]=𝜏[𝑆], 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. ◻
The partial function𝗎𝗇𝗂𝖿𝗒(𝐸) 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: 𝗎𝗇𝗂𝖿𝗒(∅)=id.
U-Delete. Delete a reflexive equation: 𝗎𝗇𝗂𝖿𝗒(𝜏≐𝜏,𝐸)=𝗎𝗇𝗂𝖿𝗒(𝐸).U-Orient. If the first equation has the form 𝐹(¯𝜏)≐𝛼, replace it in place by 𝛼≐𝐹(¯𝜏) and select the applicable clause below. This rewrite does not invoke 𝗎𝗇𝗂𝖿𝗒. U-Eliminate. If 𝛼≠𝜏 and 𝛼∉ftv(𝜏), drop the first equation 𝛼≐𝜏, put 𝑅=[𝜏/𝛼], and apply 𝑅 to every remaining equation: 𝗎𝗇𝗂𝖿𝗒(𝛼≐𝜏,𝐸)=let𝑈=𝗎𝗇𝗂𝖿𝗒(𝐸[𝑅])in𝑅;𝑈.
U-Decompose. Replace an equation with equal head constructor by its component equations: 𝗎𝗇𝗂𝖿𝗒(𝐹(𝜏1,…,𝜏𝑘)≐𝐹(𝜌1,…,𝜌𝑘),𝐸)=𝗎𝗇𝗂𝖿𝗒(𝜏1≐𝜌1,…,𝜏𝑘≐𝜌𝑘,𝐸).
U-Clash. Fail on 𝐹(¯𝜏)≐𝐺(¯𝜌) when 𝐹≠𝐺.
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.
Apply U-Eliminate to the first equation of 𝐸𝗍𝗐𝗂𝖼𝖾: 𝑅1=[𝛼𝑥→𝛽1/𝛼𝑓]. The remaining equation becomes 𝛼𝑥→𝛽1≐𝛽1→𝛽2. Decomposition gives 𝛼𝑥≐𝛽1 and 𝛽1≐𝛽2. Eliminating in that order yields 𝑅2=[𝛽1/𝛼𝑥],𝑅3=[𝛽2/𝛽1]. The returned composite 𝑅1;𝑅2;𝑅3 sends 𝛼𝑓 to 𝛽2→𝛽2 and sends 𝛼𝑥,𝛽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 𝛼𝑥 to 𝛽2.
Proof. Orientation is a finite preprocessing step and makes no recursive call. Order the arguments of recursive calls lexicographically by (|vars(𝐸)|,totalnumberofconstructornodesin𝐸,|𝐸|). 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. ◻
Proof. If 𝑆 solves the displayed equation, then 𝛼[𝑆]=𝜏[𝑆]. Hence, on 𝛼, 𝛼[𝑅;𝑆]=𝜏[𝑆]=𝛼[𝑆], while on every 𝛾≠𝛼, 𝛾[𝑅;𝑆]=𝛾[𝑆]. Thus 𝑆=𝑅;𝑆 on all problem variables. Applying this equality to both sides of each equation in 𝐸 shows 𝑆⊧𝐸[𝑅].
Conversely, 𝛼[𝑅]=𝜏=𝜏[𝑅], the last equality using 𝛼∉ftv(𝜏). Thus every substitution after 𝑅 solves 𝛼≐𝜏. If 𝑆=𝑅;𝑆 on the problem variables and 𝑆⊧𝐸[𝑅], then 𝑆 also solves every equation of 𝐸: for each side 𝜌 occurring in 𝐸, agreement on the problem variables gives 𝜌[𝑆]=𝜌[𝑅;𝑆]=𝜌[𝑅][𝑆], and the equation in 𝐸[𝑅] identifies the final expressions. ◻
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 solutiontransport,factorextension,supportpreservation. Put 𝑅=[𝜏/𝛼] and suppose the recursive call returns an MGU 𝑈 of 𝐸[𝑅]. By lemma 3.25, 𝑅;𝑈 solves the original problem. Let 𝑆 be any other solution. The same lemma gives 𝑆⊧𝐸[𝑅] and 𝑆=𝑅;𝑆 on the old problem variables. Since 𝑈 is most general for 𝐸[𝑅], there is 𝑇0 with 𝑆=𝑈;𝑇0 on its variables. Extend 𝑇0 to a substitution 𝑇 by setting 𝛾[𝑇]=𝛾[𝑆] for each 𝛾∈ftv(𝜏)∖vars(𝐸[𝑅]). The recursive support assertions say that 𝑈 fixes those added variables and that none of them occurs in 𝛿[𝑈] for 𝛿∈vars(𝐸[𝑅]). Thus changing 𝑇0 there does not disturb the old factorization, and now 𝑆=𝑈;𝑇 on vars(𝐸[𝑅])∪ftv(𝜏). Every old variable other than 𝛼 lies in that union. Therefore, for every old variable 𝛾≠𝛼, 𝛾[𝑅]=𝛾, hence 𝛾[𝑆]=𝛾[𝑅][𝑈;𝑇]. For 𝛼, 𝛼[𝑅][𝑈;𝑇]𝑑𝑒𝑓𝑖𝑛𝑖𝑡𝑖𝑜𝑛𝑜𝑓𝑅=𝜏[𝑈;𝑇]𝑓𝑎𝑐𝑡𝑜𝑟𝑖𝑧𝑎𝑡𝑖𝑜𝑛𝑜𝑛ftv(𝜏)=𝜏[𝑆]𝑆⊧𝛼≐𝜏=𝛼[𝑆]; the middle equality follows from the extended factorization on ftv(𝜏). Thus 𝑆=𝑅;𝑈;𝑇 on the original problem variables, proving that 𝑅;𝑈 is most general. This also proves that failure of the recursive call implies failure of the original problem. Finally, 𝑅 changes only 𝛼 and introduces only variables already in the original problem. The recursive result fixes variables outside vars(𝐸[𝑅]) and introduces no outside variable in the image of a recursive problem variable. Hence 𝑅;𝑈 fixes every variable outside vars(𝐸) and maps each old problem variable to a type using only old problem variables. This proves both remaining support assertions. ◻
Proof of Corollary 4.31 — Mutual factorization of most general unifiers
Proof. Because 𝑉 is a solution and 𝑈 is most general, there is 𝑅 with 𝑉=vars(𝐸)𝑈;𝑅. Interchanging 𝑈 and 𝑉 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. ◻
★★☆ Run 𝗎𝗇𝗂𝖿𝗒 on ((𝛼→𝛽)→𝛾≐(𝖭𝖺𝗍→𝖡𝗈𝗈𝗅)→𝛿,𝛿≐𝖭𝖺𝗍). Write every intermediate equation list and the returned composite substitution. Then replace the second equation by 𝛿≐𝛿→𝖭𝖺𝗍 and identify the precise failure rule. A complete calculation is given in appendix B.
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 𝗍𝗐𝗂𝖼𝖾. After the inner application has learned 𝑈1=[𝛽→𝛾/𝛼], suppose the outer application still uses the stale 𝛼. It solves 𝛼≐𝛾→𝛿 independently of 𝛼≐𝛽→𝛾; the returned result then leaves 𝛿 unconstrained and suggests the spurious type (𝛽→𝛾)→𝛽→𝛿. Applying 𝑈1 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 Γ[𝑆1].
For 𝜎=∀𝛼1⋯∀𝛼𝑘.𝜏, let 𝖿𝗋𝖾𝗌𝗁(𝜎) be 𝜏[𝛽1/𝛼1,…,𝛽𝑘/𝛼𝑘]. 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 𝛽𝑖 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.
The partial algorithm 𝖶(Γ,𝑒) returns a pair (𝑆,𝜏). Every type variable described as fresh below is consumed from the supply of definition 3.27 and is fresh for the entire run. 𝖶(Γ,𝑥)=(id,𝖿𝗋𝖾𝗌𝗁(Γ(𝑥))),𝖶(Γ,𝜆𝑥.𝑒)=let𝛼befresh,(𝑆,𝜏)=𝖶((Γ,𝑥:𝛼),𝑒),in(𝑆,𝛼[𝑆]→𝜏),𝖶(Γ,𝑒1𝑒2)=let(𝑆1,𝜏1)=𝖶(Γ,𝑒1),(𝑆2,𝜏2)=𝖶(Γ[𝑆1],𝑒2),𝛽befresh,𝐶=(𝜏1[𝑆2]≐𝜏2→𝛽),𝑈=𝗎𝗇𝗂𝖿𝗒(𝐶),in(𝑆1;𝑆2;𝑈,𝛽[𝑈]),𝖶(Γ,𝗅𝖾𝗍𝑥=𝑒1𝗂𝗇𝑒2)=let(𝑆1,𝜏1)=𝖶(Γ,𝑒1),𝜎=GenΓ[𝑆1](𝜏1),Γ1=(Γ[𝑆1],𝑥:𝜎),(𝑆2,𝜏2)=𝖶(Γ1,𝑒2),in(𝑆1;𝑆2,𝜏2). The variable clause fails when 𝑥∉dom(Γ); 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.
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 Γ[𝑆1], the function result is updated once more to 𝜏1[𝑆2], and 𝑈 solves 𝜏1[𝑆2]≐𝜏2→𝛽. In the lower row, the same 𝑆1 first updates the context used by generalization; the body then sees exactly Γ[𝑆1],𝑥:𝜎. The diagram records these two equalities and does not replace any clause of definition 3.28.
For an input (Γ,𝑒), a legal fresh supply is an injective sequence of type variables disjoint from ftv(Γ). Every allocation consumes the next element of the sequence; recursive calls receive the unconsumed suffix, and sibling calls never restart it. The monotone counter of definition 3.27 is one legal supply.
Let (𝑎1,…,𝑎𝑛) and (𝑏1,…,𝑏𝑛) be lists without repetitions, and suppose that every variable in both lists avoids a finite set 𝐹. The ordered matching 𝑎𝑖↦𝑏𝑖 extends to a permutation 𝜋 of the type variables with finite support, and 𝜋 fixes every variable in 𝐹.
Proof of Lemma 4.35 — Completion of a finite name matching
Proof. Draw the finite directed graph on 𝐴∪𝐵, where 𝐴={𝑎1,…,𝑎𝑛} and 𝐵={𝑏1,…,𝑏𝑛}, with one edge 𝑎𝑖→𝑏𝑖 for each 𝑖. 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 𝐴∪𝐵. The resulting map has exactly one incoming and one outgoing edge at every vertex of 𝐴∪𝐵, agrees with every prescribed pair, and is the identity off that finite set. Since 𝐹∩(𝐴∪𝐵)=∅, it fixes 𝐹. ◻
Run W on one input (Γ,𝑒) with two legal supplies. The executions either both fail at corresponding clauses or both succeed. In the successful case, if their results are (𝑆,𝜏) and (𝑆′,𝜏′), 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 𝑆′=𝜋−1;𝑆;𝜋,𝜏′=𝜏[𝜋],Γ[𝑆′]=Γ[𝑆][𝜋]. Consequently success, soundness, and the principal-pair property are independent of the chosen legal supply.
Proof. First induct on the unification recursion. For every finite bijection 𝜋, renaming preserves the selected clause and 𝗎𝗇𝗂𝖿𝗒(𝐸[𝜋])=𝜋−1;𝑈;𝜋when𝗎𝗇𝗂𝖿𝗒(𝐸)=𝑈; 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 𝑆′1=𝜋−1;𝑆1;𝜋 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 𝑆2 and 𝜏2. Pair the result variables next. The two application equations are related by 𝜋, so unification equivariance gives 𝑈′=𝜋−1;𝑈;𝜋. Associativity then yields 𝑆′1;𝑆′2;𝑈′=𝜋−1;(𝑆1;𝑆2;𝑈);𝜋. It also shows that unification succeeds or fails in both runs.
For let, the definition of generalization gives GenΓ[𝑆1](𝜏1)[𝜋]=𝛼GenΓ[𝑆1][𝜋](𝜏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 𝑒1 has provisional type 𝜏1 after 𝑆1. The second call may learn more, so it sees Γ[𝑆1] and changes the function type to 𝜏1[𝑆2]. A fresh 𝛽 stands for the result. Unification enforces 𝜏1[𝑆2]=𝜏2→𝛽, and its substitution 𝑈 is applied to every accumulated answer.
For 𝜆𝑥.𝑥, choose fresh 𝛼. The variable call under Γ,𝑥:𝛼 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.
Run W on 𝗍𝗐𝗂𝖼𝖾=𝜆𝑓.𝜆𝑥.𝑓(𝑓𝑥). Give (f) fresh type 𝛼 and (x) fresh type 𝛽. The inner application introduces 𝛾 and solves 𝛼≐𝛽→𝛾,𝑈1=[𝛽→𝛾/𝛼]. The outer application must use the substituted function type. With fresh result 𝛿, it solves 𝛼[𝑈1]=𝛽→𝛾≐𝛾→𝛿,𝑈2=[𝛿/𝛽,𝛿/𝛾]. Therefore 𝖶(Γ0,𝗍𝗐𝗂𝖼𝖾)=(𝑈1;𝑈2,(𝛿→𝛿)→𝛿→𝛿). 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.
Proof. For the declarative judgment, induct on the derivation. Before a Gen case, alpha-rename its quantified variable away from vars(𝑆). The side condition then remains true after applying 𝑆, and Gen rebuilds the conclusion. Inst uses lemma 3.10. Every other case applies 𝑆 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 𝐺=ftv(𝜏1)∖ftv(Γ) away from vars(𝑆) 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 𝐺 gives the alpha-equality GenΓ(𝜏1)[𝑆]=𝛼GenΓ[𝑆](𝜏1[𝑆]). Indeed, 𝑆 fixes every member of 𝐺, and every other free variable of 𝜏1 occurs in Γ, so all variables introduced by its image under 𝑆 occur in Γ[𝑆] and are not generalized. The two induction hypotheses therefore produce exactly the premises of S-Let, with the body declaration identified by (3.3). ◻
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. ◻
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 Γ⊢𝑒1: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Γ,𝑥:𝜏1(𝜌)⊒𝜏2. Choose its witnessing instance substitution 𝑄, so 𝜌[𝑄]=𝜏2. The domain of 𝑄 consists of variables generalized over the premise context, hence 𝑄 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 Γ⊢s𝑒1:𝜌1,GenΓ(𝜌1)⊒𝜎. The second induction output is initially under Γ,𝑥:𝜎. 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Γ,𝑥:𝜎(𝜌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. ◻
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 Γ,𝑥:𝜏1⊢𝑏:𝜏2 and Γ⊢𝑣:𝜏1; at let, it gives Γ⊢𝑣:𝜎 and Γ,𝑥:𝜎⊢𝑒:𝜏. 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 𝗌𝗎𝖼𝖼, the Boolean constants, and the numeral judgment 𝑋𝗓𝖾𝗋𝗈𝗇𝗎𝗆N−Z𝑛𝗇𝗎𝗆𝗌𝗎𝖼𝖼𝑛𝗇𝗎𝗆N−S. Numerals and Booleans are final values. Thus 𝗌𝗎𝖼𝖼𝑛 is final exactly when 𝑛𝗇𝗎𝗆; no value or contraction rule applies to 𝗌𝗎𝖼𝖼𝗍𝗋𝗎𝖾, so that closed term is stuck. Thus the primitive signature itself distinguishes 𝖭𝖺𝗍 from 𝖡𝗈𝗈𝗅: the former admits 𝗌𝗎𝖼𝖼, while the latter does not.
For a variable, 𝖿𝗋𝖾𝗌𝗁(Γ(𝑥)) is an instance of its scheme, so Var followed by Inst applies. In the lambda case the induction hypothesis is Γ[𝑆],𝑥:𝛼[𝑆]⊢𝑒:𝜏;Lam gives the returned type 𝛼[𝑆]→𝜏.
For 𝑒1𝑒2, the induction hypotheses give Γ[𝑆1]⊢𝑒1:𝜏1,Γ[𝑆1;𝑆2]⊢𝑒2:𝜏2. Apply lemma 3.32 with 𝑆2 to the first derivation and then with 𝑈 to both. Since 𝑈 solves W’s equation 𝜏1[𝑆2]≐𝜏2→𝛽, 𝜏1[𝑆2;𝑈]=𝜏2[𝑈]→𝛽[𝑈]. Rule App therefore returns 𝛽[𝑈] under Γ[𝑆1;𝑆2;𝑈].
For a let, the first induction hypothesis and repeated Gen give Γ[𝑆1]⊢𝑒1:𝜎,𝜎=GenΓ[𝑆1](𝜏1). Apply type substitution 𝑆2 to this declarative derivation, obtaining Γ[𝑆1;𝑆2]⊢𝑒1:𝜎[𝑆2]. The second induction hypothesis is Γ[𝑆1;𝑆2],𝑥:𝜎[𝑆2]⊢𝑒2:𝜏2. These are precisely the two premises of declarative Let. The returned context and type are Γ[𝑆1;𝑆2] and 𝜏2. ◻
Let 𝑈 be the MGU returned for an equation list 𝐸, and suppose a solution 𝑄 of 𝐸 factors as 𝑈;𝑅 on vars(𝐸). For any finite protected set 𝑃, the residual 𝑅 can be extended so that 𝑄=vars(𝐸)∪𝑃𝑈;𝑅 without changing it on the variables of 𝐸.
Proof of Lemma 4.45 — Extending an MGU factorization off its problem
Proof. For each 𝛾∈𝑃∖vars(𝐸) set 𝛾[𝑅]:=𝛾[𝑄] and retain 𝑅 elsewhere. The unifier fixes every variable outside its equation problem by theorem 3.26(3), so 𝛾[𝑈;𝑅]=𝛾[𝑅]=𝛾[𝑄] there. On vars(𝐸) the original factorization is unchanged. ◻
The protected set has a concrete role even in a one-node application. Take Γ=Γ0,𝑓:𝛼, the term 𝑓𝗓𝖾𝗋𝗈, and protect 𝑃={𝛼}. Suppose the target specialization is 𝑇=[𝖭𝖺𝗍→𝖡𝗈𝗈𝗅/𝛼]. Both recursive calls return the identity; the application equation has MGU 𝑈=[𝖭𝖺𝗍→𝛽/𝛼], and the residual 𝑅=[𝖡𝗈𝗈𝗅/𝛽] satisfies 𝑇={𝛼}𝑈;𝑅,𝛽[𝑅]=𝖡𝗈𝗈𝗅. Protecting 𝛼 is exactly what retains this equality after the recursive calls have returned.
For a substitution 𝑄 and a finite set 𝑃, write fv𝑄(𝑃):=⋃𝛾∈𝑃ftv(𝛾[𝑄]). This is the set of variables in the 𝑄-images of 𝑃; the parenthesized subscript is not substitution or term instantiation.
Let 𝑃 be a finite set of type variables and suppose Γ[𝑇]⊢s𝑒:𝜏′. Choose every variable generated during this W run fresh for 𝑃∪vars(𝑇) and for the finite target derivation, as permitted by lemma 3.29. Then W succeeds; writing its result as (𝑆,𝜏), there is a substitution 𝑅 such that 𝑇=ftv(Γ)∪𝑃𝑆;𝑅,𝜏′=𝜏[𝑅].
Proof of Lemma 4.46 — Protected principal-pair induction
Proof. Induct on the height of the syntax-directed derivation under Γ[𝑇], generalized over Γ,𝑇,𝑃, while maintaining both the displayed factorization and agreement on 𝑃. By lemma 3.29, every generated variable lies outside ftv(Γ)∪𝑃. Extend each MGU factor outside its equation variables by lemma 4.45. In the application case, protect fv𝑆1(𝑃)∪ftv(𝜏1) during the second recursive call. Its factor then satisfies 𝜏1[𝑆2;𝑅2]=𝜏1[𝑅1], so the target substitution solves W’s final application equation.
Variable. The target 𝜏′ is an instance of Γ[𝑇](𝑥), while W’s 𝜏 replaces the quantified prefix of Γ(𝑥) by variables fresh for 𝑇 and Γ. Extend 𝑇 by sending those fresh variables to the monotypes used in the target instance. The resulting 𝑅 agrees with 𝑇 on ftv(Γ). Because the generated variables avoid 𝑃 and W’s variable substitution is the identity, extend 𝑅 by 𝛾[𝑅]=𝛾[𝑇] for the remaining 𝛾∈𝑃; this does not change 𝜏[𝑅] and gives the protected agreement. Thus 𝜏′=𝜏[𝑅].
Abstraction. The target derivation has a premise Γ[𝑇],𝑥:𝜌1⊢s𝑒0:𝜌2 and 𝜏′=𝜌1→𝜌2. W chose fresh 𝛼. Extend 𝑇 by 𝛼↦𝜌1 and apply the induction hypothesis to the recursive call on Γ,𝑥:𝛼. It gives 𝑅 with 𝜌2=𝜏0[𝑅] and, because 𝛼 occurs in the enlarged context, 𝜌1=𝛼[𝑆;𝑅]. Hence 𝜏′=𝜌1→𝜌2=(𝛼[𝑆]→𝜏0)[𝑅], the returned lambda type.
Application. The target derivation has an intermediate type 𝜌: Γ[𝑇]⊢s𝑒1:𝜌→𝜏′ and Γ[𝑇]⊢s𝑒2:𝜌. The first induction hypothesis factors 𝑇 through 𝑆1 as 𝑆1;𝑅1 and gives 𝜏1[𝑅1]=𝜌→𝜏′. Regard the second target premise as a derivation under Γ[𝑆1;𝑅1] and apply the second induction hypothesis, adding fv𝑆1(𝑃)∪ftv(𝜏1) to the protected set. It factors 𝑅1 through 𝑆2 as 𝑆2;𝑅2 on both ftv(Γ[𝑆1]) and fv𝑆1(𝑃)∪ftv(𝜏1), and gives 𝜏2[𝑅2]=𝜌. Extend 𝑅2 to send the fresh result variable 𝛽 to 𝜏′. Freshness gives 𝛽∉ftv(𝜏1[𝑆2])∪ftv(𝜏2) and keeps 𝛽 out of every inherited protected image, so this extension changes none of the equalities already obtained. Then 𝑅2 solves 𝜏1[𝑆2]≐𝜏2→𝛽. Indeed, protected agreement gives 𝜏1[𝑆2;𝑅2]=𝜏1[𝑅1]=𝜌→𝜏′, while 𝜏2[𝑅2]=𝜌 and 𝛽[𝑅2]=𝜏′. Hence unification cannot fail by theorem 3.26; let its MGU be 𝑈. Its universal property factors 𝑅2=𝑈;𝑅3 on the variables of this equation, and lemma 4.45 extends 𝑅3 on the inherited protected variables and on ftv(Γ[𝑆1;𝑆2])∪fv𝑆1;𝑆2(𝑃) 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: stagestateafterthestage𝑒1Γ;ftv(Γ)∪𝑃;𝑇=𝑆1;𝑅1,𝜏1[𝑅1]=𝜌→𝜏′𝑒2Γ[𝑆1];ftv(Γ[𝑆1])∪fv𝑆1(𝑃)∪ftv(𝜏1);𝑅1=𝑆2;𝑅2,𝜏2[𝑅2]=𝜌𝑈Γ[𝑆1;𝑆2];ftv(Γ[𝑆1;𝑆2])∪fv𝑆1;𝑆2(𝑃);𝑅2=𝑈;𝑅3,𝛽[𝑈;𝑅3]=𝜏′ Substituting each residual factor into the preceding row gives the chain 𝑇𝑓𝑖𝑟𝑠𝑡𝑖𝑛𝑑𝑢𝑐𝑡𝑖𝑜𝑛ℎ𝑦𝑝𝑜𝑡ℎ𝑒𝑠𝑖𝑠=ftv(Γ)∪𝑃𝑆1;𝑅1𝑠𝑒𝑐𝑜𝑛𝑑𝑖𝑛𝑑𝑢𝑐𝑡𝑖𝑜𝑛ℎ𝑦𝑝𝑜𝑡ℎ𝑒𝑠𝑖𝑠=ftv(Γ)∪𝑃𝑆1;𝑆2;𝑅2𝑀𝐺𝑈𝑓𝑎𝑐𝑡𝑜𝑟𝑖𝑧𝑎𝑡𝑖𝑜𝑛=ftv(Γ)∪𝑃𝑆1;𝑆2;𝑈;𝑅3. Thus substitution composition gives 𝑇=𝑆1;𝑆2;𝑈;𝑅3 on ftv(Γ)∪𝑃 and 𝜏′=𝛽[𝑈;𝑅3], exactly the returned pair.
Let. The target premises have the form Γ[𝑇]⊢s𝑒1:𝜌1,Γ[𝑇],𝑥:𝜎′⊢s𝑒2:𝜏′,𝜎′=GenΓ[𝑇](𝜌1). The first induction hypothesis gives 𝑇=𝑆1;𝑅1 on ftv(Γ)∪𝑃 and 𝜌1=𝜏1[𝑅1]. Put 𝜎=GenΓ[𝑆1](𝜏1), as W does. Lemma 3.14 gives 𝜎[𝑅1]⊒GenΓ[𝑆1;𝑅1](𝜏1[𝑅1])=𝜎′. Thus lemma 3.33 turns the second target premise into one under (Γ[𝑆1],𝑥:𝜎)[𝑅1]. 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 fv𝑆1(𝑃). It factors 𝑅1 through 𝑆2 as 𝑆2;𝑅2 on the body context and on fv𝑆1(𝑃); it also gives 𝜏′=𝜏2[𝑅2]. Thus 𝑇=𝑆1;𝑆2;𝑅2onftv(Γ)∪𝑃, which is the required factorization of W’s let pair. ◻
If Γ[𝑇]⊢s𝑒:𝜏′, then 𝖶(Γ,𝑒) succeeds. Writing its result as (𝑆,𝜏), there is a substitution 𝑅 such that 𝑇=ftv(Γ)𝑆;𝑅,𝜏′=𝜏[𝑅]. Consequently, if a declarative typing of 𝑒 exists under Γ[𝑇], then W succeeds and its result has this factorization property for a syntax-directed monotype instance of that typing.
Proof. Apply lemma 4.46 with 𝑃=∅; 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. ◻
If ftv(Γ)=∅ and 𝖶(Γ,𝑒)=(𝑆,𝜏), then Γ[𝑆]=Γ and GenΓ(𝜏) is a principal type scheme for 𝑒: it types 𝑒, and it is at least as general as every other scheme assignable to 𝑒 under Γ.
Proof of Corollary 3.37 — Principal schemes in a closed signature
Proof. The context equality is immediate because 𝑆 has no free context variable to change. Soundness followed by repeated Gen types 𝑒 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 𝜏[𝑅]. 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. ◻
Suppose 𝖶(Γ,𝑒)=(𝑆,𝜏). Under the resulting context Γ[𝑆], the scheme GenΓ[𝑆](𝜏) is principal. More precisely, it types 𝑒; and whenever Γ[𝑆]⊢s𝑒:𝜏′, there is a substitution 𝑅 which fixes Γ[𝑆] and satisfies 𝜏′=𝜏[𝑅].
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 𝑇=𝑆. It yields 𝑆=ftv(Γ)𝑆;𝑅,𝜏′=𝜏[𝑅]. The first equality says exactly that Γ[𝑆]=Γ[𝑆;𝑅]=Γ[𝑆][𝑅]. Thus 𝑅 fixes every free parameter of the resulting context, and the equations 𝜏′=𝜏[𝑅] and 𝑅|ftv(Γ[𝑆])=𝗂𝖽 satisfy the instance condition of lemma 3.9. Hence 𝜏′ is an instance of GenΓ[𝑆](𝜏). 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 Γ[𝑆]=Γ.
★★☆ Run W on the previously unsolved term 𝜆𝑓.𝜆𝑥.𝑓(𝑓(𝑓𝑥)). 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.
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 𝑃𝗍𝗐𝗂𝖼𝖾:=𝗅𝖾𝗍𝑡=𝗍𝗐𝗂𝖼𝖾𝗂𝗇𝑡𝑡. The bound calculation shows exactly the substitution threading hidden by a batch solve. Give 𝑓 the fresh type 𝛼 and 𝑥 the fresh type 𝛽. In the inner application 𝑓𝑥, both variable calls return the identity. A fresh 𝛾 stands for the result, so W calls 𝗎𝗇𝗂𝖿𝗒((𝛼≐𝛽→𝛾))=𝑈1,𝑈1=[𝛽→𝛾/𝛼]. The inner application therefore returns (𝑈1,𝛾). For the outer application 𝑓(𝑓𝑥), the first recursive call again gives provisional function type 𝛼, while the second returns (𝑈1,𝛾). The application clause must apply that second substitution to the provisional function type before unifying: 𝛼[𝑈1]=𝛽→𝛾≐𝛾→𝛿, where 𝛿 is the fresh result variable. Left-to-right decomposition and elimination give 𝑈2=[𝛿/𝛽,𝛿/𝛾]. Thus the body returns (𝑈1;𝑈2,𝛿). The two lambda clauses reapply the same composite to their provisional domains, so 𝖶(Γ0,𝗍𝗐𝗂𝖼𝖾)=(𝑆0,(𝛿→𝛿)→𝛿→𝛿),𝑆0=𝑈1;𝑈2=[(𝛿→𝛿)/𝛼,𝛿/𝛽,𝛿/𝛾]. 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 𝑈1 threaded from the argument call back into the function type.
Because 𝑆0 acts as the identity on Γ0, the let clause generalizes 𝛿 and extends the context by 𝑡:𝜎𝑡,𝜎𝑡:=∀𝛿.(𝛿→𝛿)→𝛿→𝛿. Now infer the application 𝑡𝑡. Its two variable calls both return the identity substitution, but they consume different fresh instance variables, 𝜖 and 𝜁: 𝜏𝑓=(𝜖→𝜖)→𝜖→𝜖,𝜏𝑎=(𝜁→𝜁)→𝜁→𝜁. Thus 𝑆𝑓=𝑆𝑎=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 𝑈3=[(𝜁→𝜁)/𝜖,((𝜁→𝜁)→(𝜁→𝜁))/𝜂], and the body call returns (𝑈3,𝜂[𝑈3]). The complete let clause therefore returns the pair 𝖶(Γ0,𝑃𝗍𝗐𝗂𝖼𝖾)=(𝑆0;𝑈3,(𝜁→𝜁)→𝜁→𝜁). In the outer let clause of definition 3.28, its first result is the substitution called 𝑆0 in this trace, and its second result is the substitution called 𝑈3 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 𝑡 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.
Evidence terms are 𝑑::=𝑥⟨¯𝜏⟩∣𝜆(𝑥:𝜏).𝑑∣𝑑1𝑑2∣𝗅𝖾𝗍𝑥:𝜎=𝑑1𝗂𝗇𝑑2. Here 𝑥⟨¯𝜏⟩ is a term constructor for explicit type application; square brackets remain reserved for metalevel substitution. In 𝗅𝖾𝗍𝑥:∀¯𝛼.𝜏1=𝑑1𝗂𝗇𝑑2, the prefix ¯𝛼 binds in 𝜏1 and in every type occurrence in 𝑑1, including lambda annotations, let annotations, and angle-bracket type arguments. The term variable 𝑥 binds only in 𝑑2. Evidence terms are identified up to consistent renaming of either kind of binder.
Define erasure by |𝑥⟨¯𝜏⟩|=𝑥,|𝜆(𝑥:𝜏).𝑑|=𝜆𝑥.|𝑑|,|𝑑1𝑑2|=|𝑑1||𝑑2|,|𝗅𝖾𝗍𝑥:𝜎=𝑑1𝗂𝗇𝑑2|=𝗅𝖾𝗍𝑥=|𝑑1|𝗂𝗇|𝑑2|. 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.
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.
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 𝑆. 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. ◻
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 𝐺 send every variable of ftv(𝜌)∖Θ to 𝖭𝖺𝗍 and fix all others. Since 𝐺 fixes Γ and 𝜏, the syntax-directed construction in lemma 3.32 preserves their heights and changes them to Γ⊢s𝑒1:𝜌[𝐺]→𝜏,Γ⊢s𝑒2:𝜌[𝐺], and ftv(𝜌[𝐺])⊆Θ. 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. ◻
Proof of Theorem 4.54 — Checked reconstruction from W
Proof. By theorem 3.35, W’s result has a declarative derivation Γ[𝑆]⊢𝑒:𝜏. The declarative-to-syntax-directed half of theorem 3.34 gives Γ[𝑆]⊢s𝑒:𝜏. 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 𝑃𝗍𝗐𝗂𝖼𝖾, W reconstructs, up to renaming of type variables, 𝗅𝖾𝗍𝑡:∀𝛿.(𝛿→𝛿)→𝛿→𝛿=𝜆(𝑓:𝛿→𝛿).𝜆(𝑥:𝛿).𝑓(𝑓𝑥)𝗂𝗇𝑡⟨𝜁→𝜁⟩𝑡⟨𝜁⟩. 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.
★★☆ Run W on 𝗅𝖾𝗍𝑐=𝜆𝑓.𝜆𝑔.𝜆𝑥.𝑓(𝑔𝑥)𝗂𝗇𝑐𝑐. 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.
Extend the type-constructor signature by the unary constructor 𝖫𝗂𝗌𝗍. 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 𝗇𝗂𝗅,𝖼𝗈𝗇𝗌𝑒1𝑒2,𝖼𝖺𝗌𝖾𝑒𝗈𝖿{𝗇𝗂𝗅↦𝑒0,𝖼𝗈𝗇𝗌ℎ𝑡↦𝑒1. The last form binds ℎ,𝑡 in 𝑒1. The schemes 𝗇𝗂𝗅:∀𝛼.𝖫𝗂𝗌𝗍(𝛼),𝖼𝗈𝗇𝗌:∀𝛼.𝛼→𝖫𝗂𝗌𝗍(𝛼)→𝖫𝗂𝗌𝗍(𝛼). 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
Γ⊢𝗇𝗂𝗅:𝖫𝗂𝗌𝗍(𝜏)
ListNil
Γ⊢𝑒1:𝜏Γ⊢𝑒2:𝖫𝗂𝗌𝗍(𝜏)
Γ⊢𝖼𝗈𝗇𝗌𝑒1𝑒2:𝖫𝗂𝗌𝗍(𝜏)
ListCons
Γ⊢s𝗇𝗂𝗅:𝖫𝗂𝗌𝗍(𝜏)
S-ListNil
Γ⊢s𝑒1:𝜏Γ⊢s𝑒2:𝖫𝗂𝗌𝗍(𝜏)
Γ⊢s𝖼𝗈𝗇𝗌𝑒1𝑒2:𝖫𝗂𝗌𝗍(𝜏)
S-ListCons
Here 𝜏 is a monotype; the nil rules may choose any such monotype. Declaratively, case analysis has the rule
The constructor clauses of W are equally explicit. For fresh 𝛼, 𝖶(Γ,𝗇𝗂𝗅)=(id,𝖫𝗂𝗌𝗍(𝛼)). For cons, compute (𝑆1,𝜏1)=𝖶(Γ,𝑒1),(𝑆2,𝜏2)=𝖶(Γ[𝑆1],𝑒2),𝑈=𝗎𝗇𝗂𝖿𝗒((𝜏2≐𝖫𝗂𝗌𝗍(𝜏1[𝑆2]))), and return (𝑆1;𝑆2;𝑈,𝖫𝗂𝗌𝗍(𝜏1[𝑆2;𝑈])).
For a constructor calculation, take Γ=𝑥:𝛼. The declarative rules give
(𝑥:𝛼)∈Γ
Γ⊢𝑥:𝛼
Var
Γ⊢𝗇𝗂𝗅:𝖫𝗂𝗌𝗍(𝛼)
ListNil
Γ⊢𝖼𝗈𝗇𝗌𝑥𝗇𝗂𝗅:𝖫𝗂𝗌𝗍(𝛼)
ListCons
W returns (id,𝛼) for the head and (id,𝖫𝗂𝗌𝗍(𝛽)) for the tail, with fresh 𝛽. The cons equation 𝖫𝗂𝗌𝗍(𝛽)≐𝖫𝗂𝗌𝗍(𝛼) decomposes to 𝛽≐𝛼, whose MGU is [𝛼/𝛽]; the constructor clause therefore returns ([𝛼/𝛽],𝖫𝗂𝗌𝗍(𝛼)).
For example, under Γ=𝑑:𝛼,𝑥𝑠:𝖫𝗂𝗌𝗍(𝛼), the body of 𝗁𝖾𝖺𝖽𝖮𝗋 has the complete declarative derivation
Here is the corresponding W clause, written in the order in which its substitutions are learned. Choose a fresh element type 𝛼 and compute (𝑆0,𝜏0)=𝖶(Γ,𝑒),𝑈=𝗎𝗇𝗂𝖿𝗒((𝜏0≐𝖫𝗂𝗌𝗍(𝛼))),(𝑆1,𝜌0)=𝖶(Γ[𝑆0;𝑈],𝑒0),(𝑆2,𝜌1)=𝖶((Γ[𝑆0;𝑈;𝑆1],ℎ:𝛼[𝑈;𝑆1],𝑡:𝖫𝗂𝗌𝗍(𝛼[𝑈;𝑆1])),𝑒1),𝑉=𝗎𝗇𝗂𝖿𝗒((𝜌0[𝑆2]≐𝜌1)). It returns (𝑆0;𝑈;𝑆1;𝑆2;𝑉,𝜌1[𝑉]).
Definition 4.56, Definition 4.57 give one W clause for each of 𝗇𝗂𝗅, 𝖼𝗈𝗇𝗌, and 𝖼𝖺𝗌𝖾. 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.
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 𝑆2 and 𝑈, have types 𝑒1:𝜏1[𝑆2;𝑈],𝑒2:𝖫𝗂𝗌𝗍(𝜏1[𝑆2;𝑈]); apply S-ListCons. In the protected induction, a target nil typing has type 𝖫𝗂𝗌𝗍(𝜉). 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=𝖫𝗂𝗌𝗍(𝜏1[𝑆2]) true after substitution, so MGU factorization supplies 𝑈; 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 𝑈 solves 𝜏0≐𝖫𝗂𝗌𝗍(𝛼), the updated scrutinee has type 𝖫𝗂𝗌𝗍(𝛼[𝑈;𝑆1;𝑆2;𝑉]). The two pattern declarations have that element type. Because 𝑉 solves 𝜌0[𝑆2]≐𝜌1, both branches have type 𝜌1[𝑉]. Rule S-ListCase gives exactly the returned context and type.
For the protected induction, invert a target case derivation under Γ[𝑇]. For some target types 𝜉,𝜒, its premises are Γ[𝑇]⊢s𝑒:𝖫𝗂𝗌𝗍(𝜉),Γ[𝑇]⊢s𝑒0:𝜒,Γ[𝑇],ℎ:𝜉,𝑡:𝖫𝗂𝗌𝗍(𝜉)⊢s𝑒1:𝜒. Choose the W element variable 𝛼 fresh for the target derivation and let ̂𝑇 agree with 𝑇 everywhere except that 𝛼[̂𝑇]=𝜉. Freshness gives Γ[̂𝑇]=Γ[𝑇] and ̂𝑇=ftv(Γ)∪𝑃𝑇. Apply the first induction hypothesis to the scrutinee with protected set 𝑃∪{𝛼}. It returns 𝑆0,𝜏0 and a factor 𝑅0 such that ̂𝑇=ftv(Γ)∪𝑃∪{𝛼}𝑆0;𝑅0,𝜏0[𝑅0]=𝖫𝗂𝗌𝗍(𝜉). The scrutinee call receives the supply after 𝛼 was consumed, so 𝑆0 fixes 𝛼. Agreement at the added protected variable therefore gives 𝛼[𝑅0]=𝛼[𝑆0;𝑅0]=𝛼[̂𝑇]=𝜉. Together with the last equality, this says that 𝑅0 solves 𝜏0≐𝖫𝗂𝗌𝗍(𝛼). Let 𝑈 be its MGU. MGU factorization, extended on ftv(Γ[𝑆0])∪fv𝑆0(𝑃), gives 𝑅0=𝑈;𝑅𝑈 there. Therefore the nil target premise may be read under Γ[𝑆0;𝑈;𝑅𝑈]. Its induction hypothesis returns 𝑆1,𝜌0 and 𝑅1 with 𝑅𝑈=ftv(Γ[𝑆0;𝑈])∪fv𝑆0;𝑈(𝑃)𝑆1;𝑅1,𝜌0[𝑅1]=𝜒.
Protect additionally the variables of 𝛼[𝑈;𝑆1] and 𝜌0. The preceding factorizations identify the target pattern context with the W pattern context after 𝑅1: 𝛼[𝑈;𝑆1;𝑅1]=𝜉,𝖫𝗂𝗌𝗍(𝛼[𝑈;𝑆1;𝑅1])=𝖫𝗂𝗌𝗍(𝜉). The cons-branch induction hypothesis consequently returns 𝑆2,𝜌1 and 𝑅2 such that 𝑅1=𝑆2;𝑅2ontheinheritedandaddedprotectedvariables,𝜌1[𝑅2]=𝜒. Protected agreement also gives 𝜌0[𝑆2;𝑅2]=𝜌0[𝑅1]=𝜒; hence 𝑅2 solves the final equation 𝜌0[𝑆2]≐𝜌1. If 𝑉 is its MGU, extend its factorization to obtain 𝑅2=𝑉;𝑅3 on every inherited protected variable. Put 𝑃0=𝑃∪{𝛼},𝑃1=fv𝑆0;𝑈(𝑃),𝑃2=fv𝑆0;𝑈;𝑆1(𝑃)∪ftv(𝛼[𝑈;𝑆1])∪ftv(𝜌0). These are the protected arguments supplied to the three recursive induction hypotheses. The proof state is stagestateafterthestage𝑒Γ;𝑃0;𝜏0[𝑅0]=𝖫𝗂𝗌𝗍(𝜉)𝑈𝜏0≐𝖫𝗂𝗌𝗍(𝛼);𝑅0=𝑈;𝑅𝑈,𝛼[𝑈;𝑅𝑈]=𝜉𝑒0Γ[𝑆0;𝑈];𝑃1;𝑅𝑈=𝑆1;𝑅1,𝜌0[𝑅1]=𝜒𝑒1Γ[𝑆0;𝑈;𝑆1],ℎ:𝛼[𝑈;𝑆1],𝑡:𝖫𝗂𝗌𝗍(𝛼[𝑈;𝑆1]);𝑃2;𝑅1=𝑆2;𝑅2,𝜌1[𝑅2]=𝜒𝑉𝜌0[𝑆2]≐𝜌1;𝑅2=𝑉;𝑅3,𝜌1[𝑉;𝑅3]=𝜒 Associativity now assembles the five stages: 𝑇=ftv(Γ)∪𝑃̂𝑇=ftv(Γ)∪𝑃𝑆0;𝑅0=ftv(Γ)∪𝑃𝑆0;𝑈;𝑅𝑈=ftv(Γ)∪𝑃𝑆0;𝑈;𝑆1;𝑅1=ftv(Γ)∪𝑃𝑆0;𝑈;𝑆1;𝑆2;𝑅2=ftv(Γ)∪𝑃𝑆0;𝑈;𝑆1;𝑆2;𝑉;𝑅3. Finally 𝜌1[𝑉;𝑅3]=𝜒, 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. ◻
Consider 𝗁𝖾𝖺𝖽𝖮𝗋:=𝜆𝑑.𝜆𝑥𝑠.𝖼𝖺𝗌𝖾𝑥𝑠𝗈𝖿{𝗇𝗂𝗅↦𝑑;𝖼𝗈𝗇𝗌ℎ𝑡↦ℎ}. Give 𝑑 type 𝛼 and 𝑥𝑠 provisional type 𝛽. The clause above chooses a fresh element type 𝛾, so the scrutinee equation is 𝛽≐𝖫𝗂𝗌𝗍(𝛾). 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 𝗁𝖾𝖺𝖽𝖮𝗋:∀𝛼.𝛼→𝖫𝗂𝗌𝗍(𝛼)→𝛼. The unused tail 𝑡 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 𝖼𝖺𝗌𝖾𝑥𝑠𝗈𝖿𝖼𝗈𝗇𝗌ℎ𝑡↦ℎ has perfectly consistent type equations and still has no result on 𝗇𝗂𝗅.
The nil contraction selects 𝑒0; the cons contraction substitutes the stored head and tail into 𝑒1. Together they select a branch for each closed list constructor, which is the coverage needed by safety.
★★☆ Run the displayed list clause on the body of 𝗁𝖾𝖺𝖽𝖮𝗋 under 𝑑:𝛼,𝑥𝑠:𝛽. 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.
Extend the type-constructor signature by the unary constructor 𝖱𝖾𝖿. 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 𝖱𝖾𝖿, the type 𝖴𝗇𝗂𝗍, and term forms 𝗋𝖾𝖿𝑒,!𝑒,𝑒1:=𝑒2,𝗎𝗇𝗂𝗍, with the expected monomorphic rules
A run-time state is a pair (𝜇,𝑒), where the finite store 𝜇 maps locations to closed values. Call-by-value compatibility closes the following root steps: (𝜇,𝗋𝖾𝖿𝑣)⟶(𝜇[ℓ↦𝑣],ℓ)(ℓ∉dom(𝜇)),(𝜇,!ℓ)⟶(𝜇,𝜇(ℓ)),(𝜇,ℓ:=𝑣)⟶(𝜇[ℓ↦𝑣],𝗎𝗇𝗂𝗍). For the program below, the required context frames are 𝗋𝖾𝖿E, E:=𝑒, 𝑣:=E, !E, and 𝗅𝖾𝗍𝑥=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: 𝑃𝗋𝖾𝖿:=𝗅𝖾𝗍𝑟=𝗋𝖾𝖿(𝜆𝑥.𝑥)𝗂𝗇𝗅𝖾𝗍𝑢=𝑟:=(𝜆𝑥.𝗌𝗎𝖼𝖼𝑥)𝗂𝗇(!𝑟)𝗍𝗋𝗎𝖾. The first definition is assigned a provisional type 𝖱𝖾𝖿(𝛼→𝛼). Naive generalization gives 𝑟:∀𝛼.𝖱𝖾𝖿(𝛼→𝛼). The assignment instantiates this scheme at 𝖭𝖺𝗍, while the final dereferencing instantiates it at 𝖡𝗈𝗈𝗅. Thus the program receives type 𝖡𝗈𝗈𝗅.
At run time, however, 𝗋𝖾𝖿(𝜆𝑥.𝑥) allocates one location ℓ. The assignment overwrites that same location with 𝜆𝑥.𝗌𝗎𝖼𝖼𝑥. The final line therefore reduces to 𝗌𝗎𝖼𝖼𝗍𝗋𝗎𝖾, 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 𝗌𝗎𝖼𝖼 and 𝗍𝗋𝗎𝖾. 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.
Write L𝗋𝖾𝖿 for pure HM extended by 𝖴𝗇𝗂𝗍, 𝖱𝖾𝖿, locations, unit, allocation, dereference, assignment, and the rules in this section. Write L𝗅𝗂𝗌𝗍+𝗋𝖾𝖿 for L𝗋𝖾𝖿 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.
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
Γ⊢𝑣:𝜏1Γ,𝑥:GenΓ(𝜏1)⊢𝑒2:𝜏2
Γ⊢𝗅𝖾𝗍𝑥=𝑣𝗂𝗇𝑒2:𝜏2
Let-Gen
Γ⊢𝑒1:𝜏1Γ,𝑥:𝜏1⊢𝑒2:𝜏2
Γ⊢𝗅𝖾𝗍𝑥=𝑒1𝗂𝗇𝑒2:𝜏2
Let-Mono
In Let-Gen, the metavariable 𝑣 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 L𝗅𝗂𝗌𝗍+𝗋𝖾𝖿, 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 (𝑆1,𝜏1)=𝖶(Γ,𝑒1) call the body under Γ[𝑆1],𝑥:𝜏1; if it returns (𝑆2,𝜏2), return (𝑆1;𝑆2,𝜏2). For the four new term forms: 𝖶(Γ,𝗎𝗇𝗂𝗍)=(id,𝖴𝗇𝗂𝗍),𝖶(Γ,𝗋𝖾𝖿𝑒)=let(𝑆,𝜏)=𝖶(Γ,𝑒)in(𝑆,𝖱𝖾𝖿(𝜏)),𝖶(Γ,!𝑒)=let(𝑆,𝜏)=𝖶(Γ,𝑒),𝑈=𝗎𝗇𝗂𝖿𝗒((𝜏≐𝖱𝖾𝖿(𝛼)))in(𝑆;𝑈,𝛼[𝑈]),𝖶(Γ,𝑒1:=𝑒2)=let(𝑆1,𝜏1)=𝖶(Γ,𝑒1),(𝑆2,𝜏2)=𝖶(Γ[𝑆1],𝑒2),𝑈=𝗎𝗇𝗂𝖿𝗒((𝜏1[𝑆2]≐𝖱𝖾𝖿(𝜏2)))in(𝑆1;𝑆2;𝑈,𝖴𝗇𝗂𝗍), 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:
Γ⊢s𝑣:𝜏1Γ,𝑥:GenΓ(𝜏1)⊢s𝑒2:𝜏2
Γ⊢s𝗅𝖾𝗍𝑥=𝑣𝗂𝗇𝑒2:𝜏2
S-Let-Gen
Γ⊢s𝑒1:𝜏1Γ,𝑥:𝜏1⊢s𝑒2:𝜏2
Γ⊢s𝗅𝖾𝗍𝑥=𝑒1𝗂𝗇𝑒2:𝜏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
Suppose Γ and Γ′ have the same declared term variables and Γ(𝑥)⊒Γ′(𝑥) for each 𝑥. Fix either signature in definition 4.62. If its syntax-directed judgment derives Γ′⊢s𝑒:𝜏, then it also derives Γ⊢s𝑒:𝜏.
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 L𝗅𝗂𝗌𝗍+𝗋𝖾𝖿, 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. ◻
For a monotype 𝜏 and either signature of definition 4.62, value-restricted declarative typing derives Γ⊢𝑒:𝜏 if and only if the syntax-directed judgment for the same signature derives Γ⊢s𝑒:𝜏.
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 Γ,𝑥:𝜏1. Since GenΓ(𝜏1)⊒𝜏1,lemma 4.64 transports that body derivation to Γ,𝑥: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 L𝗅𝗂𝗌𝗍+𝗋𝖾𝖿, the nil, cons, and case rules translate one-for-one in both directions. These cases exhaust both extended systems. ◻
Let 𝑃 be finite and suppose the complete syntax-directed reference signature L𝗋𝖾𝖿, or the cumulative signature L𝗅𝗂𝗌𝗍+𝗋𝖾𝖿, derives Γ[𝑇]⊢s𝑒:𝜏′. Use a legal supply disjoint from 𝑃∪vars(𝑇) and from the variables of that finite derivation. The extended W succeeds; if it returns (𝑆,𝜏), some 𝑅 satisfies 𝑇=ftv(Γ)∪𝑃𝑆;𝑅,𝜏′=𝜏[𝑅].
Proof of Lemma 4.66 — Protected induction for the value-restricted signature
Proof. Induct on the syntax-directed derivation, generalized over Γ,𝑇,𝑃. The pure cases are exactly lemma 4.46. For L𝗅𝗂𝗌𝗍+𝗋𝖾𝖿, 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 Γ[𝑇]⊢s𝑒1:𝜌1,Γ[𝑇],𝑥:𝜌1⊢s𝑒2:𝜏′. The first induction hypothesis returns (𝑆1,𝜏1) and 𝑅1 with 𝑇=ftv(Γ)∪𝑃𝑆1;𝑅1,𝜏1[𝑅1]=𝜌1. Thus the body premise is a derivation under (Γ[𝑆1],𝑥:𝜏1)[𝑅1]. Apply the second induction hypothesis while protecting fv𝑆1(𝑃)∪ftv(𝜏1). It returns (𝑆2,𝜏2) and 𝑅2 such that 𝑅1=ftv(Γ[𝑆1])∪fv𝑆1(𝑃)∪ftv(𝜏1)𝑆2;𝑅2,𝜏2[𝑅2]=𝜏′. Extensionality and associativity now give 𝑇=ftv(Γ)∪𝑃𝑆1;𝑆2;𝑅2, 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 Γ[𝑇]⊢s𝑒1:𝖱𝖾𝖿(𝜌),Γ[𝑇]⊢s𝑒2:𝜌. The first induction hypothesis gives 𝑇=ftv(Γ)∪𝑃𝑆1;𝑅1,𝜏1[𝑅1]=𝖱𝖾𝖿(𝜌). Read the second target premise under Γ[𝑆1][𝑅1], protect fv𝑆1(𝑃)∪ftv(𝜏1), and apply the second induction hypothesis: 𝑅1=𝑆2;𝑅2onthatprotectedset,𝜏2[𝑅2]=𝜌. Consequently 𝜏1[𝑆2;𝑅2]=𝜏1[𝑅1]=𝖱𝖾𝖿(𝜌)=𝖱𝖾𝖿(𝜏2[𝑅2]), so 𝑅2 solves W’s equation 𝜏1[𝑆2]≐𝖱𝖾𝖿(𝜏2). If 𝑈 is its MGU, factor 𝑅2=𝑈;𝑅3 on the equation variables and extend 𝑅3 on the inherited protected set by lemma 4.45. Put 𝑃1=fv𝑆1(𝑃)∪ftv(𝜏1). The proof state is stagestateafterthestage𝑒1Γ;ftv(Γ)∪𝑃;𝑇=𝑆1;𝑅1,𝜏1[𝑅1]=𝖱𝖾𝖿(𝜌)𝑒2Γ[𝑆1];ftv(Γ[𝑆1])∪𝑃1;𝑅1=𝑆2;𝑅2,𝜏2[𝑅2]=𝜌𝑈Γ[𝑆1;𝑆2];ftv(Γ[𝑆1;𝑆2])∪fv𝑆1;𝑆2(𝑃);𝑅2=𝑈;𝑅3𝜏1[𝑆2;𝑈;𝑅3]=𝖱𝖾𝖿(𝜏2[𝑈;𝑅3]) Composing the three residual factors gives 𝑇=ftv(Γ)∪𝑃𝑆1;𝑆2;𝑈;𝑅3,𝖴𝗇𝗂𝗍=𝖴𝗇𝗂𝗍[𝑅3].
S-Deref. Inversion gives Γ[𝑇]⊢s𝑒0:𝖱𝖾𝖿(𝜌). The one recursive induction hypothesis gives 𝑇=𝑆;𝑅0 on the inherited protected set and 𝜏[𝑅0]=𝖱𝖾𝖿(𝜌). Send W’s fresh 𝛼 to 𝜌; then 𝑅0 solves 𝜏≐𝖱𝖾𝖿(𝛼). MGU factorization gives 𝑅0=𝑈;𝑅, extended on the inherited protected variables, and 𝛼[𝑈;𝑅]=𝜌. This is precisely the assignment calculation with its second recursive branch and the outer 𝖱𝖾𝖿 on 𝜏2 removed.
S-Unit and S-Ref. Unit returns (id,𝖴𝗇𝗂𝗍); take 𝑅=𝑇 and preserve agreement on 𝑃. For reference construction, the recursive induction hypothesis gives 𝜏[𝑅]=𝜌, and structural substitution gives 𝖱𝖾𝖿(𝜏)[𝑅]=𝖱𝖾𝖿(𝜌). These cases exhaust the added forms and establish the strengthened claim. ◻
For each syntax-directed signature L𝗋𝖾𝖿 and L𝗅𝗂𝗌𝗍+𝗋𝖾𝖿, 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 𝑈 changes the recursive result from 𝜏 to 𝖱𝖾𝖿(𝛼[𝑈]), so S-Deref returns 𝛼[𝑈]. For assignment, the two induction hypotheses, transported successively by 𝑆2 and 𝑈, have types 𝑒1:𝜏1[𝑆2;𝑈]=𝖱𝖾𝖿(𝜏2[𝑈]),𝑒2:𝜏2[𝑈];S-Assign returns 𝖴𝗇𝗂𝗍. The generalized and monomorphic let clauses rebuild S-Let-Gen and S-Let-Mono, respectively. For L𝗅𝗂𝗌𝗍+𝗋𝖾𝖿, the three list cases are theorem 4.58; they are absent from L𝗋𝖾𝖿.
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 𝑃=∅, 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 𝗋𝖾𝖿(𝜆𝑥.𝑥) is not a generalizable form, so 𝑟 receives the single monotype 𝖱𝖾𝖿(𝛼→𝛼). The assignment forces 𝛼=𝖭𝖺𝗍; the final use then asks for 𝛼=𝖡𝗈𝗈𝗅. Declaratively there is no monotype satisfying both uses; algorithmically W exposes the clash 𝖭𝖺𝗍≐𝖡𝗈𝗈𝗅 and rejects it by constructor disjointness.
The run-time safety result uses exactly the cumulative signature L𝗅𝗂𝗌𝗍+𝗋𝖾𝖿, the states and three reference roots of definition 4.61, and the two list contractions of section 3.8. Its closed values are 𝑣::=𝜆𝑥.𝑒∣𝗎𝗇𝗂𝗍∣ℓ∣𝗇𝗂𝗅∣𝖼𝗈𝗇𝗌𝑣𝑣. 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::={𝗇𝗂𝗅↦𝑒0;𝖼𝗈𝗇𝗌ℎ𝑡↦𝑒1},E::=[]∣E𝑒∣𝑣E∣𝗅𝖾𝗍𝑥=E𝗂𝗇𝑒∣𝗋𝖾𝖿E∣!E∣E:=𝑒∣𝑣:=E∣𝖼𝗈𝗇𝗌E𝑒∣𝖼𝗈𝗇𝗌𝑣E∣𝖼𝖺𝗌𝖾E𝗈𝖿B. For every root step (𝜇,𝑟)⟶(𝜇′,𝑟′), compatibility gives (𝜇,E⟨𝑟⟩)⟶(𝜇′,E⟨𝑟′⟩). 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 𝐸 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(Σ)Γ⊢Σℓ:𝖱𝖾𝖿(Σ(ℓ)), 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 Γ⊢Σ𝑒:𝜎, but replaces Gen and the generalizable-form let rule by
Γ⊢Σ𝑒:𝜎𝛼∉ftv(Γ)∪ftv(Σ)
Γ⊢Σ𝑒:∀𝛼.𝜎
GenΣ
Γ⊢Σ𝑣:𝜏1Γ,𝑥:GenΓ,Σ(𝜏1)⊢Σ𝑒2:𝜏2
Γ⊢Σ𝗅𝖾𝗍𝑥=𝑣𝗂𝗇𝑒2:𝜏2
Let-GenΣ
Rule Let-Mono and every nongeneralizing rule of L𝗅𝗂𝗌𝗍+𝗋𝖾𝖿 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Γ,Σ(𝖱𝖾𝖿(𝜏)) 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,Σ𝑒:𝜏 for the corresponding run-time syntax-directed judgment. It lifts every nongeneralizing syntax-directed rule of L𝗅𝗂𝗌𝗍+𝗋𝖾𝖿, and adds the two rules
ℓ∈dom(Σ)
Γ⊢s,Σℓ:𝖱𝖾𝖿(Σ(ℓ))
S-Loc
Γ⊢s,Σ𝑣:𝜏1Γ,𝑥:GenΓ,Σ(𝜏1)⊢s,Σ𝑒2:𝜏2
Γ⊢s,Σ𝗅𝖾𝗍𝑥=𝑣𝗂𝗇𝑒2:𝜏2
S-Let-GenΣ
Here 𝑣 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.
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. ◻
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. ◻
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 𝗇𝗂𝗅 or 𝖼𝗈𝗇𝗌; constructor disjointness excludes the remaining value forms. ◻
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. ◻
Proof of Lemma 4.73 — Store-indexed value substitution
Proof. Freshen every term binder away from 𝑥 and the free variables of 𝑣, then induct on the second derivation. A variable leaf for 𝑥 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 𝑣 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 𝑦. The two induction hypotheses give Γ⊢Σ𝑣1[𝑣/𝑥]:𝜏1,Γ,𝑦:GenΓ,𝑥:𝜎,Σ(𝜏1)⊢Σ𝑒2[𝑣/𝑥]:𝜏2. Removing 𝑥:𝜎 can only enlarge the generalized prefix, so GenΓ,Σ(𝜏1)⊒GenΓ,𝑥:𝜎,Σ(𝜏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. ◻
If 𝜇:Σ and ⋅⊢Σ𝑒:𝜏 for a closed run-time expression typed by definition 3.39, definition 4.68, then either 𝑒 is a value or there are Σ′⊇Σ, 𝜇′:Σ′, and 𝑒′ such that (𝜇,𝑒)⟶(𝜇′,𝑒′)and⋅⊢Σ′𝑒′:𝜏. 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 𝑣:𝜏1, and the rule side condition permits repeated GenΣ, now excluding ftv(Γ)∪ftv(Σ). Hence Γ⊢Σ𝑣: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 Γ⊢Σ𝑣:𝜌,Γ⊢Σ𝑤:𝖫𝗂𝗌𝗍(𝜌),Γ,ℎ:𝜌,𝑡:𝖫𝗂𝗌𝗍(𝜌)⊢Σ𝑒1:𝜏. Freshen ℎ,𝑡, weaken the two value typings under the other pattern declaration, and apply lemma 4.73 first to ℎ and then to 𝑡. The result is Γ⊢Σ𝑒1[𝑣/ℎ,𝑤/𝑡]:𝜏, exactly the contractum typing.
For a compatible step, induct on E. The child induction hypothesis may enlarge Σ to Σ′. In a unary frame 𝗋𝖾𝖿[] 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 []:=𝑒2, the child retains type 𝖱𝖾𝖿(𝜌) under Σ′; weakening gives ⋅⊢Σ′𝑒2:𝜌, so S-Assign reconstructs 𝖴𝗇𝗂𝗍. In 𝑣1:=[], weaken the fixed premise 𝑣1:𝖱𝖾𝖿(𝜌) instead. Application and cons use the same two calculations for their left and right frames.
For 𝗅𝖾𝗍𝑥=[]𝗂𝗇𝑒2, first weaken the body premise under 𝑥:𝜏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 E𝑒, the already evaluated function in 𝑣E, 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. ◻
★★☆ Give the complete failed W calculation for 𝑃𝗋𝖾𝖿 under definition 3.39. Identify the equation that first fixes the cell’s argument type to 𝖭𝖺𝗍 and the later equation that clashes with 𝖡𝗈𝗈𝗅. Then verify that 𝗅𝖾𝗍𝑖𝑑=𝜆𝑥.𝑥𝗂𝗇⋯ still generalizes. A complete calculation is given in appendix B.
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 𝜆𝑓.𝗅𝖾𝗍𝑛=𝑓𝗓𝖾𝗋𝗈𝗂𝗇𝑓𝗍𝗋𝗎𝖾 would need two instances of 𝑓, one with domain 𝖭𝖺𝗍 and one with domain 𝖡𝗈𝗈𝗅. Rule Lam gives 𝑓 one monotype, and W therefore produces incompatible equations for that single arrow domain. If a language allowed the rank-two annotation 𝑓:∀𝛼.𝛼→𝛼, 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 (∀𝛼.𝛼→𝛼)→𝖭𝖺𝗍 is outside the grammar of definition 3.6, even when its body merely applies the argument at 𝖭𝖺𝗍.
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, (Λ𝛼.𝜆(𝑥:𝛼).𝑥)⟨∀𝛼.𝛼→𝛼⟩(Λ𝛼.𝜆(𝑥:𝛼).𝑥). Read Λ𝛼.𝑑 here as a static type abstraction binding the type variable 𝛼 in 𝑑, and 𝑑⟨𝜏⟩ 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.
★☆☆ 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.
For input (Γ,𝑒), Algorithm W traverses 𝑒, 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 𝑒 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.
★★☆ Work under Γ0,𝑓:𝜑, where 𝜑 is an unquantified monotype variable. Infer W’s principal pair for 𝗅𝖾𝗍𝑘=𝜆𝑥.𝑓𝗂𝗇𝑘𝗓𝖾𝗋𝗈. State separately the variables inherited from the type of 𝑓 and those created while typing 𝑘. Verify directly that generalizing 𝛼→𝜑 at the let quantifies 𝛼 but not 𝜑. This is the smallest useful test of the let clause in definition 3.28.
★★☆ Run unification on (𝛼≐𝛽→𝛾,𝛽≐𝖭𝖺𝗍,𝛾≐𝛿) 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.
★★☆ Type, under the value restriction, 𝗅𝖾𝗍𝑟=𝗋𝖾𝖿(𝜆𝑥.𝑥)𝗂𝗇𝗅𝖾𝗍𝑠=𝑟𝗂𝗇𝑠. Which let may generalize? Explain why 𝑟 and 𝑠 still denote one cell and must share one monomorphic reference type.
★☆☆ Show that 𝖭𝖺𝗍 and ∀𝛼.𝖭𝖺𝗍 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?
★★☆ The expression (𝜆𝑓.𝑓)(𝜆𝑥.𝑥) is not a generalizable form but reduces without effects to the identity. Analyze 𝗅𝖾𝗍𝑖=(𝜆𝑓.𝑓)(𝜆𝑥.𝑥)𝗂𝗇𝗅𝖾𝗍𝑛=𝑖𝗓𝖾𝗋𝗈𝗂𝗇𝑖𝗍𝗋𝗎𝖾. Locate the rejection under Let-Mono. Then explain why an effect analysis could accept this program without accepting 𝑃𝗋𝖾𝖿: which absence of allocation or mutation must it certify?
★★★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 𝗎𝗇𝗂𝖿𝗒(𝐸)=𝑈, check that each equation of 𝐸[𝑈] 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 𝗍𝗐𝗂𝖼𝖾, infer the trace result of section 3.7 up to alpha-renaming, and reject 𝜆𝑥.𝑥𝑥 by the occurs check. It must also run the open context 𝑓:𝛼0⊢𝜆𝑥.𝑓, 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 𝐸, the returned 𝑈, and that every member of 𝐸[𝑈] 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.