Lectures onType Theory
Chapter 27
Chapter 27Core route

OO Self Types, F-Bounds, and Matching

The type of a method may mention the type of its receiver. This is harmless until the object is extended. Then two demands pull in opposite directions. The old code should be inherited without being rechecked, while every occurrence of the receiver type in that code should now denote the extended object. Ordinary recursive subtyping can satisfy the demand when all those occurrences are covariant. A binary method puts the receiver type in an argument position and exposes the obstruction. Heuristically, a covariant recursive record body can preserve a subtype comparison through one unfolding, whereas an antitone occurrence reverses the comparison and blocks the argument. The Point/ColorPoint pair below makes that obstruction concrete in the recursive target calculus of chapter 13.

Where the receiver type changes sign

Let the functional record operators PointF(X):={x:Nat,clone:UnitX,move:NatX},ColorPointF(X):={x:Nat,color:Bool,clone:UnitX,move:NatX} describe the two interfaces. Define Point=μX.PointF(X),ColorPoint=μX.ColorPointF(X). Unlike the invariant primitive object components of chapter 13, these are immutable covariant records. The Point/ColorPoint comparison therefore succeeds here; the binary-method case below is the obstruction that remains. This opening calculation takes place specifically in the F<:μ target calculus of definition 15.16; it is not a derivation in any of the three calculi defined later in this chapter. Its recursive-subtyping premise is the Amber-style premise, checked under X<:Y. Record width and arrow covariance in the result give ColorPointF(X)<:PointF(Y), and hence the desired ColorPoint<:Point. At the more precise static type, move returns ColorPoint, and so does clone applied to unit. After subsumption to Point, they return only Point. This loss is safe: the shorter view no longer promises color. The premise that drives proposition 15.12 is absent here: that counterexample overrides a method retained through a shorter view, while this record grammar contains no update term. Subsumption therefore changes only the static view of an immutable record.

Now add an equality-like binary method: EqPointF(X):={x:Nat,eq:XBool},EqColorPointF(Y):={x:Nat,color:Bool,eq:YBool}. The recursive-subtyping attempt reaches X<:Y(XBool)<:(YBool). Arrow subtyping asks for Y<:X, exactly the premise we do not have. Declaring the comparison covariant would be unsound. Code in the colored method may inspect its argument’s color component, while a context that sees the receiver merely as an equality point may pass an uncolored point.

Remark 21.1 — The variance obstruction

For immutable records, a receiver occurrence in a method result supports the recursive-subtyping comparison above. A receiver occurrence in a method argument reverses the required comparison. Therefore width extension and ordinary recursive subtyping do not supply the desired specialization of a binary-method argument from Point to ColorPoint. For a result occurrence the new arrow premise is X<:Y, the recursive assumption. For an argument occurrence, arrow subtyping exchanges the domain comparison and asks instead for Y<:X, which width does not provide.

Exercise 16.1

★☆☆ Add a method copyFrom:XX to both operators. Under the recursive assumption X<:Y, write both arrow-subtyping premises. Identify which one is available and which one fails. Then replace the argument type in both interfaces by the fixed type Point and repeat the calculation.

A functional calculus with hidden Self

Put the changing receiver type inside the object while hiding its exact representation from clients. The calculus is intentionally functional: records never change in place, and no store occurs in its safety theorem.

Positive families and the two Self operations

For a distinguished variable X, define positive and negative occurrence simultaneously. An X-free type has both polarities; X itself is positive; records preserve polarity componentwise; and an arrow reverses it in the domain and preserves it in the codomain. Thus X and NatX are positive, whereas XBool is negative. We do not permit a free occurrence of the distinguished X beneath a nested Self binder.

Lemma 16.3 — Polarity is monotonicity

Suppose ΔC<:D. If X is positive in B(X), then ΔB(C)<:B(D). If X is negative in B(X), then ΔB(D)<:B(C).

Proof of Lemma 16.3 — Polarity is monotonicity

Proof. Induct on the type, proving the positive and negative statements simultaneously. The X case is the given subtyping; an X-free type gives reflexivity. Records use fieldwise covariance. For an arrow A(X)B(X), the induction hypothesis for the domain has the reversed orientation and the hypothesis for the codomain has the direct orientation. Arrow subtyping combines exactly those two comparisons. The negative case exchanges the two induction hypotheses. ◻

Definition 16.4 — The calculus Self_+

This is the fragment of chapter 8 with records, arrows, Top, and bounded type variables—without bounded quantifiers, Bot, products, or sums—extended by general recursion and two Self-specific term forms. A Self package hides its representation while retaining a public upper bound. The rules below therefore compare Self packages, introduce a hidden witness, and eliminate that witness under an escape condition. Self formation also requires the displayed payload family to be positive in its bound variable. Types, terms, and values are A,B::=NatBoolUnitTopXAB{i:Ai}iISelfX.{i:Bi(X)}iI,a,b::=xntruefalseunitsucc(a)λx:A.aabif a then b else c{i=ai}iIa.fix x:A.apackSelf C with a as SuseSelf a  as X<:S,x:RS(X)useSelf a in b,v::=ntruefalseunitλx:A.a{i=vi}iIpackSelf C with v as S. Here a Self type S=SelfX.RS(X) hides the representation named by X while exposing the public payload family RS(X)={i:Bi(X)}iI. The labels are distinct, and every Bi(X) is positive in X. The binder X names the hidden representation type; opening a package substitutes its witness only for this bound type variable. It is distinct from the term variable bound by fix. The intended expansion is the bounded-existential recursive type SelfX.R(X):=μY.X<:Y.R(X), with YFV(R). We keep the two operations primitive so that their type and term substitutions remain visible. Equation (16.1) is a design explanation, not a definitional equality or a translation theorem between this primitive calculus and the target of chapter 13. The primitive rules below expose the same public bound, hidden witness, payload family, and escape condition; no result is transferred silently between the systems.

The ordinary rules are call-by-value simply typed lambda calculus, immutable covariant records, bounded type variables, and subsumption. Subtyping is the least relation closed under reflexivity, transitivity, and the following rules:

X<:AΔ
ΔX<:A
S-Bound
ΔA<:Top
S-Top
ΔA<:AΔB<:B
ΔAB<:AB
S-Arrow
JIΔAj<:Bj (jJ)
Δ{i:Ai}iI<:{j:Bj}jJ
S-Record
Δ,X<:TopR(X)<:R(X)
ΔSelfX.R(X)<:SelfX.R(X)
S-Self

All types in a rule are well formed. A type context is formed from left to right; a declaration X<:A requires A to be formed in the preceding context. Arrow and record formation require their displayed component types, and Self formation additionally requires the positive-family condition above. The complete ordinary term rules are

x:AΓ
Δ;Γx:A
T-Var
Δ;Γn:Nat
T-Const
b{true,false}
Δ;Γb:Bool
T-Bool
Δ;Γunit:Unit
T-Unit
Δ;Γa:Nat
Δ;Γsucc(a):Nat
T-Succ
Δ;Γ,x:Aa:B
Δ;Γλx:A.a:AB
T-Abs
Δ;Γa:ABΔ;Γb:A
Δ;Γab:B
T-App
Δ;Γa:BoolΔ;Γb:AΔ;Γc:A
Δ;Γif a then b else c:A
T-If
Δ;Γai:Ai(iI)
Δ;Γ{i=ai}iI:{i:Ai}iI
T-Record
Δ;Γa:{i:Ai}iIjI
Δ;Γa.j:Aj
T-Proj
Δ;Γ,x:Aa:A
Δ;Γfix x:A.a:A
T-Fix
Δ;Γa:AΔA<:B
Δ;Γa:B
T-Sub

The Self-specific typing rules are

S=SelfX.RS(X)ΔC<:SΔ;Γa:RS(C)
Δ;ΓpackSelf C with a as S:S
T-PackSelf
S=SelfX.RS(X)Δ;Γa:SΔ,X<:S;Γ,x:RS(X)b:DXFV(D)
Δ;ΓuseSelf a as X<:S,x:RS(X) in b:D
T-UseSelf

Rule T-Fix is unrestricted general recursion. Consequently this safety calculus is not normalizing: for example fix x:Nat.succ(x) has type Nat and reduces forever.

The notation in equation 16.1 does not assert equi-recursive equality. Rule T-PackSelf combines bounded-existential packaging with fold; T-UseSelf combines opening with unfold. Thus the public type S, hidden witness C, and payload type RS(C) remain distinct.

For a:S and j:Bj(X) in RS(X), define selection by aj:=useSelf a as X<:S,x:RS(X) in x.j, where the body is subsumed from Bj(X) to Bj(S). This last step exists by lemma 16.3 and X<:S. The result type does not contain X, so the escape condition is satisfied. This is the exact point at which the positive-family condition performs work. Selection therefore has a one-line root behavior: (packSelf C with v as S0)jv.j, where the step abbreviates expansion by equation 16.2 followed by (UseSelf); an ordinary projection step follows when v is a record value.

Exercise 16.2

★☆☆ Classify each family as positive, negative, both, or neither in X: X,NatX,XBool,(XBool)Nat,{a:X,b:NatX}. For every positive case, write the subtyping obtained from C<:D. For every rejected method family, identify the arrow premise with the wrong orientation.

Point and ColorPoint are complete packages

Define P:=SelfX.PointF(X),C:=SelfX.ColorPointF(X). Every family is positive. Under X<:Top, immutable-record width gives ColorPointF(X)<:PointF(X); therefore S-Self derives C<:P.

The following are closed terms: p0:=fix p:P.packSelf P with{x=0,clone=λu:Unit.p,move=λd:Nat.p} as P,c0:=fix c:C.packSelf C with{x=0,color=true,clone=λu:Unit.c,move=λd:Nat.c} as C. For p0, the recursion assumption p:P types the payload at PointF(P)={x:Nat,clone:UnitP,move:NatP}. By reflexivity, P<:P, so T-PackSelf gives the body type P and the recursion rule gives p0:P. The derivation for c0:C has the same five steps with the additional Boolean field. It follows both that c0clone:UnitC and, after subsuming the receiver, that (c0:P)clone:UnitP. Here and below (a:A) is a displayed elaboration cue: it means that the term a is first used at type A by T-Sub. It is not an additional term constructor or reduction rule.

Definition 16.5 — Call-by-value reduction

The root contractions are (λx:A.a)va[v/x],succ(n)n+1,if true then a else ba,if false then a else bb,{i=vi}iI.jvj(jI),fix x:A.aa[fix x:A.a/x],(UseSelf)useSelf(packSelf C with v as S0)as X<:S,x:RS(X) in bb[C/X][v/x]. Evaluation is the compatible closure under the deterministic left-to-right contexts E::=[]succ(E)EavEif E then a else bE.{1=v1,,k1=vk1,k=E,k+1=ak+1,}packSelf C with E as SuseSelf E as X<:S,x:RS(X) in b. No context enters a lambda, a recursion body, or a useSelf body.

Put r0={x=0,clone=λu:Unit.p0,move=λd:Nat.p0}. For a readable trace, abbreviate the opened clone body by uclone:=useSelf(packSelf P with r0 as P)as X<:P,z:PointF(X) in z.clone. Expanding the derived selection and taking one recursion step gives (p0clone)unitucloneunitUseSelfr0.cloneunitprojection(λu:Unit.p0)unitβp0unfoldrecursionpackSelf P with r0as P. The (UseSelf) step substitutes the witness type P for the type binder X, then the payload for the term binder z. It does not substitute the recursive term p0 for X. The last step unfolds the returned fixpoint once; its result is a package value. Stopping at p0 is useful when naming the recursive object but is not a complete call-by-value reduction.

Exercise 16.3

★★☆ Expand equation 16.2 and reduce (c0move)3 to the named recursive term c0. This requested endpoint is not a normal form: take one further fixpoint-unfolding step to display its package value. At the (UseSelf) step, display the type substitution and the term substitution separately. Give the type of every intermediate application.

Safety

The Self redex can be opened at a supertype of the package annotation. The proof must therefore recover the relation between the actual and expected families; merely observing that a package is present is insufficient.

Lemma 16.6 — Structural properties of Self_+

The following are admissible.

  1. Type weakening and term weakening hold. Moreover, if a judgment is derivable under Δ0,X<:A,Δ1 and Δ0C<:A, it remains derivable under Δ0,X<:C,Δ1.

  2. If a formation, subtyping, or typing judgment is derivable under Δ0,X<:A,Δ1 and Δ0C<:A, then substituting C for X in the judgment and trailing contexts yields a derivable judgment.

  3. If Δ;Γ,x:Ab:B and Δ;Γa:A, then Δ;Γb[a/x]:B.

Proof of Lemma 16.6 — Structural properties of Self_+

Proof. For item 1, weakening is induction on derivations after alpha-renaming every binder away from the appended declarations. For bound narrowing, the distinguished S-Bound conclusion X<:A is recovered from X<:C<:A by transitivity; every other rule applies the induction hypotheses to its premises and restores the same rule.

For item 2, use simultaneous induction on formation, subtyping, and typing. The distinguished S-Bound case is the premise C<:A. Records apply the hypotheses fieldwise; arrows apply them contravariantly to the domain and covariantly to the codomain. In S-Self, alpha-rename the inner Self variable away from C, substitute in the family comparison, and restore the rule. For T-PackSelf, the required family identity is (RS(D))[C/X]=αRS[C/X](D[C/X]), after freshening the Self binder. The T-UseSelf case uses the same identity, substitutes in the scrutinee and body, and preserves the escape condition because the bound variable was chosen fresh.

For item 3, induct on typing. The distinguished variable case uses the assumed typing of the substituend; every other variable retains its declaration. Alpha-rename lambda and recursion binders away from the substituend. In T-PackSelf, substitute in the payload. In T-UseSelf, substitute independently in the scrutinee and, after freshening both binders, in the body; its result type and escape condition are unchanged. Records, applications, and conditionals apply the induction hypotheses to their immediate premises. These cases exhaust the term grammar. ◻

Lemma 27.6 — Outer-shape inversion

Suppose ΔC<:A, C is not a type variable, and the outer constructor of A is an arrow, a record, or Self. Then C has the same outer constructor as A. Moreover, if C is not a type variable, there is no derivation of C<:X.

Proof of Lemma 27.6 — Outer-shape inversion

Proof. Prove both statements simultaneously by rule induction. Reflexivity is immediate. The arrow, record, and Self rules preserve their displayed outer constructors. Rule S-Top cannot end at any of the three target shapes. Rule S-Bound has a type variable on the left and therefore cannot be the last rule for the stated C.

For transitivity, write C<:D<:A. Both premises have smaller derivation height, so both simultaneous induction hypotheses are available. For the outer-shape claim, split on whether D is a type variable. If it is, the no-nonvariable-below-variable hypothesis applied to C<:D contradicts the assumption that C is not a variable. If it is not, the outer-shape hypothesis applied to D<:A gives D the outer constructor of A; applying it once more to C<:D gives C that constructor.

For the no-nonvariable-below-variable claim, the target is a variable X. Again split on D. If D is a variable, the hypothesis for C<:D is a contradiction. If D is not a variable, the hypothesis for D<:X is a contradiction. These two splits also cover D=Top, which lies in the nonvariable branch; no unproved outer-shape assertion about the intermediate is required. ◻

Lemma 16.7 — Self-subtyping inversion

If ΔS0<:S, where S0=SelfX.R0(X)andS=SelfX.R(X), then, after alpha-renaming the binders to agree, Δ,X<:TopR0(X)<:R(X). Moreover, in every factorization ΔS0<:C<:S, the intermediate type C is a Self type up to alpha-equivalence; it is not a type variable, Top, an arrow, or a record.

Proof of Lemma 16.7 — Self-subtyping inversion

Proof. Induct on the subtyping derivation. Reflexivity gives R0(X)<:R0(X), and S-Self has the required family comparison as its premise. In a transitive derivation with Self type at both ends, the shape claim gives C=αSelfX.R1(X). The two induction hypotheses give R0(X)<:R1(X) and R1(X)<:R(X) under X<:Top; transitivity gives the required comparison. Lemma 27.6 excludes a type-variable intermediate and gives the Self outer shape directly; in particular, no derivation can return from Top, an arrow, or a record to a Self type. ◻

Lemma 16.8 — Canonical forms

Let v be closed and well typed.

  1. If v:AB, then v=λx:C.a for some C,a.

  2. If v:{i:Ai}iI, then v is a record value containing every displayed label.

  3. If v:S for a Self type S, then v=packSelf C with w as S0, where C<:S0<:S and w:R0(C) for the family of S0.

Proof of Lemma 16.8 — Canonical forms

Proof. Strip final subsumption rules. The remaining value-introduction type is not a type variable, so lemma 27.6 applies to the accumulated subtype chain. At arrow type it forces an arrow introduction; the only such value rule is T-Abs, and inversion returns v=λx:C.a. At record type it forces T-Record, which returns a record value with one typed field for every label in the conclusion. At Self type it forces T-PackSelf; lemma 16.7 gives S0<:S, and inversion of T-PackSelf gives C<:S0 and w:R0(C). ◻

Theorem 16.9 — Preservation

If Δ;Γa:A and aa, then Δ;Γa:A.

Proof of Theorem 16.9 — Preservation

Proof. Induct on the reduction derivation. Compatible cases rebuild their typing rule after applying the induction hypothesis to the active subterm. Beta and recursion use term substitution. Record projection and the ground roots follow by inversion.

It remains to prove the generalized (UseSelf) root. Suppose the actual package annotation is S0=SelfX.R0(X), while the use is checked at S=SelfX.R(X). Typing inversion through final subsumption is an induction on the typing derivation: T-PackSelf gives the witness and payload judgments, and each T-Sub composes one more subtype step. Hence C<:S0,v:R0(C),S0<:S. By lemma 16.7, X<:TopR0(X)<:R(X). Type substitution with the well-formed type C gives R0(C)<:R(C), so subsumption yields v:R(C). The body premise is Δ,X<:S;Γ,x:R(X)b:D,XFV(D). Since C<:S0<:S, type substitution gives Δ;Γ,x:R(C)b[C/X]:D. Term substitution then gives Δ;Γb[C/X][v/x]:D, precisely the reduct. Thus the actual hidden witness, not the public annotation, controls the contraction. ◻

Exercise 16.4

★★☆ Assume C<:S0<:S1<:S and a package created at S0 is opened by a use checked at S. Reconstruct the (UseSelf) preservation case, including the two applications of transitivity, Self-family inversion, type substitution, payload subsumption, and term substitution. Explain why replacing the runtime witness C by the public type S is invalid.

Theorem 16.10 — Progress and functional safety

If ;a:A, then a is a value or there is an a with aa. Together with theorem 16.9, no closed well-typed term reaches a stuck term.

Proof of Theorem 16.10 — Progress and functional safety

Proof. Induct on typing after removing final subsumption. Variables are impossible in the empty term context. Introduction forms are values after their left-to-right subterms become values. For application, the function steps, the argument steps, or lemma 16.8 proves that the function is a lambda and beta applies. The conditional and projection cases use the Boolean and record canonical forms. A recursion term always unfolds.

For useSelf a as, the induction hypothesis says that a steps or is a value. In the latter case, the Self canonical form proves that a is an actual package, so (UseSelf) applies even when its annotation is a proper subtype of the type expected by the use. These cases exhaust the grammar. Preservation keeps the same type along every finite reduction sequence, and progress applies to its last term. ◻

Why negative Self is rejected

The family BadEq(X)={x:Nat,eq:XBool} does not form a Self+ type. To see the operational reason, consider the hypothetical calculus obtained by deleting only the positivity check. Put PE:=SelfX.{x:Nat,eq:XBool},CE:=SelfX.{x:Nat,color:Bool,eq:XBool},pE:=packSelf PE with{x=0,eq=λq:PE.false} as PE,cE:=packSelf CE with{x=0,color=true,eq=λq:CE.qcolor} as CE. The record-family premise of S-Self would indeed compare the two same-parameter fields XBool<:XBool; width would then give CE<:PE. That pointwise width calculation is not the error. The error is using the hidden bound X<:PE at selection time to turn XBool into PEBool. Arrow subtyping gives the opposite comparison, PEBool<:XBool, and supplies no such coercion.

If the bad coercion is inserted, the closed term eE:=((cE:PE)eq)pE:Bool has the following reduction. Expanding the first derived selection and opening the colored package yields eEUseSelf(λq:CE.qcolor)pEpEcolor{x=0,eq=λq:PE.false}.color. The last term is neither a value nor a redex. The displayed packages show that the failure is not merely a failed proof of preservation. Rejecting the negative occurrence rules this stuck program out; admitting it would type the program just reduced.

Exercise 16.5

★☆☆ Replace the Boolean result of eq by {ok:Bool}, repeat the bad reduction above, and mark the one selection coercion that cannot be derived in Self+. Explain why the pointwise record-width premise of S-Self is not itself that coercion.

Primitive covariant Self therefore handles clone-like and other self-returning methods. It does not handle a binary method whose argument was obtained independently of the receiver. Inside one open package, a method can consume another value of the same hidden witness if one is available; two separately opened packages need not choose the same witness. Matching addresses inheritance of such code by changing the relation on the one-step record families, not by weakening this safety condition.

F-bounds describe fluent code

The failed subtype judgment does not prevent us from writing one piece of code for every type that supports the same chain of methods returning that type; this is the sense of fluent used here. The bound must mention the type being bounded. This section changes calculi. Its recursive types are equi-recursive comparison types, not the iso-recursive package expansion of equation 16.1. Here equi-recursive means that a recursive type and its one-step unfolding are definitionally equal. No term-level fold or unfold is required; iso-recursive types instead keep the two types distinct and cross between them with explicit terms, as in chapter 12.

Definition 16.11 — F-bounded quantification

The types have bounded quantifiers, immutable records, arrows, and recursion. In μX.A, every occurrence of X must lie beneath a record; call this condition contractiveness. Type equality contains alpha-equivalence and μX.A=A[μX.A/X]. Formation, typing, and subtyping are closed under replacement by definitionally equal types. Subtyping, taken modulo this equality, is the least relation closed under reflexivity, transitivity, bounded-variable, arrow, and record subtyping. Equivalently, before applying a structural subtyping rule one may unfold either recursive type any finite number of times. There are no term-level fold and unfold operations in Feq.

An F-bound is a recursive upper bound X<:F(X) whose bound expression may mention the variable being bounded. F-bounded types and terms are X<:F(X).A,ΛX<:F(X).a,a[C]. Call the one-step record family F() the object’s protocol: the record-shaped interface that an implementation must support. The binder X ranges over client-chosen implementations of this protocol, and type application substitutes its argument only for that bound parameter. The bound is formed by checking F(X) under X<:Top. The rules particular to the extension are

Δ,X<:F(X)A type
ΔX<:F(X).A type
F-All
Δ,X<:F(X);Γa:A
Δ;ΓΛX<:F(X).a:X<:F(X).A
F-Intro
Δ;Γa:X<:F(X).AΔC<:F(C)
Δ;Γa[C]:A[C/X]
F-Elim

Type application contracts by (ΛX<:F(X).a)[C]a[C/X].

The calculus validates formation, typing of the displayed clients, and preservation of the type-application root. Equality and subtyping of its equi-recursive types are declarative here.

Call a type C a post-fixpoint of F when C<:F(C). Equi-recursive conversion proves concrete inequalities C<:F(C), and an F-bound says which of them may instantiate a given client. Neither choice adds a primitive Self package.

Put moveAll:=ΛX<:PointF(X).λp:X.λd:Nat.p.moved. Under X<:PointF(X), subsumption gives p:PointF(X), hence p.move:NatX. Thus moveAll:X<:PointF(X).XNatX. Any type C satisfying C<:PointF(C) may instantiate this term, and the result remains C. This is the fluent use of an F-bound.

Here is a concrete legal instantiation. Define Pointeq:=μX.PointF(X). The two separate judgments needed are then available: Pointeq=(21.3)PointF(Pointeq),Pointeq<:reflexivityPointeq=(21.3)PointF(Pointeq). Therefore F-Elim derives moveAll[Pointeq]:PointeqNatPointeq. This derivation would be incomplete in an iso-recursive calculus: there an explicit term fold cannot be replaced by a subtype premise. The familiar Java pattern T extends ComparableT has the same F-bound shape: the client chooses one T, and the bound mentions that very T in its protocol.

Lemma 16.12 — F-bound instantiation

If Δ,X<:F(X);Γa:A and ΔC<:F(C), then Δ;Γ[C/X]a[C/X]:A[C/X]. Consequently the root reduction of F-Elim preserves type.

Proof of Lemma 16.12 — F-bound instantiation

Proof. Induct simultaneously on formation, subtyping, and typing. The distinguished bound-variable case is exactly the assumption C<:F(C). In an arrow, record, or application rule, substitute in every premise and restore the same rule. At a nested type binder, alpha-rename it away from C, apply the induction hypothesis, and restore the binder. The term-variable cases merely substitute in their declared types.

For equi-recursive conversion, choose the recursive binder Y distinct from X and fresh for C. Substitution commutes with unfolding by the composition equation (A[μY.A/Y])[C/X]=αA[C/X][μY.A[C/X]/Y], same binder-fresh induction as lemma 24.2. The induction is rerun for the Feq grammar: records, bounded quantifiers, and their term annotations add componentwise congruence cases. Thus a converted premise remains definitionally equal to the substituted conclusion; the congruence cases commute homomorphically with substitution. Applying the result to the premise of F-Intro gives the type of a[C/X], which is the reduct. ◻

F-bounds can also type a homogeneous binary operation. With EqF(X):={x:Nat,eq:XBool}, the term ΛX<:EqF(X).λp:X.λq:X.p.eqq has type X<:EqF(X).XXBool. The two arguments have one chosen type X, so the contravariant occurrence causes no coercion. What F-bounded quantification does not prove is substitutability between the recursive binary-method types EqPointeq:=μX.EqF(X),EqColorPointeq:=μX.{x:Nat,color:Bool,eq:XBool}. Unfolding a proposed subtype comparison reverses the equality-method domain and asks for EqPointeq<:EqColorPointeq, which record width cannot supply. F-bounds parameterize the client; they do not turn protocol extension into subsumption.

What an F-bound forgets about a Self package

For this comparison, let R range only over the positive families generated by the common grammar L(X)::=NatBoolUnitTopXL(X)L(X){i:Li(X)}iI,R(X)::={i:Li(X)}iI. Only X may occur free. There is no primitive Self, recursive type, or bounded quantifier inside an L. The outer record makes R contractive, so the same R is legal in Self+ and beneath μ in Feq.

The two calculi agree on one construction and part company on another. For such a family R, form S=SelfX.R(X),S^=μX.R(X)in Feq. On the F-bounded side, a record at the public witness S^ has payload type R(S^)=S^, so it is a legal post-fixpoint. An F-bounded client X<:R(X).D(X) may therefore be instantiated at S^. This is the public-witness fragment of primitive Self.

The general T-PackSelf premise is different: C<:S,c:R(C)packSelf C with c as S:S. Erasing this package to S^ would require a term of R(S^). The payload premise has type R(C), not R(S^); moreover the package deliberately hides C. Revealing that witness, or replacing it everywhere by S^, destroys the representation abstraction implemented by the bounded existential. Thus the public-witness construction is not a rulewise translation of primitive Self.

Example 27.13 — A hidden witness outside the equi-recursive calculus

For a concrete instance, take R(X)={next:X},S=SelfX.R(X),C=SelfX.{next:X,tag:Nat}. Record width and S-Self give C<:S. A recursive package cC:C with payload {next=cC,tag=0} yields, by T-PackSelf, a second package at S whose hidden witness is C and whose payload is {next=cC}:R(C). The premise contains no judgment C<:S^, where S^=μX.{next:X} belongs to the distinct comparison calculus. Hence no premise derives a payload of type R(S^). This is a specific witness to the boundary, not merely a statement about notation.

The converse comparison loses a different property. An F-bounded client may quantify over the negative family EqF, because it assumes one homogeneous type X and performs no coercion between distinct post-fixpoints. Primitive Self+ admits first-class packages and covariant package subsumption, but rejects that negative family. Hence neither presentation contains the other: F-bounds lose hidden-witness packaging, while the positive Self calculus deliberately gives up binary Self selection.

Proposition 21.12 — Boundary of the public-witness comparison

For a positive contractive record family R, S^=μX.R(X) is a legal instantiation of every Feq client bounded by R. The premises of T-PackSelf do not, in general, derive a payload at R(S^), and so do not define an erasure of every Self+ package to S^.

Proof of Proposition 21.12 — Boundary of the public-witness comparison

Proof. The first assertion is equation 21.3 followed by reflexivity and F-Elim. For the second, take the legal package of example 27.13, with hidden witness C<:S. Its only payload judgment is c:R(C). Positivity can transport a payload along a proved subtype C<:S^, but the package premise is C<:S, where S is a Self+ type and S^ is an Feq type. No judgment form of either calculus contains types from both grammars, so no rule relates these two types. Consequently the required transport is absent. Adding it would expose precisely the representation relation that the existential hides. ◻

Exercise 16.6

★★☆ Define ResetF(X)={x:Nat,reset:X,same:XBool}. Give a complete typing derivation for a polymorphic term that takes p:X, invokes reset, and compares the result with p. State why the derivation does not imply a subtype relation between two different post-fixpoints of ResetF. Then instantiate it at μX.ResetF(X), displaying equi-recursive conversion and subtyping as two separate judgments.

Matching is protocol extension, not subtyping

Matching compares object types by their protocols before the recursive knot is tied. We make that sentence literal in a small higher-order target. The target is fixed here because a translation into an unnamed higher-kinded language would not be a theorem.

Definition 21.13 — Restricted higher-order target H_μ

Kinds are κ::=. Type operators and proper types are F,G::=ΦλX:.A,A,B::=XNatBoolUnitAB{i:Ai}iIF(A)μFΦF.A. Kinds classify type-level expressions. The kind contains ordinary types such as Nat and records; the kind contains unary functions from ordinary types to ordinary types. For example, λX:.{n:Nat,max:XX} has kind , and applying it to Nat: produces the proper record type {n:Nat,max:NatNat}:. Operator beta is definitional equality: (λX:.A)(B)=A[B/X], and type formation and subtyping are closed under this equality. Target terms extend the ordinary lambda-and-record terms by t::=ΛΦF.tt[F]foldF(t)unfoldF(t),v::=ΛΦF.tfoldF(v). An operator context contains X: and ΦF:. Every operator variable in such a declaration is stipulated contractive, and its bound must be contractive. In this restricted target, λX:.A is contractive exactly when every occurrence of X lies beneath a record constructor; operator variables declared contractive are contractive. Proper-type subtyping has reflexivity and transitivity together with the ordinary arrow and record rules:

ΩA<:AΩB<:B
ΩAB<:AB
S-H-Arrow
JIΩAj<:Bj (jJ)
Ω{i:Ai}iI<:{j:Bj}jJ
S-H-Record

The ordinary target term rules are

x:AΓ
Ω;Γx:A
T-H-Var
Ω;Γ,x:At:B
Ω;Γλx:A.t:AB
T-H-Abs
Ω;Γt:ABΩ;Γu:A
Ω;Γtu:B
T-H-App
Ω;Γti:Ai (iI)
Ω;Γ{i=ti}iI:{i:Ai}iI
T-H-Record
Ω;Γt:{i:Ai}iIjI
Ω;Γt.j:Aj
T-H-Proj
Ω;Γt:AΩA<:B
Ω;Γt:B
T-H-Sub

The rules particular to higher-order operators and recursion are

Ω,X:A:
ΩλX:.A:
K-OpAbs
ΩF:ΩA:
ΩF(A):
K-OpApp
ΩF:Contr(F)
ΩμF:
K-Mu
Ω,X:F(X)<:G(X)XFV(Ω,F,G)
ΩFG
S-OpPoint
ΦF:Ω
ΩΦF
S-OpBound
ΩFGΩA:
ΩF(A)<:G(A)
S-OpApp

Operator subtyping also has reflexivity and transitivity. Bounded operator quantification and iso-recursion have the rules

ΩF:Contr(F)Ω,ΦF:A:
ΩΦF.A:
K-AllOp
Ω,ΦF;Γt:A
Ω;ΓΛΦF.t:ΦF.A
T-AllOp-I
Ω;Γt:ΦF.AΩG:Contr(G)ΩGF
Ω;Γt[G]:A[G/Φ]
T-AllOp-E
Ω;Γt:F(μF)
Ω;ΓfoldF(t):μF
T-Fold
Ω;Γt:μF
Ω;ΓunfoldF(t):F(μF)
T-Unfold

Here annotations on operator bounds include their kind even when suppressed typographically. The new call-by-value roots are (ΛΦF.t)[G]t[G/Φ],unfoldF(foldF(v))v. The new evaluation contexts are E::=E[F]foldF(E)unfoldF(E). Thus μF in Hμ is iso-recursive; it is not the equi-recursive μ of Feq.

The ordinary target rules have an immediate complete use before the operator translation begins. Put R={n:Nat}. Under x:Nat, its lines are ;x:Natx:Natby T-H-Var,;x:Nat{n=x}:Rby T-H-Record,;x:Nat,r:Rr:Rby T-H-Var,;x:Nat,r:Rr.n:Natby T-H-Proj,;x:Natλr:R.r.n:RNatby T-H-Abs,;x:Nat(λr:R.r.n){n=x}:Natby T-H-App. Match abstraction uses T-AllOp-I, bounded instantiation uses T-AllOp-E, and exposure of a recursive protocol uses T-Unfold.

The translation has exactly the following source domain. Source protocols and their target images use the same Self-free family grammar L::=NatBoolUnitZLL{i:Li}iI,R(Z)::={i:Li(Z)}iI. Every R is contractive in Z. The remaining source grammar is O::=XμZ.R(Z),D::=NatBoolUnitODDX#O.D,t::=xλx:D.tttΛX#O.tt[O]x.. Source contexts are Ξ::=Ξ,X#O,Γ::=Γ,x:D. In X#O, the binder X ranges over types whose protocols extend the protocol of O; instantiation substitutes only for this match-bound variable. The recursive binder of a concrete protocol remains the separate variable Z in μZ.R(Z). The matching assumption does not assert X<:O. The judgment Ξ;Γt:D is generated by

x:DΓ
Ξ;Γx:D
T-Match-Var
Ξ;Γ,x:Dt:E
Ξ;Γλx:D.t:DE
T-Match-Abs
Ξ;Γt:DEΞ;Γu:D
Ξ;Γtu:E
T-Match-App
Ξ,X#O;Γt:D
Ξ;ΓΛX#O.t:X#O.D
T-Match-I
Ξ;Γt:X#O.DΞC#O
Ξ;Γt[C]:D[C/X]
T-Match-E
X#μZ.R(Z)Ξx:XΓR(X)()=D
Ξ;Γx.:D
T-Match-Proj

Here R(X)()=D means that the record protocol R(X) contains the field :D. Thus selection is admitted only when the receiver is a match-bound variable and the bound protocol displays the label. The source fragment has no object constructor, so theorem 16.15 covers protocol-polymorphic client code only. The target fold below constructs an example target object; it is not the translation of any source literal.

Definition 16.13 — Higher-order matching

Translate source contexts from left to right. The translation is indexed by that context: beneath X#O, the source variable X denotes μΦX. The translation is defined by the clauses Oper(X)=ΦX,Oper(μZ.R(Z))=λZ:.R(Z),X=μΦX,(μZ.R(Z))=μ(λZ:.R(Z)),(X#O.D)=ΦXOper(O).D,(ΛX#O.t)=ΛΦXOper(O).t,(t[C])=t[Oper(C)],(x.)=(unfoldΦX(x)).. Ground, arrow, variable, abstraction, and application clauses are homomorphic. The context clauses are =,(Ξ,X#O)=Ξ,ΦXOper(O):. Define ΞA#BΞOper(A)Oper(B). This is protocol comparison. In particular, matching and value typing do not combine to give subsumption: a:A,A#B⟹̸a:B.

Proposition 16.14 — Reflexivity and transitivity of matching

For every well-formed source object type A, ΞA#A. If ΞA#B and ΞB#C, then ΞA#C. Both clauses include match-bound variables.

Proof of Proposition 16.14 — Reflexivity and transitivity of matching

Proof. The translated operators have kind . Operator reflexivity gives Oper(A)Oper(A), including the case A=X, where this is ΦXΦX. For transitivity, compose the two translated operator judgments using transitivity in Hμ. No value-typing judgment occurs in either proof. ◻

Consider the source types Max:=μX.{n:Nat,max:XX},MinMax:=μY.{n:Nat,max:YY,min:YY}. Their translated operators are MaxOp:=λX:.{n:Nat,max:XX},MinMaxOp:=λY:.{n:Nat,max:YY,min:YY}. The target fold rule now constructs an actual object. Put rmax={n=0,max=λq:μMaxOp.q}. The annotated lambda has type μMaxOpμMaxOp, hence rmax:MaxOp(μMaxOp),mmax:=foldMaxOp(rmax):μMaxOp. Since rmax is a value, the explicit destructor computes: unfoldMaxOp(mmax)rmax. For arbitrary Z:, record width gives MinMaxOp(Z)<:MaxOp(Z). Thus S-OpPoint derives MinMaxOpMaxOp, and hence MinMax#Max. Recursive subtyping would instead compare XX with YY under only X<:Y, and fail at the contravariant premise.

Now suppose ΦXMaxOp and put X=μΦX, consistently with definition 16.13. The field derivation keeps typing and subtyping as separate judgments: unfoldΦX(x):TUnfoldΦX(X),ΦX(X)<:SOpAppMaxOp(X),unfoldΦX(x):THSub{n:Nat,max:XX},(unfoldΦX(x)).max:THProjXX. Consequently the translated client is preMax:=ΛX#Max.λp:X.λq:X.p.maxq,preMax:=ΛΦXMaxOp.λp:μΦX.λq:μΦX.(unfoldΦX(p)).maxq,preMax:ΦXMaxOp.μΦXμΦXμΦX. It can be instantiated with MaxOp or MinMaxOp. In the second case both arguments and the result have type μMinMaxOp; no value is coerced to μMaxOp. For an end-to-end target calculation, instantiate at MaxOp and use the object constructed above: umax:=preMax[MaxOp]mmaxmmax,(unfoldMaxOpumax).n(unfoldMaxOpmmax).nrmax.n0. Inside the first multistep, the translated client unfolds its first argument, selects max, and applies λq.q to the second argument. Thus the protocol-polymorphic client and the target object compute together, not merely in separate typing derivations.

Theorem 16.15 — Soundness of the operator reading

For the displayed source grammar and only the translation clauses in definition 16.13:

  1. every translated object type and bound is well kinded in Hμ, and every translated recursive operator is contractive;

  2. source match abstraction, match application, and match-variable field selection translate to well-typed target terms.

No operational simulation or full source-language translation is asserted.

Proof of Theorem 16.15 — Soundness of the operator reading

Proof. For item 1, induct on O. A variable uses its translated context declaration. For μZ.R(Z), translate each field type under Z:, apply K-OpAbs, use the source contractiveness condition, and apply K-Mu. A bound extends the context only after the operator on its right has been checked, so bounded operator formation applies from left to right.

For item 2, match abstraction translates to T-AllOp-I. If C#O, the definition of matching gives Oper(C)Oper(O), exactly the second subtyping premise of T-AllOp-E; item 1 proves that both operators have kind and are contractive. Finally, if the source bound exposes a field :L(X), T-Unfold gives the payload type ΦX(μΦX); S-OpApp instantiates the operator bound at μΦX; term subsumption gives the protocol record; projection gives L(μΦX). These are precisely the four judgments displayed above for max. No source subsumption rule has been used. ◻

Exercise 16.7

★★☆ Add min:XX and a Boolean field marked to a third object operator MarkedMinMaxF(X), and define MarkedMinMax=μX.MarkedMinMaxF(X). Prove pointwise that MarkedMinMax#MinMax#Max, and then use transitivity. Instantiate preMax at MarkedMinMax, writing the exact argument and result types.

Exercise 16.8

★★☆ In the deliberately restricted source, the monomorphic expression λp:Max.p.n is not admitted: field selection is available only through a match-bound variable. Define instead g=ΛX#Max.λp:X.p.n. Type g, instantiate it at MinMax, and give the translated derivation: unfold, use subsumption, then project. Finally explain why this construction still provides no coercion from an independently supplied m:MinMax to Max.

The two interpretations do not coincide

In the equi-recursive comparison calculus, the tempting first-order reading is A#BbyA<:Oper(B)(A),X#B.D(X)byX<:Oper(B)(X).D(X). It explains the moveAll and homogeneous preMax examples. It does not translate a cascading bound X#A.Y#X.D, because an ordinary type variable X has no syntactically determined Oper(X). Worse, its induced relation is not transitive in general. For example, with A=μX.{p:XNat,q:Nat},B=μX.{p:XNat},D=μX.{p:BNat}, use the following abbreviations for the one-step body and matching test: FQ(Z)=RQ(Z)when Q=μX.RQ(X),P#eqQP<:FQ(P). This is meta-level substitution inside Feq, not the matching translation Oper() of definition 16.13. The three calculations are A#eqBA<:{p:ANat},B#eqDB<:{p:BNat},A#eqDA<:{p:BNat}. The first holds after unfolding A and dropping q; the second holds after unfolding B. The third would require ANat<:BNat, hence B<:A, but unfolded B lacks q. It therefore fails. More sharply, equi-recursive conversion gives D={p:BNat}=B. Thus #eq is not even well defined on equi-recursive types: replacing B by the definitionally equal D changes the answer. On fixed presentations the preceding calculations also exhibit the attempted transitivity failure. Neither defect is a failure of ordinary subtype transitivity.

The higher-order reading instead translates X#A.D(X)toΦXOper(A).D(μΦX). It retains the protocol as an operator, so a second bound may compare a new operator with ΦX. Reflexivity and transitivity are then inherited from pointwise operator subtyping. The price is explicit higher-kinded abstraction and explicit fold/unfold at terms. The source paper discusses a broader interpretation, but theorem 16.15 proves only the displayed restricted fragment; no end-to-end theorem is imported.

Finer boundary: mutation needs a store theorem

Theorem 16.9, Theorem 16.10 quantify over the heap-free Self+ dynamics, so it has no alias invariant. To state the mutation boundary, extend Self+ with RefA, location values , and terms ref a, !a, and a:=b. Reference types are invariant: the only subtyping between RefA and RefB comes from A=B. Extend the polarity grammar by declaring RefA positive or negative in X only when XFV(A). Correspondingly, extend the value grammar by v::=. The typing rules are

Δ;Σ;Γa:A
Δ;Σ;Γref a:RefA
T-Ref
Δ;Σ;Γa:RefA
Δ;Σ;Γ!a:A
T-Deref
Δ;Σ;Γa:RefAΔ;Σ;Γb:A
Δ;Σ;Γa:=b:Unit
T-Assign
Σ()=A
Δ;Σ;Γ:RefA
T-Loc

The uniform judgment is Δ;Σ;Γa:A: Δ contains type-variable bounds, Σ assigns closed types to locations, and Γ assigns types to term variables. Every functional rule is lifted by carrying the same Σ through its premises.

A store σ maps locations to closed values. Its invariant is Σσdom(Σ)=dom(σ) and ;Σ;σ():Σ() for every . Later the chapter also writes σnW for semantic heap satisfaction and γθ,WnΓ for semantic environment satisfaction. The three source macros , , and deliberately print the same relation glyph, but their arguments identify the judgment: store typing has Σ on the left and σ on the right; heap satisfaction has σ on the left and carries an index; environment satisfaction has γ on the left and carries both closing data and an index. Configuration reduction adds the roots σ,ref vallocationσ[v],(dom(σ)),σ,!σ,σ(),σ,:=vσ[v],unit, Besides the contexts of definition 16.5, configuration reduction uses E::=ref E!EE:=a:=E. Thus the location and then the assigned value are evaluated before assignment contracts.

Lemma 27.19 — Store-typing weakening

If ΣΣ preserves every old location type and Δ;Σ;Γa:A, then Δ;Σ;Γa:A. Moreover, if Σσ, is fresh, Σ=Σ,:A, and ;Σ;v:A, then Σσ[v].

Proof of Lemma 27.19 — Store-typing weakening

Proof. The typing statement is induction on the derivation. Rule T-Loc uses preservation of old bindings; every other rule restores its conclusion after applying the induction hypotheses. For the store statement, the new domains agree, the fresh cell has the assumed type, and the old cells retain their types by the typing statement. ◻

Theorem 21.17 — Configuration preservation

If Σσ, ;Σ;a:A, and σ,aσ,a, then there is a store typing ΣΣ such that Σσ and ;Σ;a:A.

Proof of Theorem 21.17 — Configuration preservation

Proof. Induct on the configuration reduction. For allocation, inversion gives v:A0. Choose fresh , set Σ=Σ,:A0, and use T-Loc for the result. The new store clause and all old clauses follow from lemma 27.19.

For dereference, typing and reference canonical forms give Σ()=A0. The store invariant gives ;Σ;σ():A0, which is the type of the reduct. For assignment, inversion gives v:A0 at the same invariant cell type Σ()=A0. Replacing only σ() therefore preserves every clause of equation 16.3; the result is unit.

In a context step, the induction hypothesis gives ΣΣ. Lemma 27.19 retypes the inactive subterms and the context’s typing rule is rebuilt. This covers ref E, !E, E:=a, :=E, and every functional context in definition 16.5.

The functional roots leave the store fixed. Beta and recursion use term substitution. Projection and ground computation use typing inversion. For a generalized Self-open root, write the runtime annotation as S0=SelfX.R0(X) and the expected annotation as S=SelfX.R(X). Inversion gives C<:S0<:S,v:R0(C),X<:S,x:R(X)b:D. Self-subtyping inversion and type substitution give R0(C)<:R(C); subsumption gives v:R(C). Substituting C for the escaping-free X, then v for x, types b[C/X][v/x]:D. Thus the store theorem contains its own Self-open case rather than importing the functional conclusion. ◻

Theorem 21.18 — Configuration progress

If Σσ and ;Σ;a:A, then a is a value or some configuration σ,a satisfies σ,aσ,a.

Proof of Theorem 21.18 — Configuration progress

Proof. Induct on typing after stripping final subsumption. The functional cases follow the same canonical-form analysis as theorem 16.10: a function value is a lambda, a record value has the projected field, a Self value is a package, and a fixpoint always unfolds. These facts are reproved under Σ by induction on value typing; locations add no inhabitant of an arrow, record, or Self type.

For ref a, either a steps in the allocation context or it is a value and the fresh-location root applies. For !a, either a steps or reference canonical forms give a location . Its typing says dom(Σ), and the store invariant equates the domains, so the dereference root applies. Assignment first steps its left term, then its right term; when both are values, the left is a location in the store and the assignment root applies. These cases exhaust the extended grammar. ◻

Corollary 21.19 — Syntactic state safety

If Σσ and ;Σ;a:A, every finite reduction from σ,a preserves type A, satisfies an extending store typing, and cannot end in a stuck configuration.

Proof of Corollary 21.19 — Syntactic state safety

Proof. Iterate theorem 21.17; apply theorem 21.18 to the last configuration. ◻

For a small aliasing calculation, put K=SelfX.{contents:RefNat,set:NatX}. After allocating r=ref 0, define kr=fix k:K.packSelf K with{contents=r,set=λn:Nat.(λu:Unit.k)(r:=n)} as K. The family is positive in X, and RefNat is independent of X. The following trace is a concrete instance of the preservation and progress theorems. Allocation gives ,ref 0{0},,Σ1={:Nat},Σ1{0}. Write k for the displayed package with r=. We write that same package twice, a1:=k and a2:=k, only to mark its two use sites; both aliases contain the same location . The term B2:=λu:K.!(a2contents):KNat,e12:=B2((a1set)7):Nat. has the configuration trace {0},e12openSelfandprojectset{0},B2((λu:Unit.k)(:=7)){7},B2((λu:Unit.k)unit)βandUseSelf{7},!dereference{7},7. The first multi-step opens a1 and selects set. The first displayed root is assignment; then beta returns the exact receiver. The outer beta deliberately discards its parameter u; opening a2 selects the same location, and dereference reads 7. Both stores satisfy Σ1 because their sole contents have type Nat. An attempted update :=true is rejected by T-Assign before reduction.

A field RefX would be invariant in X, not positive, and is therefore outside S-Self. The fragment therefore models objects that share cells of receiver-independent data, such as counters, flags, and handles. It cannot directly model a mutable linked structure whose cell stores another value of the exact receiver type; that requires a richer invariant or guarded treatment of RefX.

A step-indexed model for Self with references

The unindexed semantic-world attempt is circular. If a world stores the semantic promise at each location, W()=V[[A]]θ(W), then defining the value relation already quantifies over worlds whose entries are instances of that same, not-yet-defined relation. There is no smaller type or world on the recursive call. Two repairs break the cycle: worlds store closed syntactic types rather than predicates, and interpreting a stored type may appeal only at a strictly smaller natural-number index.

The syntactic theorem tracks a type at each location. The semantic model tracks the behavior promised by that type at every smaller number of future steps. This second proof is independent of the functional proof and of the equi-recursive comparison calculus.

A step index n is a natural-number budget: a claim at n may appeal recursively only to claims at smaller budgets. A world records the closed type promised by every allocated location, and a later world may add locations without changing old promises. A logical relation interprets each type as the values and expressions that honor those promises; heap satisfaction says that the current store realizes its world’s promises. The strict decrease in n breaks the circularity among recursive Self values, references, and heaps. The earlier syntactic theorem already proves no-stuck safety for closed programs. This second theorem is not needed to repeat that conclusion; it additionally validates the open-term substitution principle and the recursive semantic interpretation used for aliasing.

Definition 21.20 — Syntactic worlds and closing substitutions

A closing type substitution θ respects a context Δ when it is built from left to right and, for each declaration X<:A, chooses a closed type C=θ(X) satisfying C<:A[θ]. A world W is a finite map from locations to closed, well-formed syntactic types. Write WW when dom(W)dom(W) and W()=W() at every old location. Thus worlds contain no semantic types and their construction is noncircular. The printed order marks for and the earlier operator bound are not converses: world extension compares finite maps, whereas operator bounding compares type operators. Their source macros and operand sorts remain distinct.

Definition 21.21 — Step-indexed interpretation

For a type A whose variables are closed by θ, let V[[A]]θ(W,n) be its set of related closed values. Equivalently, interpret the closed syntactic type A[θ]. The ground and top clauses are V[[Nat]]θ(W,n)={0,1,2,},V[[Bool]]θ(W,n)={true,false},V[[Unit]]θ(W,n)={unit},V[[Top]]θ(W,n)={vv is a closed value}. Records contain exactly the values with every required field related at index n: V[[{i:Ai}iI]]θ(W,n)={{j=vj}jJIJ,viV[[Ai]]θ(W,n) for iI}. The arrow and reference clauses are V[[AB]]θ(W,n)={λx:C.afor all j<n,WW,vV[[A]]θ(W,j)a[v/x]E[[B]]θ(W,j)}, V[[RefA]]θ(W,n)={dom(W), W()=Aθ}. The equality in equation 21.5 is equality of closed syntactic types, not equality of semantic predicates.

For S=SelfX.R(X), define V[[S]]θ(W,n)={packSelf C with v as S0S0<:Sθ, C<:S0,vV[[R0(C)]](W,j) for every j<n}. Here C,S0, and the family R0 displayed by S0 are closed. The value and expression interpretations are defined simultaneously by the lexicographic measure (n,|A|,ρ),V<E, where ρ records the value or expression phase. A record component decreases |A| at the same index. Arrow bodies and Self payloads decrease the index. The expression clause below may call the value clause at the same index when k=0, but then it decreases the phase from E to V. Reference and ground clauses do not recurse. Thus every semantic call decreases the displayed measure.

Heap satisfaction now reads the closed type stored in the world: σnW{dom(σ)=dom(W),σ()V[[W()]](W,j)(dom(W), j<n). The strict index again guards a cell whose closed syntactic type may contain Self or references.

A closed expression belongs to E[[A]]θ(W,n) when, for every WW, jn, k<j, and σjW, σ,akσ,awith a irreducible implies that some WW satisfies (Term)σjkW,aV[[A]]θ(W,jk). In particular, a related irreducible expression is a value.

Write WΣθ when dom(Σ)dom(W),W()=Σ()θ(dom(Σ)). For a closed store typing, WΣ abbreviates the empty closing substitution. Thus WW and WΣθ imply WΣθ. This realization judgment permits extra allocated locations; heap satisfaction in equation 21.7 still requires exact agreement among the domains of the heap and its world. An expression substitution γ satisfies Γ at (θ,W,n), written γθ,WnΓ, when it maps each x:A to a closed expression γ(x)E[[A]]θ(W,n). The images need not already be values. This choice is essential for the recursive term case below: extending the environment for a fixpoint maps its variable to the reducible fix term itself, whose first unfolding is consumed at a smaller index.

Lemma 21.22 — Closing substitution and semantic subtyping

The value and expression relations are downward closed in the index and monotone under syntactic-world extension. If ΔA<:B and θ respects Δ, then Aθ<:Bθ,V[[A]]θ(W,n)V[[B]]θ(W,n). The same inclusion holds for E.

Proof of Lemma 21.22 — Closing substitution and semantic subtyping

Proof. The first judgment is simultaneous closing substitution on formation and subtyping. In the distinguished bound case, it is exactly the premise θ(X)<:Aθ in the definition of a respecting substitution. Arrow, record, and Self rules apply the induction hypotheses; binders are alpha-renamed before substitution.

For the semantic inclusion, induct on subtyping. The ground cases are reflexive and Top contains every closed value. Records discard fields. Arrows reverse the domain inclusion and preserve the codomain inclusion. References have no nonreflexive structural subtyping. In the Self case, a package admitted on the left has a closed annotation S0<:S1θ; closing the rule premise gives S1θ<:S2θ. Transitivity gives S0<:S2θ, so the same witness and smaller-index payload satisfy equation 21.6 on the right. The expression inclusion replaces the last value membership in equation Term. Downward closure is induction in the lexicographic measure (n,|A|,ρ) of definition 21.21; record clauses use the smaller type, arrow and Self clauses use the smaller index, and the expression clause uses the smaller phase when its residual index is unchanged. World monotonicity is proved in the same induction and uses exact agreement on old syntactic bindings, especially in equation 21.5. ◻

Lemma 27.26 — Expression anti-reduction

Suppose a closed expression a has a store-independent root σ,aσ,a for every store σ. If aE[[A]]θ(W,n), then aE[[A]]θ(W,n+1).

Proof of Lemma 27.26 — Expression anti-reduction

Proof. Take a test at jn+1. The reducible term a cannot satisfy the irreducibility premise in zero steps. Every terminating test therefore first takes the displayed root and has at most j1n steps left. Downward closure places a at index j1; applying its expression clause to the residual trace yields heap satisfaction and value membership at exactly the required residual index. ◻

Lemma 27.27 — Compatibility of the reference forms

At every (θ,W,n):

  1. if aE[[A]]θ(W,n), then ref aE[[RefA]]θ(W,n);

  2. if aE[[RefA]]θ(W,n), then !aE[[A]]θ(W,n);

  3. if aE[[RefA]]θ(W,n) and bE[[A]]θ(W,n), then a:=bE[[Unit]]θ(W,n).

Proof of Lemma 27.27 — Compatibility of the reference forms

Proof. Fix a future-world and heap test. For allocation, the premise runs the initializer to a related value v. Extend the world and heap by a fresh Aθ and v. Downward closure supplies the cell invariant at every smaller residual index, and equation 21.5 relates the returned location.

For dereference, the premise runs to a location . Its reference clause gives W()=Aθ, and heap satisfaction supplies the stored value at every smaller index consumed by the dereference root. For assignment, run the left expression to such an , then the right expression to a related value. Replacing the cell preserves every other heap clause and the exact type at ; the root returns unit. In each case the sum of the premise steps and the one root step is the tested budget, so the residual index in equation Term is unchanged. ◻

Theorem 21.23 — Fundamental theorem for the imperative Self fragment

Suppose Δ;Σ;Γa:A is derivable using the Self+, reference, and recursion rules of this section. For every index n, syntactic world W, closing substitution θ, and expression substitution γ, if θ respects Δ,WΣθ,γθ,WnΓ, then aθγE[[A]]θ(W,n).

Proof of Theorem 21.23 — Fundamental theorem for the imperative Self fragment

Proof. Base forms. Use outer induction on n, and inside it induction on the typing derivation. At index zero the expression relation has no k<j0 test. Variables use the expression-related γ. Records, projection, conditionals, and ground operations use their displayed value clauses. Subsumption uses lemma 21.22.

Functions. For abstraction, take j<n, a future world WW, and an argument vV[[A]]θ(W,j). Downward closure and world monotonicity give γθ,WjΓ; a related value is also a related expression, so extending by xv satisfies Γ,x:A there. Moreover, future-world stability gives WΣθ. Apply the outer index hypothesis at j to the body derivation. This is not the inner derivation hypothesis at index n. Its conclusion is the arrow-clause obligation. Application composes the arrow and expression clauses.

Recursion. The recursion case is the reason for the order of induction. Let f=fix x:Aθ.aθγ. To prove fE[[A]]θ(W,n), consider a test at jn. The term f is reducible, and its first step is f(aθγ)[f/x]. At the smaller index j1<n, the outer induction hypothesis applied to the T-Fix derivation gives fE[[A]]θ(W,j1). Hence γ[xf] satisfies the body context at that index. Apply the outer hypothesis again, now to the body derivation Γ,x:Aa:A. Its conclusion relates the displayed reduct, and lemma 27.26 absorbs the initial step. This proves fix compatibility without assuming the result at the same index.

Self packages. For T-PackSelf, closing substitution first gives Cθ<:Sθ. The inner induction hypothesis places the payload expression in E[[R(C)]]θ(W,n), where R(C)θ=Rθ(Cθ). Consider any irreducible trace of the whole package. The evaluation context packSelf Cθ with E as Sθ runs the payload to a value v; a package with a reducible payload is not irreducible. The payload expression clause supplies vV[[Rθ(Cθ)]](W,q) at the residual index q. Downward closure supplies the same membership at every i<q. Hence the resulting package value, annotated by the closed type Sθ, satisfies equation 21.6 by reflexivity and the closed witness premise. This proves expression membership for the package; it does not treat a reducible payload as a value-related one.

Self opening. For T-UseSelf, fix a test at index j and run the related scrutinee for r steps until it produces packSelf C with v as S0. The Self value clause gives C<:S0<:Sθ and vV[[R0(C)]] at every remaining smaller index. Self-subtyping inversion, with binders alpha-renamed to agree, gives X<:TopR0(X)<:Rθ(X). Closing this judgment by [C/X] gives R0(C)<:Rθ(C), so semantic subtyping relates the actual payload at the family expected by the body. Extend the closing substitution by XC; it respects X<:S because C<:S0<:Sθ. After the Self-open root the body has budget q=jr1. If its residual trace has s steps, the original test has k=r+1+s<j,s<q,qs=jk. The Self value clause supplies the payload at q<jr. Extend γ by xv, apply the outer index hypothesis at q to the body, and use XFV(D) to remove the extension from the result type. This is the closing type substitution performed by the operational Self-open root.

References and worlds. The allocation, dereference, and assignment cases apply the three clauses of lemma 27.27 to their induction hypotheses. The construction in that lemma extends W only for allocation, so it also preserves WΣθ.

Representative compatible contexts. For application, split a terminating trace into r1 steps evaluating the function to a related lambda, r2 steps evaluating the argument to a related value, one beta step, and r3 body steps. If the test starts at j, put q=jr1 and s=qr2. From r1+r2+1+r3<j the displayed inequality and the definitions of q,s give s=jr1r2>1+r3. The arrow value clause may therefore be instantiated at s1 in the current future world; its expression conclusion handles the r3-step body trace and returns index j(r1+r2+1+r3).

For projection, split r receiver steps from the one projection root. The record value clause relates the selected field at index jr; downward closure places it at jr1, the index left by the root. For record construction, induct on the ordered field list. If the active field consumes ri steps, subtract ri from the current budget, use its expression hypothesis at that residual index, and use downward closure for every already evaluated field; the list induction preserves the invariant that the sum of the consumed field budgets plus the remaining budget is j. For a conditional, split r guard steps from its one branch-selection root. Canonical forms makes the guard either true or false; choose exactly the corresponding branch hypothesis at index jr1, leaving the unchosen branch unused. Self-package and Self-open contexts use the explicit arguments above. The reference contexts are precisely lemma 27.27. These are all evaluation contexts in the grammar, so the future-world quantifier composes the induction hypotheses without an unexamined context case. ◻

Corollary 21.24 — Semantic state safety

Suppose ;Σ;a:A, WΣ, and σk+1W. If σ,akσ,a and a is irreducible, then a is a value. Hence no such trace ends in a stuck configuration.

Proof of Corollary 21.24 — Semantic state safety

Proof. Apply theorem 21.23 with empty substitutions at index k+1, and use equation Term with the given heap. It produces an extending world W for which aV[[A]](W,1). Every defining clause of V[[A]] contains only syntactic values: ground constants, lambdas, record values, locations, or Self packages with value payloads. Hence a is a value. ◻

Exercise 16.9

★★☆ Start from the empty store, allocate the cell used by kr, and reduce (λu:K.!(a2contents))((krset)7), through every compatible-context and root step. Display the store after allocation and assignment, and check equation 16.3 at both points. Explain why assigning a Boolean is rejected before reduction.

The boundaries between the three self mechanisms

Convention 16.17 — Terminology firewall

OO Self in this chapter is the hidden representation binder of equation 16.1. It is not the term receiver passed to a method, the recursive variable of an ordinary μ-type, or a match-bound protocol variable. Likewise, A#B says that A’s protocol extends B’s; it is not a subtyping judgment. In particular, no typing rule derives Γa:B from Γa:A and A#B. The primitive Self core uses pack/open iso-recursion; Feq uses equi-recursive type conversion; and Hμ uses explicit term fold/unfold. An equation or reduction from one of these three calculi is never silently transferred to another.

Sources.

The expansion SelfX.B=μY.X<:Y.B, its pack/use operations, the covariance derivation, the covariant-method-family condition, and the moving point and binary-method boundary are in Abadi and Cardelli, A Theory of Primitive Objects: Second-Order Systems, Sections 3.4 and 4.1–4.2, author-preprint PDF pp. 9–16, especially the rules on printed pp. 10–14. Its semantics is functional; the recoup invariant and the limit on overriding Self-returning methods are on printed pp. 17–19 [AC95]. The Point/ColorPoint argument-specialization failure and the distinction between binary-method typing and privileged representation access are developed in Bruce et al., On Binary Methods, file-PDF pp. 4–7. Its matching treatment is at file-PDF pp. 10–13; the archive cover precedes the article [BCC^+95].

The definition of matching, the statement that it grants no subsumption, and the Max/MinMax derivation occur in Abadi and Cardelli, On Subtyping and Matching, printed pp. 6–10. F-bounded quantification and its post-fixpoint interpretation are on printed pp. 12–13. The F-bounded failures of cascading bounds, reflexivity, and transitivity are on printed pp. 16–18. The higher-order translation, its reflexivity and transitivity, and its fold/unfold field derivation are on printed pp. 18–21. That source promises a rigorous full translation but gives no end-to-end proof [AC96a]; theorem 16.15 is restricted to the displayed clauses. Definition 21.20, Definition 21.21 instead give a local step-indexed model and safety proof for this reference-and-Self fragment.

Suggested first pass.

Begin with exercise 16.10, exercise 16.11, exercise 16.12; then use the remaining problems to reconstruct the two safety boundaries.

Exercise 16.10

★☆☆ For each occurrence of X in {clone:X,map:(NatBool)X,equal:XBool,choose:(XBool)X}, compute its polarity from the outside inward. Determine the largest Self+ subrecord and give the failed monotonicity premise for each removed field.

Exercise 16.11

★★☆ Define a positive Self type with natural and Boolean fields and toggle:UnitX. Type a closed recursive package and one toggle reduction; then omit the natural field by width and repeat the generalized (UseSelf) preservation calculation.

Exercise 16.12

★★★ Let Ordered(X)={le:XBool},NamedOrdered(X)={name:Nat,le:XBool}. Prove that the recursive types match. Give the explicit higher-order fold/unfold typing of a match-polymorphic homogeneous comparison and its F-bound analogue. Then state the subsumption judgment that would be required to use a named ordered object where an ordered object is expected, and show by arrow-domain and record-width inversion which premise prevents its derivation.

Exercise 16.13

★★☆ Reconstruct the single generalized (UseSelf) preservation case when the runtime package annotation is a subtype of the expected public Self type. State exactly where Self-subtyping inversion, hidden-witness type substitution, payload subsumption, and payload substitution are used. Then give the corresponding progress argument when the scrutinee is already a closed package value.

Exercise 16.14

★★★ Extend the cell with bump:UnitX, which increments the shared reference and returns the exact receiver. Type its package, including the annotated lambda and succ(!r). With two aliases, fully type the trace bump unit through the first, read through the second, recording the store typing before and after allocation, assignment, and dereference. Replace the field by RefX; identify the failed positivity and subtyping rules.

Exercise 21.15

★★★ Practical project.matching-package-checker Implement four finite checks for the types in this chapter: positivity, protocol matching, package introduction, and package opening. Preserve two invariants: references containing Self are invariant rather than positive, and (UseSelf) substitutes the same hidden witness into both payload and result. The seven cases must include NamedOrdered matching Ordered, rejection of RefX, and the absence of matching-based subsumption. The audit is empty, and the run ends with All 7 self/matching corpus cases passed. Construct three deliberately incorrect checkers by reversing arrow polarity, treating references as covariant, and substituting a different witness in the result. Each variant must remain executable and fail at least one named case. These are finite boundary checks, not a proof of the step-indexed fundamental theorem.

Search the book

Type to search the local edition.