Lectures onType Theory
Chapter 7
Chapter 7Core route

Row Polymorphism and Extensible Records and Variants

A record is a finite collection of values tagged by distinct names, called labels; its field at label is retrieved by the selector r.. Mathematically, a closed record is an element of the finite indexed product of its field types: it chooses one value at every label. A variant instead chooses one label and one value of the type associated with that label. The corresponding sum of alternatives is called the indexed coproduct of the field types. A row is the finite partial map from labels to component types that indexes either construction.

A record selector is easy to type when the whole record is known. For example, λr.r.port:Rec({host:String,port:Nat})Nat. But this type fixes something that the program does not use: the selector works just as well when the record has a timeout, a log handle, or fifty other fields. Repeating one type for every possible remainder is not a solution, and ordinary Hindley–Milner substitutions replace type variables by types, not by rows of fields.

Extension makes the obstruction sharper. We want one principal type for withSecure:=λr.{secure=truer}, and we want the result to retain every field of r. A type variable for the entire argument loses those fields; a fixed record type chooses them in advance. The missing object is a variable ranging over the row of untouched fields.

The same objects occur outside software records. A measurement result can be a record of named channels, while a tagged experimental outcome is a variant; row polymorphism lets a calculation mention the channels or outcomes it uses while remaining uniform in the unmentioned part of the index family.

Rows are finite maps: each label occurs at most once, and field order is irrelevant. The judgment ρ\ proves that extending ρ at preserves this invariant. Rows with duplicate labels and first-match selection obey a different equality.

Rows and the strictness invariant

Fix a countably infinite set of labels. Labels are written ,m,n; names such as host and port are particular labels. Type variables α,β, and row variables ξ,ζ, are disjoint countable supplies.

Definition 4.1 — Types, rows, and lacks predicates

Types and rows are formed simultaneously by τ::=αNatBoolStringττRec(ρ)Var(ρ),ρ::=ξϵρ{:τρ}. Here ϵρ is the empty row. A lacks predicate is ρ\, read as the assertion that row ρ lacks label . The backslash is a relation between a row and a label, not row subtraction or set difference. A finite set of predicates is denoted P. Type substitutions send type variables to types and row variables to rows, and act structurally on both sorts and on predicates. A row substitution is allowed only when it sends every assumed lacks fact to a true lacks fact; otherwise substitution could create a duplicate label.

The four operations that later share a minus-like glyph have different input and output sorts: ρ\lacks propositionρmetalevel deletion from a finite rowρresidual row returned by constrained insertionesource term restricting a record.

The braces in {:τρ} construct a row, not a record type; Rec and Var turn the same row into a record type and a variant type. Thus Var abbreviates variant; it never denotes a term or row variable. For a closed row ρ, these types denote respectively the indexed product and indexed coproduct over dom(ρ). We abbreviate nested extensions by {1:τ1,,k:τkρ},{1:τ1,,k:τk}:={1:τ1,,k:τkϵρ}.

The grammar alone permits {:τ,:υρ}. The formation judgment now excludes it.

Definition 4.2 — Constraint entailment and strict formation

The function simp scans a row until it finds , reaches the empty row, or reaches an open tail. It is defined by structural recursion on the row argument: simp(ϵρ\)=,simp({m:τρ}\)=simp(ρ\)(m),simp({:τρ}\)=,simp(ξ\)=ξ\. The row-variable clause returns a normal form; simp is a total function, not a term-reduction relation. For a set, apply it to every predicate, discard , fail at , and remove duplicates. Write nf(P) for the resulting set. A predicate context is always normalized: every member therefore has form ξ\. In particular it is satisfiable, by sending every row variable to ϵρ. For example, simp({host:Stringξ}\port)=ξ\port. For a one-predicate calculation, write pq when simp(p)=q.

For normalized P, the entailment Pρ\ is generated by

ξ\P
Pξ\
L-Assume
Pϵρ\
L-Empty
Pρ\m
P{m:τρ}\
L-Extend

For normalized predicate contexts, write QP when Q entails every predicate in P.

For example, the two rule applications needed to pass a displayed field distinct from the requested label are ξ\portPPξ\portLAssumehostportP{host:Stringξ}\portLExtend.

Definition 7.3 — Admissible substitution

An admissible substitution S:PQ is a finite, sort-preserving substitution for which every nonidentity type image is a Q-formed type, every nonidentity row image is a Q-formed strict row, nf(P[S]) exists, and Qnf(P[S]). Thus the target may contain more information than substitution alone produces, but it may not license an ill-formed image. All substitution statements below use admissible substitutions; an attempted substitution whose normalized context is is rejected rather than treated as a map of strict rows. The notation S:PQ records the source and target predicate contexts of the substitution. It is not a function type inside the object language.

Definition 7.4 — Strict formation

Write Pρ row for strict row formation. The empty row and every row variable are rows. Type variables and the three base types are types; arrows are formed componentwise. The row-specific rules are

Pξ row
Row-Var
Pϵρ row
Row-Empty
Pρ rowPτ typePρ\
P{:τρ} row
Row-Ext

The remaining type-formation rules are Pτ typePυ typePτυ typeTyArrowPρ rowPRec(ρ) typeTyRecordPρ rowPVar(ρ) typeTyVariant.

The definitions are well founded. Simplification and lacks entailment recurse on the row tail; strict formation also recurses on a smaller tail and uses, but never defines, lacks entailment.

For example, ξ\port{host:Stringξ}\port, because the assumption is the tail judgment ξ\port and hostport is the side condition of L-Extend. Hence ξ\port{port:Nat,host:Stringξ} row. By contrast, formation of {port:Bool,port:Natξ} would require a derivation of {port:Natξ}\port, and no rule can produce one.

Rows are unordered, but only distinct labels may pass one another.

Definition 4.3 — Row equality

Row equality =r on types and rows is the least two-sorted congruence containing {:τ,m:υρ}=r{m:υ,:τρ}(m), without a formation side condition. Under a predicate context P, write aPa when both expressions have the same sort, are well formed under P, and a=ra. Thus row equality permutes distinct fields, and type equality transports those permutations through Rec, Var, arrows, and field types.

Lemma 7.6 — Formation and constructor separation

  1. If a is a type or row formed under P and a=ra, then a is formed under P. More precisely, the raw equality can be represented by a finite chain of adjacent exchanges in constructor contexts, and every expression in that chain is formed under P.

  2. Type equality preserves and reflects outer constructors. In particular, τ1τ2Pυ1υ2τ1Pυ1 and τ2Pυ2,Rec(ρ)PRec(ρ)ρPρ,Var(ρ)PVar(ρ)ρPρ. No arrow, record, variant, base type, or type variable is equal to a type with another outer constructor; equal base types and type variables are identical.

Proof of Lemma 7.6 — Formation and constructor separation

Proof. Flatten a raw-equality derivation into a chain. Reflexivity gives the empty chain, symmetry reverses a chain, transitivity concatenates two chains, and congruence places every link in the same constructor context. Thus each link changes only one adjacent pair {:τ,m:υρ}{m:υ,:τρ},m, possibly below constructors.

One such link preserves formation. Formation of the left row says that ρ lacks m and that {m:υρ} lacks . Since m, the latter premise reduces to the fact that ρ lacks . These two facts build the right row in the opposite order. The reverse direction is the same calculation. If the exchange occurs inside a tail, the same calculation is performed there and each enclosing Row-Ext is rebuilt; its lacks premise is unchanged because an exchange of labels distinct from the queried label merely changes the order of two L-Extend steps. Type constructors and field types rebuild structurally. Induction along the flattened chain proves item 1, including formation of every intermediate expression.

An exchange has row sort. Placing it in a type context can change only the components below that fixed context; it cannot change the outer type constructor. Follow the chain and project its links into the two arrow components or the single Rec or Var component. The projected chains are the equalities displayed in item 2. A base type or type variable has no row-containing subexpression, so its chain is empty. ◻

Lemma 4.4 — Lacks respects row equality

If ρPρ, then Pρ\ exactly when Pρ\.

Proof of Lemma 4.4 — Lacks respects row equality

Proof. It suffices to inspect one adjacent exchange in its constructor context. If the exchanged pair belongs to the row whose lack of is being proved and either exchanged label is , neither side has a lacks derivation. If both labels differ from , two uses of L-Extend remove them in either order and leave the same tail judgment. An exchange inside a field type leaves that row unchanged. An exchange in its tail is handled by the induction hypothesis before rebuilding the identical outer L-Extend steps. Congruence, symmetry, and transitivity preserve the resulting equivalence. ◻

Thus {host:String,port:Natξ}P{port:Nat,host:Stringξ}, provided Pξ\host and Pξ\port. The order has changed; the label-to-type association has not.

Lemma 4.5 — Finite-map character of strict rows

Let Pρ row.

  1. Every displayed label occurs at most once before any row-variable tail.

  2. Two rows with the same tail variable (or both with empty tail) are equal exactly when their displayed fields determine the same finite map from labels to types, with field types compared by type equality.

  3. If :τ occurs in ρ, there is a row ρ, unique up to P, such that ρP{:τρ}.

Proof of Lemma 4.5 — Finite-map character of strict rows

Proof. For item 1, induction on formation. The premise of Row-Ext says the tail lacks the new label. For item 2, adjacent exchanges of distinct labels generate every permutation of a finite list, so equal finite maps give equal rows. Conversely, one exchange preserves the finite map; induction on an equality derivation proves the other direction.

For item 3, scan the displayed fields. At the desired label, delete its field. Before reaching it, exchange it leftward past each distinct label. Item 1 guarantees that there is only one field to delete. Any two scans leave the same finite map and tail, hence item 2 makes their results equal. ◻

The tempting equation {:τ,:υρ}{:υ,:τρ} is not merely absent: neither side is a strict row. If duplicates were admitted and the equation were added, selection would have to decide which is meant. A system that retains the first matching label can be coherent, but it is a different calculus.

Exercise 4.1

★☆☆ Assume P entails that ξ lacks host, port, and secure. Give adjacent-exchange derivations putting each of the three labels first in {host:String,port:Nat,secure:Boolξ}. Explain at which formation premise the same calculation fails if the last label is another port.

Qualified types and record operations

A lacks predicate is useful only if it can travel with a polymorphic function. We therefore refine the schemes of definition 3.6.

Definition 4.6 — Qualified schemes

A qualified type and a qualified scheme have forms χ::=Pτ,σ::=α¯ξ¯.Pτ. Read Pτ as type τ subject to the assumptions P. This double arrow belongs to qualified types; the mapsto arrow in a case term separates a pattern from its branch body. The prefix binds both sorts of variables. A ground substitution G satisfies P, written GgP, when every ρ\P becomes a closed strict row lacking . This notation is used in the ground-discharge interface below.

A symbolic instance of σ in an ambient predicate context P chooses a substitution T for exactly the quantified prefix, sends type variables to P-formed types and row variables to P-formed strict rows, and requires Pnf(P[T]). It then has type τ[T]. Thus a symbolic use retains residual row variables and discharges their lacks requirements from the ambient assumptions. A ground instance additionally requires every image of T to be closed; taking P= alone does not close a row-variable image. Generalize every type or row variable absent from Γ. A predicate mentioning a variable fixed by Γ remains outside the scheme as a residual assumption, because no use of the let-bound variable can discharge it. Formally, put X=ftv(P,τ)ftv(Γ),Pr={pPftv(p)X}. Then GenΓ(Pτ):=X.Pτ, where X lists all type variables first and then all row variables, using once-and-for-all orders on the two countable supplies. On type variables this is the fixed order of definition 3.12; the row-variable order extends that convention rather than replacing it. Generalization keeps all of P in the scheme. The residual predicates Pr mention variables fixed by the environment and must additionally remain assumptions of the enclosing let judgment. Thus the whole predicate set travels with the scheme, while the enclosing let also retains Pr outside it; otherwise an unused binding could hide an unsatisfied ambient requirement. Here ftv is deliberately two-sorted: it collects both free type variables and free row variables.

The constraint is a condition on instantiation, not a hidden field. For example, αξ.(ξ\port)Rec({port:αξ})α has an instance at every record row containing one port field. It has no instance in which ξ itself supplies another port.

The qualified judgment Γqe:Pτ records the constraints required by e. Contexts contain qualified schemes. We use a separate nullary signature Σ0={zero:Nat,true:Bool,false:Bool}{"s":Strings is a string}. Its constants are values. Thus a closed term has empty Γ without losing the base literals, and no unmentioned arrow-valued primitive can create a stuck application.

Definition 4.7 — Qualified typing

Let σ=α¯ξ¯.Qτ0. A symbolic instance chooses such a sorted, formation-preserving substitution T for exactly the displayed prefix. Qualified typing is generated by

Γ(x)=σPnf(Q[T])Pτ0[T] type
Γqx:Pτ0[T]
Q-Var
c:σ is in the fixed signaturePnf(Q[T])Pτ0[T] type
Γqc:Pτ0[T]
Q-Const
Γ,x:τ1qe:Pτ2
Γqλx.e:Pτ1τ2
Q-Lam
Γqe1:P1τ1τ2Γqe2:P2τ1nf(P1P2)=P
Γqe1e2:Pτ2
Q-App
Γqe1:P1τ1Γ,x:GenΓ(P1τ1)qe2:P2τ2nf(P1rP2)=P
Γqlet x=e1 in e2:Pτ2
Q-Let
Γqe:PττPτ
Γqe:Pτ
Q-Conv

Every judgment Γqe:Pτ presupposes that Γ is a context of P-formed schemes and that Pτ type. A monotype declaration abbreviates the scheme .τ.

The six rules already type a complete qualified let before any transport lemma is needed. With id=λx.x, the derivation x:αqx:αqλx.x:ααQLamid:α.ααqidzero:Natqlet id=λx.x in idzero:NatQLet uses the Nat instance at the body occurrence. A predicate-bearing let differs only in the residual set retained by Q-Let; the first such calculation follows the record rules below.

Qualified typing is declarative: it constrains instances and lets but chooses no fresh variables, work-list order, or unification strategy. Rules Q-Var and Q-Const instantiate constraints at use sites, while Q-Let is the sole generalization rule.

The opening programs force four saturated record operations before any transport theorem is required.

Definition 4.13 — Record terms and typing

Extend the pure let-language of definition 3.1 by e::={}{=ee}e.e. The empty record is the base case. Selection exposes a field after row conversion, and restriction retains the exposed tail:

Γq{}:Rec(ϵρ)
Q-Empty
Γqr:PRec({:τρ})
Γqr.:Pτ
Q-Select
Γqr:PRec({:τρ})
Γqr:PRec(ρ)
Q-Restrict

Extension joins the constraints of its two subterms with the exact absence fact that makes the result strict:

Γqa:P1τΓqr:P2Rec(ρ)nf(P1P2{ρ\})=P
Γq{=ar}:PRec({:τρ})
Q-Extend

Update is derived rather than primitive: {:=er}:={=er}. If e:α and r:Rec({:βξ}), restriction returns Rec(ξ) and extension returns Rec({:αξ}) under the constraint ξ\. The repeated tail ξ expresses preservation of every untouched field.

Lemma 7.12 — Predicate weakening

If P entails every predicate of P and Γqe:Pτ, then Γqe:Pτ, provided the displayed context and type are formed under P.

Proof of Lemma 7.12 — Predicate weakening

Proof. Induct on the typing derivation. Variable and constant leaves retain their instance entailments by transitivity. Lambda, application, and the record and variant rules use the induction hypotheses and normalization idempotence.

For Q-Let, keep the first premise Γqe1:P1τ1, its generalized declaration σ=GenΓ(P1τ1), and the residual split fixed. The induction hypothesis weakens only the body premise from P2 to P. Since the original conclusion has P=nf(P1rP2) and PP, every member of P1r and every member of P2 is entailed by P. Hence nf(P1rP)=P, where normalized duplicates are discarded and every residual assumption is already a consequence of P. Reapply Q-Let with conclusion context P. This argument does not require P to entail predicates that were generalized into σ; those remain checked only at instances of the bound variable. For Q-Conv, lacks conversion preserves the row equalities used by the final type conversion. ◻

Definition 4.8 — Substitution and qualified-context equivalence

For a finite two-sorted set X of variables, write SPT on X when the two substitutions have P-formed images there and α[S]Pα[T] for every type variable αX, while ξ[S]Pξ[T] for every row variable ξX. Literal list equality would be wrong: two factors may display the same strict row in different field orders.

Two scheme contexts are P-equivalent, written ΓPΓ, when they have the same term variables and every pair of corresponding schemes satisfies the following three conditions after common freshening:

  1. their quantified prefixes are identical;

  2. their bodies are raw-equal up to row permutation;

  3. for each predicate ρ\ in the normalized instance predicates of one scheme, the other contains a predicate ρ\ with the same label and ρPρ, and conversely.

Formation here is qualified-scheme formation. For a declaration of the form α¯ξ¯.Qστ, treat its bound prefix as parameters and form every predicate row and τ under nf(PQσ). Thus a scheme may use the very lacks facts that it records. The definition is deliberately structural rather than merely extensional at P. By lacks conversion, every symbolic instance usable at a variable leaf under P has a P-equal instance in the other context, in both directions.

For example, under P={ξ\host,ξ\port}, the declarations Γ1=x:Rec({host:String,port:Natξ}),Γ2=x:Rec({port:Nat,host:Stringξ}) form P-equivalent one-variable contexts. A variable leaf may use either declaration, and conversion identifies the two resulting record types. For a predicate-bearing pair, take y:ξ.(ξ\host,ξ\port)Rec({host:String,port:Natξ}),y:ξ.(ξ\port,ξ\host)Rec({port:Nat,host:Stringξ}). Normalization pairs the two host predicates and the two port predicates by label; each paired row is literally ξ.

Row permutation may change the written context without changing any symbolic instance. Admissible substitutions compose, and generalization retains the environmental lacks assumptions needed after substitution. The following transport lemmas state these facts.

Lemma 4.9 — Changing the predicate context

Suppose ΓPΓ, and suppose a predicate context Q forms every corresponding scheme in Γ and Γ in this qualified sense. Then ΓQΓ, and ftv(Γ)=ftv(Γ).

Proof of Lemma 4.9 — Changing the predicate context

Proof. Work one corresponding declaration at a time, after commonly freshening its quantified prefix. Raw equality is independent of a predicate context, and it preserves every free variable. Under the common internal predicate set Qσ, the assumed qualified Q-formation therefore turns the same raw body equality into equality under nf(QQσ). It does the same for the paired rows in the normalized instance predicates; the lacks-conversion lemma then transfers their entailment. Hence a prefix substitution is a symbolic instance of one declaration under Q exactly when it is a symbolic instance of the other, and their instance bodies are Q-equal. This is ΓQΓ. Equality of free-variable sets follows at the same time, declaration by declaration. ◻

Lemma 4.10 — Qualified typing respects equivalent contexts

If ΓPΓ and Γqe:Pτ, then Γqe:Pτ. In particular, if SPT on every type and row variable free in Γ, then Γ[S]PΓ[T], and either image context may replace the other in a qualified typing under P.

Proof of Lemma 4.10 — Qualified typing respects equivalent contexts

Proof. Induct on the typing derivation. For a variable leaf, commonly freshen Γ(x) and Γ(x). If the original symbolic instance uses prefix substitution T, the definition of context equivalence gives τx[T]Pτx[T]andPnf(Qx[T]). Thus Q-Var derives the corresponding instance under Γ, and Q-Conv restores the original result type. Constants do not inspect the context. For every other premise with predicate context Pi, the formation presupposition of definition 4.7 supplies qualified Pi-formation derivations for every declaration used in that premise. The change lemma therefore gives ΓPiΓ. Apply the induction hypothesis to that premise, then reapply the lambda, application, record, or variant rule.

In a let, the first induction hypothesis preserves P1 and τ1. The change lemma gives ftv(Γ)=ftv(Γ), so both generalizations choose the same prefix and residual split. Extend both contexts by this common declaration and apply the second induction hypothesis under P2. Transitivity discharges the final conversion. The last assertion follows by induction on scheme bodies; pointwise row equality is preserved by every type constructor, and the preceding lacks-conversion lemma preserves every instantiated predicate. ◻

Lemma 7.16 — Admissibility of one-variable elimination

Suppose a is P-formed, z has the same sort as a, zftv(a). Put I=[a/z], and suppose Q=nf(P[I]) exists. Then I:PQ is admissible.

Proof of Lemma 7.16 — Admissibility of one-variable elimination

Proof. The substitution is finite and sort preserving. Its only nonidentity image is formed and, at row sort, strict by hypothesis. Substitute [a/z] through its formation derivation. In the Row-Ext case, substitute through the lacks derivation as well. Each resulting assumption leaf occurs in Q=nf(P[I]), so it is entailed by Q. This proves that the image is Q-formed. Finally Q is the normal form of the substituted source predicates, so it entails that normal form by reflexivity. These are exactly the clauses of definition 7.3. ◻

Lemma 4.11 — Identity and composition of admissible substitutions

The identity is admissible PP. If S:PQ and R:QP are admissible, then S;R:PP is admissible. Moreover, substitution by R sends every Q-formation, lacks-entailment, and strict row equality derivation to the corresponding P-derivation.

Proof of Lemma 4.11 — Identity and composition of admissible substitutions

Proof. Identity substitution leaves every formation, entailment, and strict-equality derivation unchanged. For composition, induct first on type and row formation. Every image under S is Q-formed, and the induction sends it to a P-formed image under R. Induction on lacks entailment sends Qρ\ to Pρ[R]\: assumption leaves follow from Pnf(Q[R]), and empty and unequal extension are preserved literally. Applying this to Qnf(P[S]) shows Pnf(P[S;R]). Adjacent exchanges retain their distinct labels, so the same induction preserves strict row equality. These are exactly the clauses in the definition of admissibility. ◻

A substituted environment may need ambient lacks assumptions to form its declarations. Let F be the normalized lacks assumptions used in those formation derivations. Since every variable of F occurs in the environment, generalization retains rather than quantifies them.

Lemma 4.12 — Ambient formation and generalized declarations

Let σ=GenΓ(Pτ). Freshen its bound prefix away from S, and write Sσ for substitution by S on the free variables of the scheme only. Suppose F is normalized, every variable of F occurs in Γ[S], and PS=nf(P[S]F),σS=GenΓ[S](PSτ[S]) are formed. If Q entails F, then every qualified derivation under Γ[S],x:Sσ and ambient predicates Q remains derivable after replacing that declaration by x:σS.

Proof of Lemma 4.12 — Ambient formation and generalized declarations

Proof. Induct on the derivation. Only a leaf selecting x changes. Such a leaf chooses an instance T of the old bound prefix and has entailment premise Qnf(P[S;T]). The same T is an instance of σS. It fixes F, because the variables of F occur in Γ[S] and hence are not generalized. Therefore Q entails nf(P[S;T]F)=nf(PS[T]), and the result type is still τ[S;T]. Rebuild the leaf and then the surrounding derivation. ◻

The residual set of Q-Let is a deliberate difference from unrestricted qualified-implication introduction: a constraint involving an environmental variable cannot be hidden merely because the let-bound variable is unused. Record and variant operations are saturated term forms, so progress needs no run-time status for a half-applied selector or case operator.

Example 4.14 — The environment operations

The definitions portOf:=λr.r.port,withSecure:=λr.{secure=truer},withoutPort:=λr.rport have the displayed schemes portOf:αξ.(ξ\port)Rec({port:αξ})α,withSecure:ξ.(ξ\secure)Rec(ξ)Rec({secure:Boolξ}),withoutPort:αξ.(ξ\port)Rec({port:αξ})Rec(ξ). For example, put Ps={ξ\secure}. Before generalization, the whole derivation of the second definition is r:Rec(ξ)qtrue:PsBool,r:Rec(ξ)qr:PsRec(ξ),nf(PsPs{ξ\secure})=Ps. Thus Q-Extend gives the premise of the following Q-Lam instance: r:Rec(ξ)q{secure=truer}:PsRec({secure:Boolξ})qwithSecure:PsRec(ξ)Rec({secure:Boolξ})QLam. The two leaves are predicate weakenings of Q-Const and Q-Var. The same two-step calculation for portOf ends in Q-Select; for withoutPort it ends in Q-Restrict.

For instance, instantiate ξ in withSecure by {host:String,port:Nat}. The lacks premise reduces twice by L-Extend and ends at L-Empty; the result retains both fields and adds secure.

The residual part of Q-Let is visible only when the environment fixes a row variable. Let Γ=r:Rec(ξ),Ps={ξ\secure}. The extension calculation above gives Γq{secure=truer}:PsRec({secure:Boolξ}). Here ξftv(Γ), so the generalized prefix is empty and Psr=Ps. Even if the bound variable is unused, Γqlet x={secure=truer} in zero:PsNat. The constraint remains because it forms the bound expression; dropping it would accept an ambient record that already contains secure. By contrast, in the closed definition withSecure the row variable is absent from the environment, so the same predicate is generalized with ξ.

The same instantiation fails on a row already containing secure. That rejection is intentional: extension means adding a new field. Replacement is written with update, whose input explicitly contains the old field.

Exercise 4.2

★☆☆ Derive the scheme of update from restriction and extension using the defining equation above. Write the single tail row at every intermediate type and identify the one lacks predicate used by both operations.

Variants from the same rows

A record with row ρ carries every field of ρ. A variant with the same row carries exactly one of them. The shared row language makes record extension and variant widening dual operations.

Definition 4.15 — Variant terms and typing

Extend the grammar by injection =e, embedding embede, and the binding form case e of {=xe1;ye2}, where x binds in e1 and y in e2. Injection selects the new alternative, while embedding preserves a value from the old row.

Γqa:P0τnf(P0{ρ\})=P
Γq=a:PVar({:τρ})
Q-Inject
Γqu:P0Var(ρ)nf(P0{ρ\})=PPτ type
Γqembedu:PVar({:τρ})
Q-Embed

Decomposition reverses that choice:

Γqu:P0Var({:τρ})Γ,x:τqe1:P1υΓ,y:Var(ρ)qe2:P2υnf(P0P1P2)=P
Γqcaseu of {=xe1;ye2}:Pυ
Q-Case

In Q-Inject, the payload derivation determines τ, while the typing derivation chooses the residual row ρ. In Q-Embed, the embedded value inhabits an old alternative, so it supplies no value at the newly added alternative. Fresh variables make both choices most general.

Injection chooses the new alternative. Embedding preserves a value from the old alternatives. Decomposition reverses that choice. The following calculation shows decomposition and embedding; injection is practiced in the following exercise.

Example 4.16 — An extensible error case

Assume e:Var({ok:α,error:Stringξ}),ξ\ok,ξ\error. First permute the row to expose error. With ρ={ok:αξ}, the case term caseerror e of {error=szero;yzero} has type Nat: under s:String the first body is zero:Nat, and under y:Var(ρ) the second body has the same type. The second branch receives the whole residual variant; it may immediately decompose the known ok alternative. An alternative represented only by ξ can be decomposed later only when its label and a corresponding lacks fact are known.

Writing Pe for the two displayed lacks predicates, the complete rule instance is Γqe:PeVar({error:Stringρ})Γ,s:Stringqzero:NatΓ,y:Var(ρ)qzero:Natnf(Pe)=PeΓqcaseerrore of {error=szero;yzero}:PeNatQCase.

Conversely, if u:Var(ρ), then embederroru has the original larger variant type. No case is lost and no run-time tag is fabricated.

Exercise 4.3

★★☆ Let f:αβ and e:Var({ok:α,error:Stringξ}) under the two corresponding lacks predicates. Construct a term of type Var({ok:β,error:Stringξ}) which applies f only to the ok alternative and preserves every other alternative. Display the decomposition, the new injection in the ok branch, and the embedding that puts the residual branch at the common result type.

Evaluation and safety

The constraints now have an operational purpose. A record is represented by a finite label-to-value map; a variant by one labeled value. The source syntax does not prescribe an ordering in memory yet.

Definition 4.17 — Values, contexts, and primitive reduction

Evaluation is left-to-right call by value. Write v::=cλx.eRV,R::={}{=vR},V::==v,E::=[]EevElet x=E in e{=Ee}{=vE}E.E=EembedEcaseE of {=xe1;ye2}. Record values R have distinct labels. This is the complete evaluation context grammar for the term syntax developed so far. The two base contractions are (λx.e)ve[v/x],let x=v in ee[v/x], and the compatible rule is EeEe whenever ee. Write ee for the reflexive–transitive closure of this source reduction. The primitive redexes are {=vR}.v,{m=wR}.R.(m),{=vR}R,{m=wR}{m=wR}(m),embedm=vm=v(m),case=v of {=xe1;ye2}e1[v/x],casem=v of {=xe1;ye2}e2[m=v/y](m). Record extension with an already present label and variant embedding of an -tag are not run-time states of a well-typed program; the lacks premise excludes them. Update first restricts and then extends.

For a neutral scrutinee z, a variant case does not reduce. Its typing derivation contains premises for both branch bodies, one under the inserted label and one under the residual row; neither premise determines which tag a future closing substitution will reveal.

Weakening and term substitution are unchanged by rows: predicates and types contain no term variables. Type-and-row substitution requires one extra observation.

Lemma 4.18 — Structural stability

Let S:PQ be admissible.

  1. Lacks entailment. If Pρ\, then Qρ[S]\.

  2. Formation and equality. If Pρ row, then Qρ[S] row; if ρPρ, then ρ[S]Qρ[S].

  3. Type-and-row substitution. From Γqe:Pτ follows Γ[S]qe:Qτ[S].

  4. Term substitution. If x has monotype τx, Γ,x:τxqe:Pτ, and Γqa:Paτx, then Γqe[a/x]:Qτ for Q=nf(PPa). If instead Γ,x:GenΓ(Paτa)qe:Pτ,Γqa:Paτa, then Γqe[a/x]:Pτ: the constraints of every instance of a are entailed at the corresponding use of x.

  5. Weakening. Weakening by a declaration fresh for the term preserves typing.

Proof of Lemma 4.18 — Structural stability

Proof. For item 1, normalize the substituted entailment derivation. An assumption ξ\ becomes a predicate whose normal form occurs in Q. Empty and unequal extension commute with S, because substitutions do not change labels. Item 2 is simultaneous induction on formation and equality. At Row-Ext, substitution sends the premise Pρ\ to Qρ[S]\; an adjacent exchange retains its two distinct labels.

For item 3, induct on qualified typing. Variable and constant instances compose their prefix substitution with S. Application and each record/variant rule normalize the substituted union of premise constraints. Lambda is structural. In Q-Let, write its first predicates as P1=P1gP1r and freshen the generalized prefix away from S. Define the finite set without choosing formation derivations: F={ξ\Qξftv(Γ[S])}. Formation is syntax-directed on a row, so these are exactly the possible assumption leaves used to form the context images. Every variable of F occurs in Γ[S]. Then P1S=nf(P1[S]F) contains the normal forms of every substituted premise and every context assumption used by S. Hence the restriction of S is an admissible map P1P1S, so the first induction hypothesis gives Γ[S]qe1[S]:P1Sτ1[S]. The body induction hypothesis initially uses the declaration SGenΓ(P1τ1). Apply lemma 4.12 to replace it by GenΓ[S](P1Sτ1[S]). Finally Q entails F and P1r[S], so Q-Let returns exactly the ambient predicate context Q. The case rule freshens both branch binders. Conversion uses item 2.

For item 4, induct on typing and freshen lambda, let, and case binders away from a. At a monomorphic distinguished variable leaf use the premise for a and normalize the union. At a polymorphic let leaf, its symbolic instance is a substitution T on the generalized variables. First use the leaf premises to regard T, extended by the identity elsewhere, as an admissible map PaP: its images are P-formed and P entails nf(Pa[T]). Item 3 therefore types the same term a at τa[T] directly under the surrounding P. The domain of T contains only variables generalized away from Γ, so Γ[T]=Γ. Thus no extra copy of the scheme’s predicates is added to the conclusion. Every other variable is unchanged. Each record, variant, and case rule reapplies after the induction hypotheses for its immediate subterms. Weakening is the same induction, because no old variable leaf selects the fresh declaration. ◻

Corollary 4.19 — Ground discharge

If Γqe:Pτ and G:P is an admissible ground substitution on all free type and row variables of Γ,P,τ, so in particular GgP, then Γ[G]qe:τ[G].

Proof of Corollary 4.19 — Ground discharge

Proof. Admissibility says that every image is formed and every predicate in P[G] normalizes to , hence nf(P[G])=. Now item 3 of lemma 4.18 applies. ◻

The word admissible cannot be replaced by ground. A raw map sending an unconstrained ξ to {:Nat,:Bool} grounds ξ and vacuously satisfies the empty predicate set, but its image is not a strict row and cannot classify a term.

The preservation proof needs to peel a run-time map in the same way as its static row.

Lemma 4.20 — Peeling and deletion

Let P be normalized, let Pρ row, and let Γ be a context of P-formed schemes.

  1. if Γq{m=wR}:PRec(ρ), then there are υ,ρ0 with ρP{m:υρ0},Γqw:Pυ,ΓqR:PRec(ρ0).

  2. If distinct labels m, occur in ρ, then (ρm)P(ρ)m; both deletions preserve the same tail variable, every other displayed field, and its type.

Proof of Lemma 4.20 — Peeling and deletion

Proof. Strip final uses of Q-Conv. Lemma 7.6(2) ensures that each conversion retains the outer Rec constructor and projects to an equality of its rows. The only rule producing the displayed record constructor is Q-Extend, whose two premises give w and R; its conclusion gives the extended row. If a premise uses a smaller normalized predicate context, apply lemma 7.12 to restore P, then reapply the stripped conversions to obtain the first equality and both displayed qualified typings. For item 2, use the finite-map description of lemma 4.5: both orders of deletion leave the same finite map and the same row-variable or empty tail, so they are equal under P. ◻

Lemma 4.21 — Ground canonical forms

Let P be normalized, let Pρ row, and let Γ be a context of P-formed schemes.

  1. If ΓqR:PRec(ρ) and R is a record value, then either R={} and ρPϵρ, or R={=wR0} and there are τ,ρ0 such that ρP{:τρ0},Γqw:Pτ,ΓqR0:PRec(ρ0).

  2. If ΓqV:PVar(ρ) and V is a variant value, then V==w and there are τ,ρ0 such that ρP{:τρ0},Γqw:Pτ.

  3. If Γqv:PAB and v is a value, then v=λx.e. This uses the deliberately nullary constant signature Σ0; it contains no arrow-valued constants.

When P=, Γ=, and ρ is closed, repeated use of item 1 shows that a record value has exactly one value at each label of ρ; item 2 selects exactly one field of a closed variant row.

Proof of Lemma 4.21 — Ground canonical forms

Proof. Strip final conversions and inspect the last introduction rule. Rules Q-Empty and Q-Extend give the two record cases and their displayed premises; Q-Inject gives the variant case. Projecting the stripped conversion through Rec or Var supplies the row equalities by lemma 7.6(2). An embedding is not a value form. Base constants, records, and variants cannot have arrow type, so the remaining arrow-valued form is an abstraction.

In the closed record case, repeat item 1 down the finite value spine. Every Q-Extend premise proves that the new label is absent from the tail, so no label repeats. The terminal Q-Empty fixes the empty tail, and row equality preserves the resulting finite label-to-type map. The closed variant statement is item 2 together with the same finite-map characterization. ◻

Theorem 4.22 — Safety of strict records and variants

Suppose qe:Pτ. For every substitution G:P that is admissible and grounds all free type and row variables of P,τ:

  1. if ee, then qe:τ[G];

  2. either e is a value or some e satisfies ee.

Consequently evaluation never selects or removes an absent field and never reaches a variant case without a matching branch.

Proof of Theorem 4.22 — Safety of strict records and variants

Proof. Ground discharge gives qe:τ[G].

For preservation, prove first the stronger qualified assertion: if Γqa:P0υ and aa, then Γqa:P0υ. Induct on the reduction, with the typing derivation as a subsidiary induction to remove a final conversion. Beta and let use term substitution. This stronger statement is important in the compatible rule for let x=a in b: the predicates stored in the scheme of x need not occur among the body’s predicates, but the reduct of a retains exactly its own qualified typing. For the let contraction, first weaken the body from P2 to nf(P1rP2) and then apply the generalized term-substitution clause (item 4) of lemma 4.18. The retained residual predicates keep the bound expression formed even when x has no occurrence in the body.

For record lookup, perform an inner induction on the run-time record. At a matching head, lemma 4.20 proves that its stored type is the unique field type. At an unequal head m, peeling types the tail at ρm; deletion commutation says this residual row still exposes at the same type, so the inner induction applies. Restriction uses the same inner induction, but reattaches each unequal m field; deletion commutation proves that the resulting row is ρ. At the matching head, the preservation subcalculation is therefore explicit: Γq{=vr}:PRec({:τρ})Γq{=vr}.:PτQSelect{=vr}.lookupv, and peeling the record derivation recovers Γqv:Pτ, the type of the reduct. The unequal-head induction repeats this calculation on r.

For embedding, qualified value inversion says the old value is m=v with m in the residual row. The lacks premise gives m, and injection at the enlarged row types the reduct. In the matching case clause, the payload has the field type τ from Q-Case; in the unequal clause, deleting from the finite map leaves the tag m in ρ, so the residual value has type Var(ρ). More explicitly, lemma 4.5(3) writes the original row as {m:υρm}; deletion of the distinct preserves that field, so Q-Inject types the residual tag. Term substitution types the selected branch. Compatible-context cases use the induction hypothesis under the corresponding displayed typing rule.

For progress, induct on the closed typing derivation with the strengthened property: for every predicate context P0 and every ground admissible H:P0, a closed judgment qa:P0A has a subject that is a value or steps. Reduction does not inspect types or H, so changing the ground satisfier between premise calls does not change the subject or its next step. Every normalized predicate set has a ground admissible substitution: send every row variable to ϵρ and every unconstrained type variable to Nat. Use such a substitution whenever an immediate premise carries predicates not retained by its conclusion. In particular, for a let, apply the induction hypothesis to the bound expression under a ground satisfier of its own P1; if it steps, use the let context, and if it is a value, use the let contraction. For application, first step the function; when it is a lambda value, step the argument; when both are values, beta reduction applies and term substitution types the reduct. Constants and lambdas are already values. For Q-Let, use the bound premise under its own normalized P1, as above, because its generalized predicates need not occur in the conclusion. In every other multi-premise rule, each premise constraint belongs to the conclusion’s normalized union, so one ground satisfier serves all immediate premises.

If the scrutinee of a row operation steps, use the compatible rule. If it is a value, lemma 4.21 proves that it has the required field form, residual-record form, or one of the two variant forms, so exactly one displayed primitive clause applies. The strictness premise rules out the two forbidden duplicate states named after definition 4.17. ◻

Lemma 7.28 — Determinism of the extended source

If ee1 and ee2, then e1=e2.

Proof of Lemma 7.28 — Determinism of the extended source

Proof. Every nonvalue that steps has a unique decomposition e=Er, where r is one of the root redexes in definition 4.17. Prove this by induction on e, following the left-to-right context grammar. For example, a record selection first follows its unique scrutinee decomposition. Once its scrutinee is a record value, exactly one of m= and m selects the matching-head or unequal-head root clause. Restriction and variant case have the same exclusive split; an embedding root exists exactly when its tag differs from the inserted label. The beta and let roots have outer shapes distinct from all row roots. Hence two reductions of e use the same context and root clause, whose contractum is unique. ◻

Exercise 4.4

★★☆ Suppose m and a closed record value begins {m=wR}. Write the full preservation step from {m=wR}. to R.: expose the static m field, type R, and use deletion commutation to show that its row still contains the same field.

Type safety says why lacks predicates are not bureaucratic decoration: they are the static form of the fact that the operational lookup and deletion recursions will find exactly one component.

Unifying rows

Inference must solve equations such as {port:αξ}{host:String,port:Natζ}. The first labels disagree syntactically, but the equation is solvable: move port to the front on the right, unify α with Nat, and make the two remainders agree. Ordinary first-order decomposition cannot perform the first step. The operation it lacks is: expose a requested field wherever it occurs, extending an open tail if necessary.

Exposing a field in an open tail must also preserve strictness. A substitution-only insertion clause would return I0=[{:τζ}/ξ]with no obligation on ζ. Instantiating the supposedly fresh tail by ζ={:υ} then produces {:τ,:υ}, which is not a strict row. If the input context contains ξ\, applying I0 should instead produce , but a substitution-only result has nowhere to record that failure. The repair is to return the new lacks obligation together with the substitution. Replacing an open tail ξ by {:τζ} is valid only if the fresh tail ζ lacks . Therefore insertion cannot return a substitution alone; it must return the new obligation at the moment the new tail is created.

Definition 4.23 — Constrained insertion and unification

The insertion and unification algorithms below are mutually recursive. Within their clauses, q,r,ρ,ρ range over rows. The disambiguation table following definition 4.1 fixes the overloaded row operations; the two signatures displayed below fix the inputs and outputs of insert and solve.

Given Pτ type and Pρ row, insertP(:τ,ρ)=(I,Q,ρ) returns an admissible substitution I:PQ and a residual row such that ρ[I]=r{:τ[I]ρ},Qρ\. For one top-level call, let A contain every variable in its predicate set, types, rows, equations, and any protected set named by the caller. Recursive calls share one supply; a fresh choice is the first variable, in the fixed type- and row-variable orders specified with generalization, outside A and all earlier choices of that call. The insertion clauses are:

  1. For a row variable ξ, provided ξftv(τ), choose fresh ζ, set I=[{:τζ}/ξ],Q=nf(P[I]{ζ\}),ρ=ζ. Fail if the occurs check or normalization fails.

  2. Insertion into ϵρ fails.

  3. For ρ={:υρ0}, compute solve(P;τυ)=(U,Q) and return (U,Q,ρ0[U]).

  4. For ρ={m:υρ0} with m, compute insertP(:τ,ρ0)=(I,Q,q) and return (I,Q,{m:υ[I]q}).

The solver solve(P;E)=(U,Q) takes a finite predicate set P and a finite, well-sorted list E of equations between types or rows formed under nf(P). Every call first normalizes P and fails if normalization reaches . The clauses below are tried in their printed order, so the algorithm is deterministic up to fresh names.

  1. On the empty work list, return (id,nf(P)).

  2. Discard a syntactic identity aa at the head of a nonempty list.

  3. If the head is za, where z is a type variable and a a type, or z is a row variable and a a row, fail when zftv(a). Otherwise put I=[a/z], normalize P1=nf(P[I]), solve solve(P1;E[I])=(V,Q), and return (I;V,Q). If both sides are variables of the same sort, this clause eliminates the left variable. A head az is oriented to za only when a is not itself a variable. Thus the orientation is deterministic, row-variable equations are eliminated explicitly, and both the old predicates and the remaining equations are rewritten by I. Thus U-Var fuses the orient and eliminate behaviours that definition 3.22 names separately.

  4. This clause applies only to equations of type sort. Equal base-type constructors are discarded; unequal base types and unlike outer type constructors fail. Replace τ1τ2υ1υ2 by the two equations τ1υ1,τ2υ2 in that order. Replace Rec(ρ)Rec(q) and Var(ρ)Var(q) by ρq. A record constructor never unifies with a variant constructor.

  5. Discard ϵρϵρ. An empty row against an extension fails.

  6. After the preceding variable clauses, an extension equation has an extension on each side. For {:τρ}q, compute insertP(:τ,q)=(I,P1,q)solve(P1;(ρ[I]q,E[I]))=(V,Q)solve(P;({:τρ}q,E))=(I;V,Q). Here (ab,E) means that ab is placed at the front of the remaining work list. Insertion may scan through several distinct labels before it reaches the requested one or an open tail.

These clauses, together with the four insertion clauses above, are the complete mutually recursive algorithm. Rigid decomposition always prepends its subequations from left to right.

For reference, the complete clause sheet is insert1IVarξ2IEmptyϵρ (failure)3IMatch{:υρ0}4ISkip{m:υρ0}, msolve1UDone[]2UDeletesyntactic identity3UVarza or its allowed orientation4UTypetype-sort rigid equation5UEmptyempty-row comparison6URowextension equation via insert These named rows list the ten clauses in priority order; the definitions above give their outputs and failure conditions. The algorithm is a partial recursive function, so these are defining clauses rather than derivability rules.

The first insertion clause is the point that a “unify first, check lacks later” presentation misses. If P already contains ξ\m with m, substitution reduces it to ζ\m. If it contains ξ\, normalization reaches , correctly rejecting an attempt to expose a field known to be absent. Independently, ζ\ makes the newly constructed row itself strict.

For the equation displayed at the start of the section, insertion scans past host, meets port:Nat, and unifies α with Nat. It returns the residual row {host:Stringζ}, so the remaining equation is ξ{host:Stringζ}. No new lacks predicate is needed in this particular scan: both fields were already displayed in a strict input row.

Example 4.24 — Exposing two open tails

The fresh-tail clause appears in the smallest crossing equation. Start with P={ξ\x,ζ\y} and solve {x:Natξ}{y:Boolζ}. To expose x on the right, insertion first passes the distinct y field and then reaches the open tail ζ. Choose fresh ω and set ζ[I]={x:Natω}. The new tail must lack x. The old predicate ζ\y also becomes ω\y. After reattaching the stored y field, the residual equation is ξ{y:Boolω}. Eliminating ξ leaves the principal answer U=[{x:Natω}/ζ,{y:Boolω}/ξ],Q={ω\x,ω\y}. Thus the fresh tail is not an implementation trick: its two lacks facts are exactly what makes both substituted rows strict. Any larger common tail is obtained by an admissible instance of ω.

Definition 7.31 — Finite-multiset descent

For finite multisets of natural numbers, write M<mulN when there are a nonempty submultiset XN and a finite multiset Y such that M=(NX)Y,and for every yY some xX satisfies y<x. Thus one or more entries are removed and may be replaced by finitely many strictly smaller entries.

Proof of Lemma 7.32 — Well-founded multiset descent

Proof. Suppose an infinite descending chain M0>mulM1>mul existed. A descending step never creates an entry larger than one it removes, so the maximum entry of M0 bounds every later multiset. For multisets whose entries are at most k, induct on k. At k=0, a descending step can only delete zeroes, so cardinality decreases. For the successor step, the number of occurrences of k never increases; whenever a removed occurrence of k is not restored, that number decreases. Along the assumed infinite chain this can happen only finitely often, so the count is eventually constant. Remove those fixed occurrences of k from every remaining multiset. Every subsequent change then concerns entries below k, where the induction hypothesis excludes an infinite descending chain. ◻

Lemma 4.25 — Termination

Constrained insertion and unification terminate on finite input.

Proof of Lemma 4.25 — Termination

Proof. Let L be the finite set of labels occurring in the initial predicate set and equation list. No clause invents a label. At any recursive call, apply the accumulated substitution and regard each remaining row-variable tail t as carrying the set Ft:={Lthe normalized predicate context contains t\}. Equation size alone is not a decreasing measure. In I-Var, replacing ξ by {:τζ} can make a residual equation strictly larger: ξρis replaced by{:τζ}ρ. The decreasing resource is instead the set of labels that each open tail is still permitted to absorb. Define Φ:=t(|L||Ft|). Here t ranges over the distinct row variables in the normalized active input and predicate context, not over their occurrences. The sum is finite because those objects are finite. A global substitution may duplicate occurrences of a tail, but it cannot duplicate a summand.

When insertion exposes at a variable tail t, normalization fails if Ft. Otherwise the clause replaces t globally by one fresh tail t and gives Ft=Ft{}; hence Φ decreases by exactly one. If variable elimination identifies two tails, their forbidden sets are unioned and one tail disappears. This cannot increase Φ: for F,GL, |L||FG|(|L||F|)+(|L||G|). Eliminating a tail against a closed row removes its summand. Eliminating it against a displayed row with one existing tail has the same union calculation after the displayed labels have been checked. Thus every row-variable elimination leaves Φ unchanged or decreases it, and fresh-tail creation decreases it strictly.

Let the size of a type or row be its number of syntax nodes, and put |ab|=1+|a|+|b|,|(:τ,ρ)|=1+|τ|+|ρ|. At a solver call, let M be the multiset of equation sizes; at an insertion call, let it be the singleton containing the insertion-input size. Let N count the distinct type variables and row tails in the normalized predicates and active input, after applying the accumulated substitution. Use the lexicographic measure (Φ,N,M), where M is ordered by <mul.

Every mutually recursive edge is recorded in the following table. A dash means that the earlier component is merely nonincreasing; the rightmost named component is the first strict decrease.

caller clause callee clause Φ N strict component and reason
U-Var solve N: occurs-safe elimination
U-Delete/U-Empty solve M: delete one equation
U-Type solve M: proper subterms
U-Row insert M: smaller insertion input
I-Match solve M: field-type subterms
I-Skip insert M: proper row tail
U-Row after insert solve first of Φ,N,M changed below

For U-Var, the occurs check prevents the eliminated variable from reappearing. U-Delete, U-Type, and U-Empty delete an equation or replace one rigid equation by equations between proper subexpressions. In definition 7.31, take X to contain the removed equation size and Y to contain the sizes of the proper-subexpression equations; deletion takes Y=. Thus M decreases when the earlier components are unchanged. The first call of U-Row, from solving to insertion, replaces its extension equation by the strictly smaller input (:τ,q). I-Match replaces an insertion input by the strictly smaller equation τυ, while I-Skip replaces it by the input on a proper row tail. Each of these is the single-element instance X={n} and Y={n} with n<n.

It remains to compare the second call of U-Row, which solves the residual row equation and old work list. If its insertion computation uses I-Var at any depth, the new lacks fact makes Φ strictly smaller. Otherwise insertion creates no tail. If its returned substitution is nonidentity, some nested solver call used U-Var on a type or row variable; no other clause creates a binding when I-Var is absent. The occurs check prevents that eliminated variable from reappearing, so N strictly decreases. If the returned substitution is the identity, the old work list is unchanged and the residual comparison has removed the matched displayed field, so M strictly decreases. These cases cover every actual mutually recursive edge. Lemma 7.32 and the lexicographic composition of the well-founded orders on N,N, and <mul therefore prove termination. ◻

Lemma 4.26 — Fresh support of insertion and solving

Let Z be a finite set of variables disjoint from the active inputs of an insertion or solver call. The call may choose every fresh tail outside Z, without changing its result except for fresh renaming, and its returned substitution is the identity on Z.

Proof of Lemma 4.26 — Fresh support of insertion and solving

Proof. Induct over the mutually recursive clauses. Empty, rigid, and failure clauses introduce no substitution. A variable clause changes only the displayed input variable; an exposing insertion changes only that input variable and chooses its new tail outside Z. Composition preserves identity on Z. In a recursive clause pass Z together with all variables of the surrounding arguments that are absent from the recursive call. Thus neither the recursive result nor a later fresh choice can acquire one of those names. This proves both assertions simultaneously. ◻

Theorem 4.27 — Principal row solver

Insertion. Suppose insertion returns insertP(:τ,ρ)=(I,Q,ρ). Then equation 4.1 holds and Qρ row,ρ[I]Q{:τ[I]ρ}. If S:PP is admissible and ρ[S]P{:τ[S]q}, then SPI;R on the variables of P,τ,ρ for some admissible R:QP. If insertion fails, no such S exists.

Solving. Suppose E is well sorted and formed under P. If solve(P;E)=(U,Q), then U:PQ is admissible and solves every equation of E under Q. Every admissible solution S:PP factors as SPU;R on the variables of P,E for some admissible R:QP. If the solver fails, no admissible solution exists. In particular, a ground admissible solution of E factors through U by a ground admissible map out of Q.

Proof of Theorem 4.27 — Principal row solver

Proof. The calculation terminates by lemma 4.25. Use well-founded induction on that calculation, proving the insertion and solver clauses simultaneously.

For variables in a recursive input, the induction hypothesis gives SPI;R. The remaining variables form a protected set Z. For each zZ, define R(z)=S(z); fresh support gives I(z)=z, so the same factorization equation holds on the surrounding input without changing an equation already factored.

For insertion into a row variable ξ, a target solution has, by lemma 4.5, a unique decomposition ξ[S]P{:τ[S]q} with Pq\. Define ζ[R]=q and let R agree with S on the old variables. The displayed lacks judgment satisfies the new predicate ζ\; admissibility of S satisfies every predicate inherited from P[I]. Hence R:QP and I;RPS on the insertion variables. Regard types and rows as one finite, mutually defined syntax tree: arrow components, record/variant indices, and extension field types and tails are its children. If the occurs check finds zftv(a), any solution would equate the finite tree S(z) with a tree containing S(z) as a proper descendant, which is impossible. The empty row cannot expose a field. If normalization of P[I]{ζ\} fails, an inherited predicate entails ξ\. Substitution stability would make every admissible target S:PP satisfy Pξ[S]\, while the required insertion equality writes ξ[S] as {:τ[S]q}. A formed strict row with an -field cannot lack , so no target solution exists.

Suppose the insertion input begins with :υ. Strict finite-map equality separates the target equation into τ[S]Pυ[S] and ρ0[S]Pq. Apply the simultaneous induction hypothesis to the smaller type-equation solve, protecting Z=ftv(ρ0)ftv(P,τ,υ). Its factor R0 agrees with S after U on the active solver variables. By lemma 4.26, U fixes Z. Extend R0 by z[R]=z[S] for zZ. The new images are formed because S is admissible, while no predicate of the solver output acquires a protected variable; hence the extension remains admissible. It now satisfies ρ0[U;R]Pρ0[S]Pq. Meanwhile substitution stability carries the original tail-lacks premise to Qρ0[U]\. This proves the returned specification and its factorization.

Now let the insertion input be {m:υρ0} with m. In any target solution the unique m field occurs in q. Delete it and put qm=qm. Deleting m from the target equality gives ρ0[S]P{:τ[S]qm}. Protect also Z=ftv(υ)ftv(P,τ,ρ0). The insertion induction hypothesis on the proper tail yields a factor R0 and residual r such that R0 agrees with S after I on the recursive input. By lemma 4.26, I fixes Z; extend R0 by z[R]=z[S] there, preserving admissibility as in the same-head case. Then reattaching the stored m:υ[I] gives the algorithm’s residual {m:υ[I]r}, and applying R makes it P-equal to q. The original strict tail lacks m, and deletion of the distinct field preserves that fact, so r lacks m. It also lacks by the induction hypothesis. Thus the reattached residual is formed and the adjacent exchange with proves the insertion specification.

For the solver, the ordinary sorted variable and rigid-constructor cases are the elimination and decomposition calculations of theorem 3.26, performed separately for type variables and row variables. Each recursive result must additionally normalize its target predicate context; admissibility of the elementary substitution supplies the formation and entailment premises for that normalization. In a variable elimination, the equation’s formation premise proves that the image is formed, the occurs check proves that the eliminated variable is absent from that image, and normalization forms the target predicate context. Therefore lemma 7.16 proves that the elementary substitution is admissible. In particular, a row image remains a formed strict row, and the same substitution preserves formation of the remaining equations by the direct formation induction used in that lemma. Then lemma 4.11 composes the returned factors. In the row-extension case, protect Z=ftv(ρ,E)ftv(P,τ,q) in the insertion call. Restricting an arbitrary target solution to the insertion equation gives a solution of that insertion problem. Its induction hypothesis gives a factor R1 on the active insertion variables. Extend R1 by the target solution on Z; lemma 4.26 says I fixes Z, so the extended map is admissible and agrees with the target after I on ρ,E as well. Because the target solution satisfies the original extension equation, strict finite-map uniqueness identifies its exposed -field with the one produced by insertion. Deleting that field on both sides gives the residual equation after R1; agreement on the protected variables also preserves every equation in the old work list. Thus R1 solves the residual equation and remaining work list. The solver induction hypothesis factors R1 through V, hence the target is P-equivalent to the composite I;V;R2 on every original problem variable. Conversely, the insertion equation reattaches the same :τ[I;V] field to the residual rows equated by V, so I;V solves the original equation.

The failure cases use the same local obstructions. Predicate normalization at bottom contradicts admissibility. The occurs check contradicts finiteness of the joint tree. Unequal base or outer constructors contradict lemma 7.6(2). An empty row cannot equal an extension by lemma 4.5, and I-Empty is the same fact for insertion. The equal-head and unequal-head insertion failures reduce to a nested solver failure or to the duplicate-label contradiction displayed at the end of the I-Var paragraph. Hence each declared failure excludes a solution.

Finally suppose the competing solution S:P is ground. In the constructions above, every old or protected variable is mapped as S maps it, and every fresh tail is mapped to a residual row obtained by deleting fields from a closed S-image. Induction on the calculation therefore makes every image of the factor R:Q closed. This proves the ground conclusion; P= alone would not suffice. ◻

Example 4.28 — A successful and a failing solve

Take the initial formation context P0={ξ\port,ζ\host,ζ\port}. For E={{port:αξ}{host:String,port:Natζ}}, insertion exposes port on the right, unifies α with Nat, and leaves ξ{host:Stringζ}. Hence U=[Nat/α,{host:Stringζ}/ξ]. After applying U, the residual context is Q={ζ\host,ζ\port}. The first predicate makes the substitution for ξ a strict row; the second preserves both original row-formation derivations. Every other admissible solution chooses a row for ζ satisfying Q and factors through U.

Now instead begin with P=P0{ξ\host}. Applying U gives {host:Stringζ}\host. The raw equations still unify, but the constrained problem has no admissible solution. This is the static detection of a duplicate-label extension.

Exercise 4.5

★★☆ Run insertion and row unification on {x:α,y:Boolξ}{z:String,x:Natζ}. First write the four residual lacks predicates needed to form both input rows. Give the MGU and its fresh common tail. Then also normalize the predicates ξ\z and ζ\y. State whether the constrained problem succeeds.

Qualified Algorithm W and principality

Qualified Algorithm W returns principal source typings. Its predicates are carried through sequential calls and generalized with their types. Each saturated row form adds one syntax-directed clause using the principal solver. The application order is forced by the case term case=zero of {=x(λz.z)x;y(λz.z)zero}. The scrutinee first determines the selected payload type; that substitution must reach the matching branch before the residual branch is inferred, and both branch substitutions must reach the final result equation. The clauses below are the least left-to-right threading that makes those four obligations simultaneously well formed. The calculation immediately after the definition checks every stage on this term.

Definition 4.29 — Qualified Algorithm W

The judgmental notation Wr(Γ,e)=(P,S,τ) means that inference returns predicates P, substitution S, and monotype τ. At a top-level call, let A be the finite set of all type and row variables, free or bound, written in Γ and the constant schemes used by e. The call owns two supplies in the fixed variable orders used by generalization. Each recursive call receives the unused suffixes; “choose fresh” means take the first variable outside A and all earlier choices of this top-level call. The clauses are as follows.

  1. For x with Γ(x)=α¯ξ¯.Qτ, replace the prefix by fresh variables and return the fresh instance of (Q,id,τ).

  2. For λx.e, choose fresh α, infer (P,S,τ) under Γ,x:α, and return (P,S,α[S]τ).

  3. Constants use the same fresh qualified instantiation as variables.

  4. For e1e2, infer (P1,S1,τ1), then (P2,S2,τ2) under Γ[S1]. Choose fresh β, compute solve(P1[S2]P2;τ1[S2]τ2β)=(U,Q), and return (Q,S1;S2;U,β[U]).

  5. For let x=e1 in e2, infer (P1,S1,τ1), put X1=ftv(P1,τ1)ftv(Γ[S1]),P1g={pP1ftv(p)X1}, P1r=P1P1g,σ=GenΓ[S1](P1τ1), infer (P2,S2,τ2) under Γ[S1],x:σ, and return (nf(P1r[S2]P2),S1;S2,τ2), failing if normalization does.

  6. Wr(Γ,{}) returns (,id,Rec(ϵρ)).

  7. For {=ar}, infer (P1,S1,τ1) for a, then (P2,S2,τ2) for r under Γ[S1]. Choose fresh ξ and solve solve(P1[S2]P2{ξ\};τ2Rec(ξ))=(U,Q). Return (Q,S1;S2;U,Rec({:τ1[S2;U]ξ[U]})).

  8. For r., infer (P,S,τ), choose fresh α,ξ, solve solve(P{ξ\};τRec({:αξ}))=(U,Q), and return (Q,S;U,α[U]). Restriction uses the same calculation and returns (Q,S;U,Rec(ξ[U])).

  9. For =a, infer (P,S,τ), choose fresh ξ, normalize the following set, and return it if normalization succeeds: (nf(P{ξ\}),S,Var({:τξ})).

  10. For embedu, infer (P,S,τ), choose fresh α,ξ, solve solve(P{ξ\};τVar(ξ))=(U,Q), and return (Q,S;U,Var({:α[U]ξ[U]})). The fresh α is intentionally unconstrained: embedding preserves a value from the old row and constructs no payload for the new alternative.

  11. For a case, infer the scrutinee (P0,S0,τ0), choose fresh α,ξ, and solve solve(P0{ξ\};τ0Var({:αξ}))=(U0,Q0). Infer the matching branch and then the residual branch in the contexts Γ[S0;U0],x:α[U0]andΓ[S0;U0;S1],y:Var(ξ[U0;S1]), obtaining (P1,S1,τ1) and (P2,S2,τ2), respectively. Finally solve solve(Q0[S1;S2]P1[S2]P2;τ1[S2]τ2)=(V,Q) and return (Q,S0;U0;S1;S2;V,τ2[V]).

Lemma 7.38 — Fixed-choice determinism of qualified W

With the two supplies just specified and the solver’s printed clause order, Wr(Γ,e) has at most one result. If two runs instead use different legal fresh names, there is a unique sort-preserving renaming of the names they allocate, fixing every variable in A, that carries one result and its induced typing derivation to the other.

Proof of Lemma 7.38 — Fixed-choice determinism of qualified W

Proof. Induct on e. Its outer constructor selects exactly one W clause, and the clause fixes the order of recursive calls, substitutions, predicate normalizations, and solver work lists. The induction hypotheses identify the recursive results. The ordered solver is deterministic by definition 4.23; the fixed quantifier and predicate orders determine generalization at a let. Lambda, application, and the row-form clauses each consume their fresh names before the next recursive call, while the case clause consumes the scrutinee names, then the matching branch supply, then the residual branch supply. Hence the fixed-supply results are equal. For two legal supplies, pair their first choices at each corresponding clause. The induction extends this pairing once per allocation, producing the stated renaming and leaving all variables in A fixed. ◻

Example 7.39 — Substitution order in a case

Consider case=zero of {=x(λz.z)x;y(λz.z)zero}. Injection gives a fresh tail ξ0 and the predicate ξ0\, so its result is Var({:Natξ0}). The case clause chooses fresh α,ξ1 and sends the equation Var({:Natξ0})Var({:αξ1}) to the solver. U-Type, U-Row, and I-Match expose the two equations Natα and ξ0ξ1. The fixed variable orientations return U0=[Nat/α,ξ1/ξ0],Q0={ξ1\}. Hence the first branch is inferred under x:Nat.

For (λz.z)x, choose fresh β for the lambda domain and γ for the application result. The generated equation is ββNatγ. Decomposition followed by the variable clauses gives S1=[Nat/β,Nat/γ] and branch type Nat. The residual branch is then inferred under Γ[U0;S1],y:Var(ξ1[U0;S1])=y:Var(ξ1). For (λz.z)zero, choose a disjoint fresh pair δ,ε. The equation δδNatε gives S2=[Nat/δ,Nat/ε] and the same branch type. The final equation is therefore NatNat; deletion returns V=id. The algorithm returns ({ξ1\},U0;S1;S2,Nat). Reading the postfix composite from left to right reproduces the computation: shape the scrutinee, solve the first branch, transport that result into the second branch, and finally equate the branch results. In a larger case the same positions are occupied by U0,S1,S2,V, yielding S0;U0;S1;S2;V.

Lemma 4.30 — Fresh support of qualified W

Let Z be a finite set of variables disjoint from ftv(Γ). A run of Wr(Γ,e) may choose all fresh variables outside Z; its returned substitution is then the identity on Z.

Proof of Lemma 4.30 — Fresh support of qualified W

Proof. Induct on e in the order of definition 4.29. Variable and constant instantiation return the identity, and a lambda changes only its freshly chosen domain variable and what the recursive call changes. At every sequential clause, pass to the later recursive call both Z and the variables of earlier results that are not free in its input context. The induction hypothesis fixes them. Solver calls fix the same protected set by lemma 4.26; composition preserves that property. The let and case clauses differ only in the number of such successive calls. Thus no clause changes a protected variable. ◻

The first component of a let is not discarded. Its predicates travel in σ and reappear, with its bound variables freshly instantiated, at each use of x; predicates mentioning the surrounding environment also remain in the returned residual set. This is qualified let polymorphism in exactly the same place where ordinary HM generalized type variables.

Example 4.31 — Inference of field-preserving extension

Infer the body of withSecure=λr.{secure=truer}. Give r fresh type α. The extension clause infers true:Bool and r:α, then introduces fresh ξ. Its equation αRec(ξ) and strictness condition give P={ξ\secure},τ=Rec(ξ)Rec({secure:Boolξ}). Generalization quantifies ξ, giving the scheme in example 4.14. Nothing in the calculation enumerates the untouched fields.

Definition 4.32 — Regular output of row inference

Write PregS:Γ when every image under S of a variable free in Γ is P-formed and every declaration of Γ[S] has its scheme body formed under the union of P with its own predicate set. A triple (P,S,τ) is regular for Γ when P is normalized, PregS:Γ, and Pτ type. The decorated turnstile is a regularity check, not another entailment relation.

Lemma 4.33 — Restricted-support transport

Suppose P0 is normalized, every row ρ occurring in a predicate ρ\P0 satisfies P0ρ row, and P0τ type for every type τ in the finite list τ¯0. Suppose also that (P,S,τ) is regular for Γ, and the fresh variables allocated by this run of W are disjoint from ftv(P0,τ¯0)ftv(Γ). Put Y=ftv(Γ,P0,τ¯0) and restrict S to Y, making it the identity elsewhere. Then S|Y:P0nf(P0[S]P) is admissible, provided the displayed normalization succeeds, and the restriction agrees with S on Γ,P0,τ¯0.

Proof of Lemma 4.33 — Restricted-support transport

Proof. A variable of P0 or τ¯0 changed by S|Y either occurs in Γ, in which case regularity forms its image, or would be a fresh variable of this run, which the disjointness condition excludes. Every other nonidentity component of S has been discarded. The normalized union entails P0[S]. The global formation hypotheses therefore prove that the restriction is admissible. ◻

In applications of the lemma, the global fresh supply and lemma 4.30 establish its disjointness hypothesis before a later recursive call transports an earlier predicate set.

By lemma 4.11, an admissible U:PQ sends a regular (P,S,τ) to the regular (Q,S;U,τ[U]). The restricted-support lemma explains why later calls may instead transport only the finite support actually used by an earlier premise.

Lemma 4.34 — Qualified generalization calculation

Suppose R:PP is admissible, τPτ[R], and Γ=Γ[R]. Put σ=GenΓ(Pτ) and σ=GenΓ(Pτ). Freshen the prefix of σ away from R, and write Rσ for the scheme obtained by applying R only to the free variables of σ; its displayed prefix remains bound. Every symbolic instance of σ under a predicate context Q entailing its instantiated predicates is also a symbolic instance of Rσ. Moreover, each predicate of Pr is carried to a predicate entailed by P. Consequently a qualified typing under x:σ remains derivable after replacing that declaration by the more general x:Rσ in the already substituted surrounding context Γ[R].

Proof of Lemma 4.34 — Qualified generalization calculation

Proof. Let X=ftv(P,τ)ftv(Γ). Freshen X away from the variables of R and from the quantified prefix of σ. A symbolic instance of σ consists of a substitution K on that latter prefix and an entailment of P[K]. Instantiate each zX in Rσ by z[R;K]. This substitution is sorted and formed. Since R:PP and Q entails P[K], substitution stability gives Qnf(P[R;K]); its result type is equal to τ[K] by the displayed equality. Hence the chosen target instance is an instance of Rσ.

Each predicate in Pr mentions a variable fixed by Γ, so admissibility makes its R-image a consequence of P. For the final claim, induct on the body typing: only a leaf selecting x changes, and the instance construction just given replaces it. ◻

Theorem 4.35 — Sound, complete, principal row inference

Call a scheme context well formed when every declaration X.Pτ has Pτ type, treating its unbound variables as parameters. For every such Γ in the pure let-language with the saturated row forms of definition 4.13, definition 4.15:

  1. Soundness. If Wr(Γ,e)=(P,S,τ), then (P,S,τ) is regular for Γ and Γ[S]qe:Pτ;

  2. Factorization. If Γ[T]qe:Pτ, where T is sorted and its images and Γ[T] are formed under P, then W succeeds and, for its result (P,S,τ), there is an admissible R:PP such that TPS;R on the variables of Γ, τPτ[R], and P entails every predicate of nf(P[R]) (spelling out the predicate-entailment component of admissibility);

  3. Failure. W fails exactly when there is no qualified typing of this form under any P,T,τ.

Proof of Theorem 4.35 — Sound, complete, principal row inference

Proof. We prove soundness, regularity, and factorization simultaneously by induction on the term e, following the clause order of definition 4.29. In the factorization assertion, the comparison substitution is admissible from the returned predicate context to the target context; equality between composite substitutions means pointwise P-equivalence as in definition 4.8. Final uses of Q-Conv in the target derivation are stripped before inversion and restored by transitivity.

The induction invariant at every recursive call has two support clauses. Let Vsur contain the variables needed by the surrounding factorization and let Vrec contain the variables in the recursive input. First, Z=VsurVrec. Second, the recursive substitution fixes Z, and its residual factor is extended on Z by the target solution. Lemma 4.26, Lemma 4.30 preserve the first clause; admissible composition and restricted-support transport preserve the second. Alongside these support clauses, regularity states that every returned type, predicate context, and substitution image is formed at its displayed target context. These are the recursive hypotheses used in every case below.

For each term form, inversion gives the equation in the middle column. Theorem 4.27 factors its target solution through the substitution returned by W; the last column records that factorization.

term form constraint or fresh choice factorization step
x fresh scheme instance map fresh variables to the target instance
λx.e fresh domain α extend the target map by αA
e1e2 τ1τ2β factor the target arrow equation
let x=e1 in e2 Gen(P1τ1) qualified generalization, then the body factor
{} none identity
{=e1e2} τ2Rec(ξ) factor the record-row equation
e., e τRec({:αξ}) factor the exposed-field equation
=e fresh tail ξ map ξ to the target tail
embede τVar(ξ) factor the old-variant row
casee of  scrutinee exposure and branch equality compose three solver factors

The context invariance of lemma 4.10 permits every recursive target typing to be rebuilt under the compared composite context. Thus, if the second recursive call returns S2 under P2, its fresh variables are disjoint from the earlier P1,τ1, and lemma 4.33 proves that S2|Y:P1nf(P1[S2]P2) is admissible on the support of the transported premise. On every variable occurring in those premise types, S2|Y and S2 agree, so those types may be written with S2. Subsequent solver maps compose by lemma 4.11.

For a variable, W replaces the quantified prefix by fresh variables. Rule Q-Var types this instance under its fresh predicate set. Any target leaf first applies the outer substitution T to Γ(x), then chooses formed types and strict rows for its quantified prefix. Define the residual factor to agree with T on the free variables of the original declaration and send W’s fresh prefix variables to those instance choices. Thus S;R agrees with T on Γ(x); the target entailment premise proves that this map is admissible and gives the required type equality. Formation of the instance also proves regularity. Constants are identical, using Q-Const.

For λx.e0, W assigns x a fresh α. The soundness induction hypothesis gives Γ[S],x:α[S]qe0:Pτ, so Q-Lam gives the returned α[S]τ. Conversely, inversion of a target lambda typing yields a domain A and a typing of the body under x:A. Extend the target substitution by αA and apply the body factorization. Removing the fresh component again gives the required factor on Γ. Regularity follows from the formed domain image and the body result type.

For e1e2, the two soundness hypotheses, transported through S2, give types τ1[S2] and τ2. The solver returns U with τ1[S2;U]Qτ2[U]β[U] and with Q entailing the transported premise constraints. Predicate weakening, Q-App, and Q-Conv therefore type the application at β[U]. For factorization, invert a target application typing. The two recursive factorizations make its function and argument types an admissible solution of precisely the arrow equation and predicate set passed to the solver. The solving clause of theorem 4.27 factors that solution through U; composing the three factors gives R. The regularity hypotheses prove that P1[S2]P2 is formed, and the solver theorem proves that U is admissible from that context to Q; hence the returned composite and result type are regular.

At a let, soundness for the bound expression gives Γ[S1]qe1:P1τ1. Generalize the variables absent from Γ[S1] and retain P1r. Put QL=nf(P1r[S2]P2). Regularity of the body call gives P2-formation derivations for the S2-images of variables free in Γ[S1]. Let FP2 be the normalized set of assumption leaves used by those derivations. With P1S=nf(P1[S2]F), the restricted-support transport observation types e1 under P1S. Predicate weakening moves the body hypothesis from P2 to QL, and lemma 4.12 replaces its declaration by GenΓ[S1;S2](P1Sτ1[S2]). Now Q-Let returns QL: the residual part of P1S is P1r[S2]F, already contained in or entailed by QL. For principality, let R1 be the factor returned by the first recursive call. The target declaration for e1 is an instance of R1GenΓ[S1](P1τ1) by lemma 4.34; its non-generalizable predicates occur in the target let conclusion. Replace that target declaration by this more general substituted scheme, apply the body factorization, and compose. Regularity of S2 and retention of P1r[S2] form the returned context and result. This is the ordinary HM let argument with the residual set carried in every displayed step.

The empty record is immediate from Q-Empty. For {=ar}, transport the two soundness hypotheses through S2 and then U. The solver equation types the second term as Rec(ξ[U]), and Q entails ξ[U]\. Rule Q-Extend yields Rec({:τ1[S2;U]ξ[U]}), the returned type. A target extension typing, after inversion, contains a payload type, a residual record row, and the corresponding lacks judgment. The two recursive factors turn these data into an admissible solution of the solver call in the extension clause. Principal constrained unification then yields a factor R through which that solution factors.

For r., soundness first gives Γ[S]qr:Pτ. Solver soundness gives τ[U]QRec({:α[U]ξ[U]}), and both sides are formed under Q; solver admissibility transports the input typing from P to Q. The returned context also records the strictness predicate for ξ[U]. Conversion followed by Q-Select therefore gives the returned type α[U]. Conversely, inversion of a target selection exposes a unique field Rec({:Aρ}) and its lacks judgment. Together with the recursive factor for r, these are a solution of the displayed solver call; its principal factorization gives R. Restriction begins with the same exposure, but Q-Restrict returns Rec(ξ[U]). In the converse direction, the exposed target tail ρ is therefore the image of the fresh ξ; no additional equation is needed.

For =a, use the induction hypothesis for the payload, choose the fresh ξ, and apply Q-Inject. Given any target injection typing, map that fresh ξ to its residual row ρ. The target lacks premise is ρ, so the map is admissible and the returned row and constraint are most general. For embedu, the recursive hypothesis types the old variant. The solver returns U with ξ[U]=ρ and records ρ; Q-Embed adds the fresh alternative of type α[U]. Inverting a target embedding derivation gives the same row equation and lacks predicate. The solver theorem therefore gives its factorization.

Finally consider a case. The first solver and the scrutinee hypothesis give Γ[S0;U0]qu:Q0Var({:α[U0]ξ[U0]}). The two recursive hypotheses type the branches in the two contexts printed in definition 4.29. Transport the first branch through S2; soundness of the last solver call equates its type with the second branch type and combines all three predicate sets. Rule Q-Case, followed by conversion, gives τ2[V]. Inverting a target case typing gives its selected field, residual variant, and common branch type. Principal factorization of the scrutinee solver fixes the two branch contexts; the two branch induction hypotheses then factor their typings; the final solver factors the equality of their result types. Their ordered composite is the required R.

In every row clause, the recursive triples are formed, transport proves each intervening substitution admissible, and solver soundness proves formation of the returned row and type. In the case clause the admissible substitutions are, successively, S0;U0, S0;U0;S1, S0;U0;S1;S2, and finally the admissible composite S0;U0;S1;S2;V. Thus no composite used in the soundness argument is merely a sorted raw substitution.

At the first failing recursive or solver call, the corresponding induction hypothesis or factorization theorem rules out the inverted target typing. Two clauses can instead fail while normalizing predicates. In the let clause, an inverted target typing supplies a formed normalized union of the residual definition predicates and body predicates, so the requested normalization must exist. In the injection clause, the target Q-Inject premise supplies a formed residual row lacking the injected label, so adjoining and normalizing that lacks predicate must also succeed. Hence either normalization failure rules out the corresponding target typing. Therefore W fails exactly when no qualified typing exists. ◻

This theorem is deliberately limited to pure let. If references from section 3.9 are added, the same conservative value restriction must be applied to qualified schemes; a row constraint does not make allocation pure.

Exercise 4.6

★★☆ Run Wr on λx.embederrorok=x. Keep the independently fresh row tails introduced by injection and embedding until unification identifies them. Give the principal qualified scheme and explain why neither ok nor error may occur in its residual tail.

Offset evidence and one concrete implementation

So far, ξ\ has been used as a proposition. It also tells the compiler where an field would be inserted into a canonical layout of ξ. Fix a decidable total order < on labels; store record fields in increasing order, and tag a variant by the zero-based position of its label in that order.

Elaboration reifies a proof of absence as an offset expression. The context Δ contains variables standing for assumed lacks predicates, and the judgment Δd:ρ\ reads: from those assumptions, expression d computes the insertion offset of in ρ. Thus predicates remain static propositions in the source, while their derivations produce integer evidence for the target.

Definition 4.36 — Evidence for lacks

A predicate assignment Δ contains at most one variable dξ, for each residual predicate ξ\. Evidence is computed by

dξ,:ξ\Δ
Δdξ,:ξ\
Ev-Assume
Δ0:ϵρ\
Ev-Empty
Δd:ρ\<m
Δd:{m:τρ}\
Ev-Before
Δd:ρ\m<
Δd+1:{m:τρ}\
Ev-After

The evidence is the insertion offset for . The two inequalities are exhaustive because a derivable lacks predicate rules out m=.

An affine evidence expression in this chapter has one of the forms n or dξ,+n, with nN. Addition of zero is omitted. We call evidence coherent when all derivations of the same lacks judgment normalize to the same such expression.

For ρ={host:String,port:Nat}, suppose host<port<secure. The evidence for ρ\secure is 0+1+1=2; inserting the field at offset 2 appends it to the canonical two-field array. If the labels are displayed in another row order, the same comparison count gives the same answer.

Lemma 4.37 — Evidence coherence

Let P be normalized, and let ΔP contain one evidence variable for each predicate. Any two evidence derivations of ΔPd:ρ\ yield the same affine expression in the variables of P. This expression is invariant under every adjacent exchange of distinct displayed fields, hence under every presentation of the same strict finite row. For a closed row, that expression is the numeral #{mlabels(ρ)m<}.

Proof of Lemma 4.37 — Evidence coherence

Proof. Induct on the number of displayed fields of ρ. At a row variable, normalization leaves exactly the unique matching evidence variable. At the empty row both derivations end at 0. At a displayed field m, the order decides uniquely whether to retain the tail expression or add one; apply the induction hypothesis to the tail. Adjacent exchange of two distinct labels preserves the number of labels smaller than , so the result is independent of the chosen row display. The closed formula follows by ending at the empty row. ◻

Definition 4.38 — Evidence-passing target calculus

The target is a call-by-value let calculus with static type abstraction and explicit offset arguments. Its monotypes are θ::=αNatBoolStringθθArray(ρ)Sum(ρ). A normalized predicate context P indexes target formation. Write Ptθ type when every row index in θ is a strict P-formed row and every field type is recursively target-formed. A substitution S:PtQ is target-admissible when it maps type variables to Q-formed target types, maps row variables to Q-formed strict rows, and Q entails every normalized predicate in P[S]. For each assumption variable in ΔP, that entailment has an evidence derivation in ΔQ; write S^ for the simultaneous replacement by the canonical affine evidence expressions from lemma 4.37.

Fix the label order used by evidence. Let sort(ρ) sort the finite displayed prefix of a strict row by label, preserve its row-variable or empty tail, and recursively canonicalize field types. Let canTy(θ) apply this operation to every row index and recurse through arrows. Target type conversion is the exact rule

Δ;Γt:θcanTy(θ)=canTy(θ)
Δ;Γt:θ
T-Conv

Target types are therefore not an implicit quotient: the displayed judgment uses syntactic types plus this one conversion rule. Since sorting commutes with target-admissible substitution up to a final sort, formation and substitution preserve the premise of T-Conv. In particular, if ρPρ, then sort(ρ)=sort(ρ), and both Array and Sum convert between the two indices.

A target scheme has form X.Off(P)θ, where Off(P) is one offset-certificate argument for each member of the normalized set P. Order these members first by the fixed order of their row variables and then by the label order <. A target context may contain such schemes. The target term grammar is explicit: t::=xcλx.tttlet x=t in tΛX.λd¯.tt[T]d¯[]lookup d tdelete d tinsert d t ttag d twiden d tsplit d t t t. The forms ΛX.t and t[T] are static type abstraction and application. Type abstraction erases; evidence lambdas execute at run time. Thus static instantiation may follow any term that evaluates to a scheme abstraction, while every data primitive is a saturated term former. The scheme rules are

Γ(x)=σ
Δ;Γx:σ
T-Var
c:θΣ0
Δ;Γc:θ
T-Const
Δ;Γt:X.Off(P)θT is sorted and formedΔd¯:Off(P[T])
Δ;Γt[T]d¯:θ[T]
T-Inst
Δ,d¯:Off(P);Γt:θXftv(Δ,Γ)=
Δ;ΓΛX.λd¯.t:X.Off(P)θ
T-Ev-Abs
Δ;Γt1:σΔ;Γ,x:σt2:θ
Δ;Γlet x=t1 in t2:θ
T-Let

We identify a monotype θ with the empty scheme .Off()θ. Instantiation of this empty scheme emits no target syntax: a monomorphic variable leaf elaborates to x, and a monomorphic constant leaf to c. The form t[T]d¯ is emitted only when the type prefix or evidence tuple is nonempty. Dually, an empty abstraction emits no wrapper: Λ.λ.t is represented simply by t. Thus lambda-bound variables and monomorphic let bindings remain ordinary; an expression such as (λx.x)c does not reduce to a spurious static application of the base value c. Here the type variables X are parameters in the premise of T-Ev-Abs. The monomorphic variable, constant, lambda, and application rules, with the evidence context copied unchanged, are Γ(x)=θΔ;Γx:θTMVarc:θΣ0Δ;Γc:θTMConstΔ;Γ,x:θt:υΔ;Γλx.t:θυTLamΔ;Γt:θυΔ;Γu:θΔ;Γtu:υTApp. The binder ΛX is static and erases; the evidence lambdas remain ordinary run-time lambdas. Thus this is ML let polymorphism with explicit predicate arguments, not first-class polymorphism. Scheme abstractions are values, and their application computes by (ΛX.λd¯.t)[T]n¯t[T/X,n¯/d¯]. Together with the ordinary call-by-value let contraction, this is the whole extra dynamics for schemes.

Lemma 7.49 — Canonical target indices

Let P,Q be normalized and let S:PtQ be target-admissible. Then the following properties hold.

  1. If strict rows satisfy ρPρ, then sort(ρ)=sort(ρ).

  2. For every P-formed target type θ, canTy(θ[S])=canTy(canTy(θ)[S]).

  3. If ΔP;Γt:θ, then

    ΔQ;Γ[S]t[S,S^]:θ[S].

    Target formation is likewise stable under S. A type in a judgment may also be replaced by a Q-formed type with the same canTy-image.

Proof of Lemma 7.49 — Canonical target indices

Proof. For item 1, use the finite-map characterization of strict row equality in lemma 4.5. Both sorts list the same label–type map in the fixed label order and retain the same tail. For item 2, induct on θ. The arrow case uses the two induction hypotheses. In an array or sum case, substitution may expose a displayed prefix from a row variable; the final call to sort merges that prefix with the surrounding fields, so both sides list the same finite map in the same order.

For item 3, induct on the target formation or typing derivation. Target-admissibility supplies formed images in variable and row-index cases. At an evidence premise, substitute the corresponding component of S^; its derivation exists by the defining entailment of S:PtQ. Every other syntax-directed rule follows from its induction hypotheses. In the T-Conv case, item 2 transports equality of canonical images through S. Replacement by an equal canonical image is one further application of T-Conv. Thus a raw sort-preserving map such as S(ξ)={a:Nat,a:Bool} is excluded: its row image is not Q-formed. ◻

Definition 7.50 — Target row primitives

Run-time values additionally include arrays [w0,,wn1] and tagged payloads i,w. If Δd:ρ\, the nullary array constructor and lookup rule are

Δ;Γ[]:Array(ϵρ)
T-Empty
Δ;Γr:Array({:αρ})Δd:ρ\
Δ;Γlookup d r:α
T-Lookup

The other five saturated rules are Δ;Γr:Array({:αρ})Δd:ρ\Δ;Γdelete d r:Array(ρ)TDeleteΔ;Γa:αΔ;Γr:Array(ρ)Δd:ρ\Δ;Γinsert d a r:Array({:αρ})TInsertΔ;Γa:αΔd:ρ\Δ;Γtag d a:Sum({:αρ})TTagΔ;Γu:Sum(ρ)Δd:ρ\Δ;Γwiden d u:Sum({:αρ})TWidenΔ;Γu:Sum({:αρ})Δ;Γf:αβΔ;Γg:Sum(ρ)βΔd:ρ\Δ;Γsplit d u f g:βTSplit. Each displayed primitive rule includes its evidence premise. There is therefore no term such as a half-applied lookup d: it is neither syntax nor a possible stuck state.

Definition 7.51 — Target dynamics and canonical values

Ground arrays use increasing label order. For A=[a0,,an1], write A[i] for zero-based indexing when 0i<n, and write Ai for deletion at such an index. For 0in, define the length-increasing insertion operation insi(v,A)=[a0,,ai1,v,ai,,an1], with the empty prefix understood at i=0 and the empty suffix at i=n. The primitive reductions are lookup i AA[i],delete i AAi,insert i v Ainsi(v,A),tag i vi,v,widen ij,v{j,vj<i,j+1,vij, and split ij,v f g{fvj=i,gj,vj<i,gj1,vi<j. The compatible closure is left-to-right call by value. Besides the ordinary application and let contexts it evaluates the head of t[T]n¯, the single data argument of lookup, delete, tag, and widen, the two arguments of insert in their printed order, and the scrutinee and two branch functions of split in their printed order. These contexts contain only saturated primitive forms. Evidence expressions are stored in the canonical affine form dξ,+n or n; after a ground assignment they are numerals. Rule T-Empty types the empty array. A ground canonical array has type Array(ρ) exactly when its entries, in increasing label order, have the field types of ρ. Likewise i,v:Sum(ρ) exactly when position i of the sorted row ρ carries the type of v. These clauses type every target value produced by the reductions above.

At row {host:String}, two primitive rules compute immediately: lookup 0 ["db"]lookup"db",tag 0 truetag0,true.

Definition 4.39 — Canonical offset representation

Translate rows and types simultaneously: lay(ξ)=ξ,lay(ϵρ)=ϵρ,lay({:τρ})=sort({:lay(τ)lay(ρ)}),lay(Rec(ρ))=Array(lay(ρ)),lay(Var(ρ))=Sum(lay(ρ)),lay(τυ)=lay(τ)lay(υ). Base types and variables are unchanged. Thus every row output of lay is canonical even when the source display is not, and row-equal displays have literally the same layout. An entailment derivation Pρ\ determines, by definition 4.36, a target evidence term from the variables for P. Write it dP,ρ,. Send a source declaration x:X.Pτ to x:X.Off(P)lay(τ).

For example, lay(Rec({port:Nat,host:String}))=Array({host:String,port:Nat}), where the last row is in canonical label order.

The canonicalization function can(t) identifies target terms that differ only by row order or affine presentation: it sorts every strict row in a target type or static argument and reduces each offset expression to affine normal form. This is meta-level normalization of annotations and evidence, not an additional run-time reduction. Its type component is exactly canTy, so the only typing equality it uses is the displayed T-Conv rule.

Definition 7.53 — Unambiguous evidence interface

An open qualified judgment is unambiguous when ftv(P)ftv(Γ,τ). For a closed scheme X.Pτ, this specializes to ftv(P)ftv(τ). The evidence-passing compiler accepts a principal inference result only when this condition holds. An ambiguous result remains a valid source typing and is covered by source safety, but it does not determine its evidence interface from the visible environment and result type.

For instance, the closed scheme ξ.ξ\Nat is ambiguous: its result contains no occurrence of ξ, so two ground instances may require different offsets although the visible result type is the same. The compiler rejects this evidence interface rather than choosing one offset invisibly.

Definition 4.40 — Evidence elaboration

The elaboration is defined on a typing derivation. At a variable or constant leaf, instantiate its scheme and apply the selected evidence tuple. Lambda and application elaborate homomorphically. At a let, replace the bound expression by ΛX.λd¯:Off(P1).e1, where X is the generalized prefix; the body uses this scheme at each fresh instance. When X=P1=, use e1 itself, in accordance with the empty-scheme convention. Final row conversion emits no term. The empty constructor and the six evidence-carrying row forms become sourcetarget at evidence d{}[]r.lookup d rrdelete d r{=vr}insert d v r=vtag d vembedvwiden d vcasev of{f;g}split d v f g. Here d=dP,ρ, is the evidence term of definition 4.39 for the final normalized predicate context at that source rule; it is well defined up to the equality used by the target through lemma 4.37. In the case translation, f and g are the elaborated branch lambdas. These seven clauses, together with the four pure-let clauses, are the complete translation.

Example 4.41 — Compiling field-preserving extension

Keep the label order host<port<secure. At the ground row ρ={host:String}, the evidence for ρ\secure is 1. The source calculation is withSecure{host="db"{}}{secure=true{host="db"{}}}. Its elaborated generalized value is Λξ.λd:Off(ξ\secure).λr.insert d true r. Instantiating the static row, supplying its certified offset, and applying the resulting function gives the complete target trace. In this display [ρ] is static type application, whereas ["db"] is the array-value notation introduced in definition 7.50. ((Λξ.λd.λr.insert d true r)[ρ]1)["db"]schemeβ(λr.insert 1 true r)["db"]βinsert 1 true ["db"]insert["db",true]. The last array is the sorted representation of the source result. The unmentioned host field survives because the offset operation inserts rather than rebuilds the record from a closed list of labels.

The tempting lockstep theorem is false. If the source bound expression steps as e1e1, then let x=e1 in e2let x=e1 in e2, whereas its generalized target binding stores let x=ΛX.λd¯.e1 in e2; the bound scheme abstraction is already a target value, so a step-for-step simulation is false. We instead relate final values and, at a scheme, quantify over every ground instance and its canonical evidence.

Definition 4.42 — Ground value relation

Fix an admissible ground substitution. For each resulting ground type τ, define a relation Rτ on source and target values. Base constants relate only to themselves. At a record type, sort the source map by label; it relates to an array when matching entries relate. At a variant type, let the unique field at have type α. Then =v relates to i,w when vRαw and i counts the row labels below . Finally, fRτυg when, for every aRτb, the two applications evaluate to values related by Rυ.

A source value and a target scheme value are related at X.Pτ when, for every ground, formed instantiation of X satisfying P, application of the target value to its canonical evidence tuple evaluates to a value related to the source value at the instantiated type. Source and target environments are related when their values are related at every declaration. This last clause is the invariant needed at a polymorphic let.

The fundamental lemma proves that every closed grounded source term and its elaboration reach related values. Purity and absence of recursion make this total value relation sufficient; references or recursion would require a partial or step-indexed relation instead.

Lemma 4.43 — The six offset simulations

Let ρ be a strict ground row lacking , and let i be the number of labels of ρ smaller than . The source row operations and the target operations at offset i preserve the ground value relation. More precisely:

  1. the empty source record and the empty target array are related at Rec(ϵρ) and Array(ϵρ), respectively;

  2. related fields and records remain related after source extension and target insertion;

  3. selection and lookup return related fields, while restriction and deletion return related residual records;

  4. injection and tagging return related variants;

  5. embedding and widening return related variants; and

  6. suppose the distinguished and residual source branch functions are related to target functions f and g at αβ and Var(ρ)β, respectively. Then source case analysis and target split evaluate to related results.

Proof of Lemma 4.43 — The six offset simulations

Proof. The first clause is the empty-list instance of the record relation. Sort the labels of ρ as 0<<n1. By definition of the record relation, a source record R={0=v0,,n1=vn1} relates to A=[w0,,wn1] precisely when vjRwj at the field type carried by j. Inserting a related field vRαw at position i gives the sorted lists {=vR}Rinsi(w,A). This proves the extension clause. In a record already containing at position i, source selection scans to the same field that lookup i indexes, and source restriction removes the same entry that delete i removes. This proves the three record-operation clauses.

Injection is immediate: the new label has position i, so =v relates to i,w. For embedding, let an old label m have position j in ρ. Its position in {:αρ} is j when m<, and j+1 when <m. These are exactly the two clauses of widen.

It remains to check case analysis. Let the larger-row target value be j,w. There are three possibilities. If j=i, its source mate is =v; source case takes the distinguished branch and target split reduces to fw. The arrow relation for the two distinguished branch functions gives related results. If j<i, the carried source label is smaller than and already has residual-row position j; split passes j,w to g. If i<j, removing the inserted position lowers the residual tag to j1; split passes j1,w to g. In the last two cases this target value is precisely the mate of the whole residual source variant passed to the residual branch. Its arrow relation gives the required result. This exhausts the six operations. ◻

Lemma 4.44 — Typing of evidence elaboration

Let D derive Γqe:Pτ, and let ΔP contain one evidence variable for every member of normalized P. Then ΔP;lay(Γ)D:lay(τ).

Proof of Lemma 4.44 — Typing of evidence elaboration

Proof. Induct on D. The Q-Empty case elaborates to ([ ]) and uses T-Empty. In Q-Var, translate each entailed instantiated predicate to its evidence expression; T-Var followed by T-Inst then has exactly the displayed result type when the scheme is genuine; for the empty scheme the convention in definition 4.38 uses T-Var alone. The nullary Q-Const instances use the target constant rule. Lambda and application use the ordinary target rules. Conversion emits no term: item 1 of lemma 7.49 makes the two layouts identical, and T-Conv transports the target typing.

For Q-Let, if the generalized prefix and P1 are both empty, the two induction hypotheses and T-Let give the ordinary monomorphic let directly. Otherwise apply the first induction hypothesis under fresh evidence variables for all of P1 and use T-Ev-Abs. This types ΛX.λd¯:Off(P1).D1:X.Off(P1)lay(τ1). The evidence context for nf(P1rP2) projects the tuple needed by the body derivation. The second induction hypothesis and T-Let finish the case. The abstraction takes the whole P1 tuple; its residual part also remains available outside the let.

For extension, the two induction hypotheses type the field value and record array. The normalized conclusion contains an evidence term for ρ\, so the type of insert gives the translated result. Selection and restriction use lookup and delete with the same certificate. Injection and embedding use tag and widen. In the case rule, the two branch hypotheses type the lambdas λx.D1 and λy.D2 at the two argument types of split; T-Split then returns lay(β). ◻

Lemma 4.45 — Fundamental evidence lemma

Let D derive Γqe:Pτ. Let G:P be an admissible ground substitution, let n¯ be its canonical evidence tuple, and let source and target environments γ,η be related at Γ[G]. Then there are values v,w with e[γ]v,G(D)[n¯/ΔP,η]w,vRτ[G]w.

Proof of Lemma 4.45 — Fundamental evidence lemma

Proof. Induct on D. The Q-Empty case evaluates to the empty source record and the empty target array; they are related by the first clause of lemma 4.43. At a variable leaf, the related-environment hypothesis relates the source value to the target scheme value. Applying the chosen ground instance and its canonical evidence tuple is exactly the scheme clause of definition 4.42. A constant evaluates to itself on both sides and is related at its declared base type.

For a lambda, take arbitrary related arguments a and b. Substitution extends the two environments by xa and xb; the induction hypothesis for the body gives related results. Hence the two lambda values satisfy the arrow clause. In an application, the two induction hypotheses give fRαβg,aRαb. The defining arrow clause, not a new operational argument, says that fa and gb evaluate to values related at β.

At a let with empty generalized prefix and P1=, the first induction hypothesis gives related source and target values for the bound expression; extend the two environments by those values and apply the body induction hypothesis. This is the ordinary monomorphic-let case.

Otherwise first extend the ground map to the generalized variables: send fresh type variables to Nat and fresh row variables to the empty row. No row extension is required, because the empty row satisfies every normalized lacks predicate on those fresh variables. Together with the fixed ambient assignments, this is an admissible ground map for the first premise. Its induction hypothesis evaluates e1[γ] to a source value v. Now take an arbitrary ground instantiation satisfying P1 and apply the same induction hypothesis. It produces some source value vT and a target value obtained from (ΛX.λd¯.D1)[T]n¯1. The generalized variables are absent from the source environment and term, so both source evaluations start from the same expression. By lemma 7.28, vT=v. Since the ground instantiation was arbitrary, the fixed source value v and the target abstraction satisfy the universally quantified scheme clause of definition 4.42. Extend γ,η by them and apply the second induction hypothesis. The source evaluates e1 once before substitution; the target abstraction may evaluate its pure body at each instance. The scheme relation makes these evaluations agree.

For extension, selection, restriction, injection, and embedding, apply the subterm induction hypotheses and the corresponding clause of lemma 4.43. In the case rule the scrutinee induction hypothesis proves that the two evaluated scrutinees are related variants. Apply the two branch induction hypotheses under arbitrary related values for x, respectively y; by the lambda argument above, the elaborated branch lambdas are related to the two source branch functions. The case-analysis clause of lemma 4.43 now treats all three possible tag comparisons. In particular, the residual branch receives the unchanged tag below the inserted position and the decremented tag above it, exactly as the sorted residual row requires. Conversion changes only a row display, and lemma 4.5 leaves the relation unchanged. ◻

Theorem 4.46 — Preservation, adequacy, and coherence of evidence passing

For every derivation D of Γqe:Pτ:

  1. its elaboration has the target type stated in lemma 4.44;

  2. if Γ is empty and G:P is admissible and ground, then there are values v,w such that ev,G(D)[n¯/ΔP]w,vRτ[G]w;

  3. if D changes only the derivations of normalized lacks entailments or the display order of equal strict rows, then can(D)=can((D)) as literal target syntax.

Proof of Theorem 4.46 — Preservation, adequacy, and coherence of evidence passing

Proof. Item 1 is lemma 4.44. For item 2, apply lemma 4.45 to the two empty environments. For item 3, induct on the common source rule tree. Variable and constant leaves select the same scheme instance; lambda and application follow from their induction hypotheses. At a let, the generalized variable set is unchanged because row display and lacks derivations do not change free variables; rename the two fresh static binders to the fixed quantifier order and apply both induction hypotheses. At Q-Conv, canonical target indices agree by lemma 7.49(1). For every record or variant rule, lemma 4.37 gives the same affine evidence expression, the induction hypotheses give the same operands, and lemma 4.5 gives the same canonical row index. These cases cover the rule tree, so the canonical target terms are literal syntactic equals. ◻

Corollary 7.61 — Coherent principal compilation

Suppose qualified W returns an unambiguous principal result for e. Run W with the fixed work-list, quantifier, label, and predicate orders of this chapter, then elaborate its induced typing derivation. The resulting canonical target term is unique up to renaming of freshly allocated static variables.

Proof of Corollary 7.61 — Coherent principal compilation

Proof. The solver clauses are ordered, and lemma 7.38 gives determinism of W up to the unique fresh renaming that fixes the input. The unambiguity condition requires every residual evidence parameter to occur in the visible type interface. Finally, item 3 of theorem 4.46 removes the remaining choices of lacks derivation and row display order. ◻

Remark 7.62 — Scope of coherence

Principal inference fixes the residual row and field-type choices; administrative coherence then removes the remaining choices of row display and lacks derivation. For a fixed source typing tree, the resulting target programs have the same canonical syntax. Predicate weakening can change the evidence tuple, so comparing arbitrary derivations additionally requires the principal-inference choice.

One should not strengthen adequacy to the literal claim that every source step is simulated by one or more target steps. A generalized source let may reduce its bound expression, whereas its evidence-passing translation stores an evidence abstraction, already a target value. The logical relation permits these administrative steps to occur in a different order while still requiring the same ground result. For each primitive record or variant contraction, the proof above does give a direct target simulation; the weaker global statement is needed only because of qualified let generalization.

Exercise 4.7

★☆☆ Assume label order error<host<ok<port. Compute the evidence for each of {host:String,port:Nat}\ok,{ok:α,error:String}\host. Then trace the numeric tag changes when an ok variant is embedded first past host and then past error.

Three nearby designs

The completed calculus lets us compare alternatives without confusing their theorems.

Presence flags

Rémy represents each label by a presence descriptor: pre(τ) when the field is present and abs when absent, with row variables standing for the remaining descriptors. In that notation, strict extension changes the descriptor from abs to pre(τ). Our judgment ξ\ carries the same negative fact separately from the positive row structure.

For a finite label universe, the translation is literal: send {:τρ} to the flag map obtained from ρ by setting to pre(τ), and send every absent label to abs. Lemma 4.5 shows that permutation does not change this map. With an unbounded label set, Rémy’s sorted record algebra is more economical than writing an infinite flag vector. Its unitary unification theorem—every solvable problem has one most general solution up to mutual instantiation—and the Gaster–Jones theorem reach the same principal-inference goal through different syntax; we do not combine their unification rules.

Scoped duplicate labels

If duplicate labels are retained, extension need not require a lacks predicate. Selection and restriction act on the first matching label, and row equality swaps adjacent fields only when their labels differ. Then λr.{scratch=zeror} has an unconstrained row-polymorphic type even when r already contains scratch; the old field remains behind the new one. In our calculus, the same term requires ξ\scratch, while replacement is written explicitly as update.

Neither choice is a notational variant of the other. The duplicate-label calculus admits the row rejected after definition 4.3; our canonical-forms proof uses its impossibility. Its simpler unification does not prove theorem 4.35, and our insertion theorem does not prove principality for its equality. Leijen’s scoped-label calculus gives the alternative rules and proof boundary.

Width subtyping

For this comparison only, take the two rules JI{i:τi}iI<:{j:τj}jJWidthΓe:AA<:BΓe:BSub, where a shared label has the same field type on both sides. These rules are not part of the row calculus proved above; they define the small comparison system used in this paragraph and the following exercise. With width subtyping, one may declare {port:Nat,host:String}<:{port:Nat} and type λr.r.port at the fixed function type {port:Nat}Nat. Subsumption then accepts larger arguments. Row polymorphism instead gives a quantified tail and needs no subsumption judgment.

The difference appears in the result of extension. Subtyping can forget fields to view a large record as a smaller one, but the type {port:Nat}{secure:Bool,port:Nat} does not say that an arbitrary input field survives. The row type ξ.(ξ\secure)Rec(ξ)Rec({secure:Boolξ}) does. Row inclusion and subtyping can coexist, but they solve different typing problems.

Exercise 4.8

★★☆ First derive width subtyping for applying portOf to a record with host, port, and secure fields. Then give the row-polymorphic instantiation. Explain why width subtyping alone cannot recover the precise result type of adding a timeout field while preserving all three inputs.

First-class labels and generic traversal

The strict calculus fixes every operation label in the term syntax. To make a label a value, add a label sort, label variables k, singleton types Lab(k), and lacks predicates with label-variable right sides. The judgment Pξ\k says that the row assigned to ξ has no field at the label denoted by k. The selection rule is Pξ\kΓp:Lab(k)Γr:Rec({k:αξ})ΓselectAt p r:αLabelSelect. The singleton index connects the run-time label value to the row component selected by the operation. Consequently the generic selector has scheme k,α,ξ.(ξ\k)Lab(k)Rec({k:αξ})α. A plain type Label would not suffice: it would forget which row component the value denotes. An existential package k.Lab(k) hides its index, but two package openings alone do not prove that their witnesses differ: both packages may contain the same label. A generative allocator needs an operational freshness invariant. For example, let W be the finite set of allocated labels; allocation chooses kW, returns a package hiding k, and continues with W{k}. Two sequential allocations are distinct because the second choice excludes the first. This subsection uses only first-class selection and does not claim that generative calculus.

First-class labels still do not supply generic traversal. For a type operator F, write Map(F,ρ) for the row obtained by applying F to every field type in ρ. A generic dispatcher would have type Rec(Map(λα.αγ,ρ))Var(ρ)γ. It must inspect whichever label the variant carries and select the matching function from the record. The strict calculus can eliminate one statically named label, but it has neither Map on rows nor induction over an unknown row. Thus repeated uses of its case rule implement the dispatcher only after the finite labels are known. This obstruction separates first-class selection from genuinely generic programming over extensible data.

One concatenation program in two row theories

Consider symmetric concatenation mn, defined only when the input records have disjoint labels, and the program λm.λn.(mn).x. The selected field may come from either input. This single term exposes a constraint that a lacks predicate on one named extension cannot express.

One constraint presentation regards a row as a label-indexed family of descriptors Abs or Pre(τ). If L is a finite or cofinite set of labels, write L:C(φ1,,φn) when the component constraint C holds pointwise for every label in L. Define Merge(a,b,c) by the two successful cases Merge(Pre(τ),Abs,Pre(τ)),Merge(Abs,Pre(τ),Pre(τ)), together with Merge(Abs,Abs,Abs); there is no case with two present inputs. The filtered constraint Labels:Merge(ρ1,ρ2,ρ3),{x}:ρ3=Pre(α) types (7.2). The singleton filter states the selection fact, while the all-label filter states disjoint concatenation.

An abstract row theory packages the same information as two predicates. Write Combine(ρ1,ρ2,ρ3) when the first two rows combine to the third, and write Contains({x:α},ρ3) when the third row contains the selected field. The program then has the qualified type ρ1,ρ2,ρ3,α.(Combine(ρ1,ρ2,ρ3),Contains({x:α},ρ3))Rec(ρ1)Rec(ρ2)α. This formulation can be interpreted by different row-combination algebras; the strict system developed in this chapter fixes only single-field insertion with a lacks side condition.

There is no direct encoding of this program that uses only strict selection from one fixed input and finitely many extensions at labels known in the program text. Instantiate once with x present only in ρ1, and once with it present only in ρ2. Both instances satisfy the two displayed constraint accounts. A strict selection from a fixed input fails one of them, while selecting from the result would require the absent concatenation primitive. Expanding concatenation into extensions is possible for a known finite row, but not uniformly for two unknown row tails. Thus the two instances separate that direct-encoding class. They do not exclude arbitrary whole-program translations into a richer target. The comparison theories add a row-wide combination relation, not merely alternate notation for ξ\.

Bibliographic notes.

The qualified types, mutually recursive unification and insertion procedures, principal inference pattern, and offset-evidence idea follow Gaster and Jones [GJ96]. Their constructor syntax permits raw duplicate rows and relies on predicates at operation sites. Our normalized predicate contexts and strict formation are therefore an adaptation, not a verbatim reproduction. The constrained insertion theorem above proves the correspondence needed here: every admissible strict solution factors through the same fresh-tail exposure, and every generated residual predicate is precisely a strict-formation obligation. We proved termination, factorization, safety, and the restricted administrative coherence of remark 7.62 for this adapted presentation rather than importing those claims. Gaster and Jones give the two evidence-parameter rules and their offset-entailment rules in Section 5.1 and Figure 6, then explicitly omit the complete translation for reasons of space. We therefore define the target in definition 4.38 and the translation in definition 4.39, definition 4.40. The required properties are proved in theorem 4.46.

Rémy’s record algebra and transfer from unitary unification to principal ML typing are in [Ré91]. Scoped duplicate labels are compared from Leijen [Lei05]; those sections respectively give record operations and equality, variants, and the formation (there called kinding) and unification metatheory. That paper’s published proof is not used for the strict calculus. These sources make different choices about duplicates and predicates, so their theorems are not pooled.

The singleton label indices, row-equality predicates, and generativity boundary in subsection 7.8.4 follow the first-class-label calculus of Leijen [Lei04]. The row-mapping dispatcher isolates the additional generic traversal studied by Hubers and Morris [HM23a]. The filtered component account of subsection 7.8.5 is reconstructed from Pottier [Pot03]; the combination and containment predicates are the abstract row-theory interface of Morris and McKinna [MM19]. We use these works for comparison only. The strict safety, inference, and elaboration proofs do not import their metatheorems.

Boundary and further problems

The fields in this chapter have ordinary, nondependent types. If a later field type may mention the value of an earlier field, permutation is no longer a bare exchange: the type must be transported when the dependency crosses the exchange. Likewise, row unification would have to solve terms inside types, not just the sorted first-order equations of definition 4.23. We therefore make no dependent-row claim here. Any dependent extension must state its own substitution, equality, decidability, and inference theorems.

Suggested first pass.

Begin with exercise 4.10. It is a board-sized calculation that tests both admissible grounding and the exact point at which a bad instance fails. Then reconstruct the two factor maps in exercise 4.11; these two problems form the first pass. Use exercise 4.9 as the next synthesis problem and exercise 7.12 as the implementation project. The final two problems test the duplicate-label and evidence-ambiguity boundaries.

Exercise 4.10

★★☆ Let G instantiate ξ by {host:String} in the principal type of withSecure. Write the complete ground typing and reduce its application to {host="db"{}}. Repeat with an attempted instantiation containing secure:Nat and identify the exact failed normalization step.

Exercise 4.11 — Most-general rows (★ )

★★☆ Suppose (U,QU) and (V,QV) are principal solutions of the same row equation problem over P. Use their two factorization properties to construct admissible maps R:QUQV and S:QVQU with VQVU;R and UQUV;S on the problem variables. Give a two-label example where the printed fresh tail names, residual predicate contexts, and field orders differ although this mutual instance conclusion holds.

Hint. Apply the factorization property of U to the solution V, then the factorization property of V to U. For the example, use the crossing equation of example 4.24 twice, with different fresh tail names and different legal exchange sequences.

Exercise 4.9

★★★ Assume m and a tail ξ lacking both labels. Infer the principal scheme of λa.λb.λr.{:=a{m:=br}}. Show by row permutation that reversing the two updates gives the same result type. Does the operational result have the same finite map? Give the two restriction-and-extension calculations.

Exercise 7.12

★★★ Practical project.row-inferencer Work in artifacts/ch7-rows/corpus.kp. Implement the four insertion and six solver clauses of definition 4.23 in their printed order, including U-Delete. Return the actual clause trace with every successful solver result; do not predict it by traversing the input a second time. Preserve the invariant that the returned substitution is admissible from the input predicate context to the normalized output context.

The accepted crossing run must return and print U-Row -> I-Skip -> I-Var -> U-Var -> U-Done. Apply its substitution to both input rows, check that both substituted rows are strictly formed under the returned predicates, and then check contextual row equality. Also test syntactic identity deletion, principal factorization, termination within the input-derived bound, record and variant operations, fresh supply, qualified generalization at two instances, empty insertion, the occurs check, duplicate formation, and the nonprincipal closed-tail mutant. Run the four commands in Appendix F and require all eleven PASS lines, the printed summary, and audit output []. Finally apply mutant-accept-empty-insertion.patch to a disposable copy: it must still check and must fail the empty-insertion oracle. These finite runs are implementation evidence; they do not prove termination, factorization, principality, coherence, inference correctness, or safety.

Exercise 4.12 — Scoped duplicate records (★ )

★★★ Design the record fragment of a scoped-duplicate calculus, rather than changing formation alone. Make the following four changes.

  1. Delete the lacks premise from each extension rule: RowExt,QExtend.

  2. Permit repeated labels in record values.

  3. Let selection and restriction act on the first displayed occurrence.

  4. Continue to swap only adjacent distinct labels.

Which sentence in the proof of lemma 4.21 now becomes false? Repair the canonical-forms statement for this calculus, and state why the repaired lemma does not justify the strict update equation without specifying which occurrence is removed.

Do not extend this answer to variants. A scoped-duplicate variant calculus must additionally change embedding, represent the nesting level of equal labels at run time, and give matching case rules; the label-only variant values of definition 4.17 cannot express that information.

Hint. Induct on the displayed record spine. Selection returns the first matching occurrence, whereas restriction removes that occurrence and leaves every later occurrence available.

Exercise 4.13 — Ambiguous evidence (★ )

★★★ Consider the scheme ξ.(ξ\)Nat. Its result type does not determine which evidence is required. Show that all its ground instances have the same source type but may elaborate to functions expecting offsets for different rows. State the usual unambiguity condition for an open judgment and its closed-scheme specialization: ftv(P)ftv(Γ,τ),ftv(P)ftv(τ). Now run Wr on case=zero of {=xzero;yzero}. Show that its principal result contains a fresh predicate ξ\ although its result type is Nat. Thus unambiguity is an additional acceptance condition, not an invariant of the inference clauses. Give two possible repairs: reject such ambiguous principal results, or choose and document a default ground row when evidence is elaborated.

Hint. Give injection a fresh tail ξ. The case result forgets that tail because both branches return Nat, but the injection’s formation constraint ξ\ remains in the normalized predicate set.

Search the book

Type to search the local edition.