Lectures onType Theory
Chapter 25
Chapter 25Core route

Object Calculi and Recursive Object Types

A record can contain functions, but selecting a function does not pass it the record that was selected. Passing the record when the function is built is also insufficient. After one method has been replaced, every other method must invoke the replacement through the new receiver. A receiver binder is the variable that invocation replaces by the current object, rather than an ordinary stored argument. Equivalently, the object is a finite system of mutually recursive equations xi=bi(x¯). Replacing one equation should make every other equation refer to the replacement when the system is solved again; an ordinary record of already-closed functions solves the system once and freezes those references. The receiver binder is the syntax that keeps the equations open until invocation.

A receiver that survives replacement

Ground terms are variables, natural numerals, unit, and succ(a), with n:Nat,unit:Unit,a:Natsucc(a):Nat. Numerals and unit are values, succ(n)n+1, and succ(E) is an evaluation context. These forms let the running methods return observable numerals.

The word object here means a runtime value: one receiver together with its methods. There are no class declarations, constructors, or nominal class types in this calculus. A class, if a source language had one, would instead be a separate device for producing such objects.

Definition 15.1 — The functional object calculus Ob_1

Methods return ground or object types and take no ordinary parameters. The source grammar has neither arrow nor polymorphic method types. The new types and terms are A,B::=NatUnit[i:Bi]iI,a,b::=[i=ς(si:A)bi]iIa.a.ς(s:A)b. The labels in an object or object type are distinct, their order is irrelevant, and I may be empty. In ς(s:A)b, the variable s is bound in b. Terms are identified up to alpha-renaming, and substitution is capture avoiding.

Each method has its own self binder si; equal spellings in two fields do not make those binders the same variable. A field is therefore a method taking no argument but the receiver, whose body is bi; it is not a stored record component. Override is the single syntactic form displayed above: the receiver and replacement method are both part of one term. Its infix is that term former, not the checking judgment of chapter 10.

The binder ς(s:A)b plays the semantic role of a method’s this: invocation substitutes the receiver object for s in b. At the metalanguage level, the family Ppt:=[x:Nat,get:Nat],Point(n):=[x=ς(s:Ppt)n, get=ς(s:Ppt)s.x]. is a small object-producing schema: each numeral n determines an object term Point(n), whereas Point denotes one recursive type. Reduction of Override constructs a new object literal with the selected method body replaced and leaves the receiver object unchanged. The term grammar contains no store update, prototype relation, or nominal class name.

The object rules are

A=[i:Bi]iIΓ,si:Abi:Bi(iI)
Γ[i=ς(si:A)bi]iI:A
T-Object
Γa:[i:Bi]iIjI
Γa.j:Bj
T-Invoke
Γa:AA=[i:Bi]iIΓ,s:Ab:BjjI
Γa.jς(s:A)b:A
T-Override

An override is defined only for an existing label and preserves its result type. The object annotation is part of the term. It shows the type that every occurrence of self is expected to have.

An eliminator whose receiver is a variable is neutral rather than a redex: s:Ps.x:Nat,s.x. Only substitution of an object value for s can expose an invocation root.

For the remainder of this section, abbreviate the running point interface by P:=[x:Nat,get:Nat] and define p0:=[x=ς(s:P)0,get=ς(s:P)s.x]. The two premises of T-Object are s:P0:Nat,s:Ps:Ps:Ps.x:NatTInvoke. Thus p0:P. Notice that the body of get does not contain p0; it invokes x through the receiver supplied when get is called.

Definition 15.2 — Weak object reduction

Weak object reduction is call-by-value reduction that never enters a ς-body. Object literals and ground constants are values. Besides the ground contexts, evaluation contexts contain E::=E.E.ς(s:A)b. One-step reduction is compatible closure under these contexts of (Invoke)[i=ς(si:A)bi]iI.jbj[[i=ς(si:A)bi]iI/sj](Override)[i=ς(si:A)bi]iI.jς(s:A)c[j=ς(s:A)c,i=ς(si:A)bi]iI{j} Both contractions require jI. The right side of (Override) is a new value. The original object is unchanged.

Put p1:=p0.xς(s:P)1. The override body has type Nat under s:P, so p1:P. Reduction exposes late self invocation: p1.getoverrideexposure[x=ς(s:P)1,get=ς(s:P)s.x].getinvokeget[x=ς(s:P)1,get=ς(s:P)s.x].xinvokex1. The second line is decisive. The old get body receives the new object and therefore finds the replacement for x.

Exercise 15.1

★☆☆ Let p2:=(p0.xς(s:P)2).xς(s:P)3. Derive p2:P, reduce p2.get, and also reduce p0.get. Identify the calculation that shows that override is functional rather than imperative.

Safety before subtyping

Two facts do all the work. Substitution reconnects an invoked body with its receiver, while canonical forms guarantee that a closed receiver has the method promised by its type.

Lemma 15.3 — Structural properties of Ob_1

The following hold.

  1. If Γa:A and ΓΓ, with every declaration preserved, then Γa:A.

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

  3. If Γ0,x:A,y:B,Γ1a:C, where xy, then Γ0,y:B,x:A,Γ1a:C.

Proof of Lemma 15.3 — Structural properties of Ob_1

Proof. Weakening is induction on the typing derivation. In the object case, first alpha-rename every si away from the added declarations. The induction hypothesis changes Γ,si:Cbi:Bi into Γ,si:Cbi:Bi, and T-Object restores the conclusion. Invocation and override restore their last rules; the latter treats its receiver premise and its self-body premise separately.

For substitution, induct on the derivation of Γ,x:Ab:B. At the variable x, use the assumed derivation Γa:A; every other variable retains its declaration. For an object, alpha-rename each si away from x and from the free variables of a. The induction hypothesis gives Γ,si:Cbi[a/x]:Bi for every method, so T-Object applies. Invocation follows by applying the induction hypothesis to its receiver. In an override, apply it once to the receiver and once under the fresh declaration s:C to the new body, then restore T-Override. The ground constructors have no new binder and follow directly.

For exchange, induct on the typing derivation. A variable lookup is preserved by the adjacent permutation because source types contain no term variables. Every nonbinding rule applies the induction hypotheses to its premises. For object formation and override, alpha-rename the self binder away from x and y, exchange the two outer declarations in each body premise, and restore the rule. Thus the permutation cannot capture a free occurrence. ◻

Lemma 15.4 — Object canonical forms

If v:[i:Bi]iI and v is a value, then v=[i=ς(si:A)bi]iIwithA=[i:Bi]iI. In particular, every label in the type occurs in the value.

Proof of Lemma 15.4 — Object canonical forms

Proof. Numerals and unit have ground types. The only remaining value form is an object literal. Inverting T-Object equates its annotation with [i:Bi]iI, its label set with I, and each component result type with the corresponding Bi. There is no subsumption rule that could hide methods or change the outer type. ◻

Theorem 15.5 — Safety of the functional object calculus

For closed terms of Ob1:

  1. if a:A and aa, then a:A;

  2. if a:A, then a is a value or there is an a with aa.

Consequently a closed well-typed term never reaches a nonvalue with no next step.

Proof of Theorem 15.5 — Safety of the functional object calculus

Proof. For preservation, induct on the reduction derivation. Compatible steps use the induction hypothesis and restore the typing rule for the surrounding context. There are two new root cases.

For (Invoke), inversion of the receiver’s T-Object derivation gives sj:Abj:Bj,[i=ς(si:A)bi]iI:A. Substitution therefore types the reduct bj[[i=ς(si:A)bi]iI/sj] at Bj, the type assigned by T-Invoke.

For (Override), inversion gives s:Ac:Bj and, for the retained methods, si:Abi:Bi. These are exactly the premises of T-Object for the replacement literal, so the reduct has type A. Ground contraction uses its ground typing premise.

For progress, induct on typing. Object literals are values. In an invocation, the receiver either steps, and the invocation context lifts that step, or is a value. In the latter case it is a literal containing the selected label by lemma 15.4, so (Invoke) applies. Override is identical up to its root rule. Ground terms use their usual progress cases. The final safety claim follows by applying preservation after every step and progress at the last term of any finite reduction. ◻

Remark 25.6 — Safety is not normalization

The safety theorem permits infinite reduction. For D=[loop:Nat],d=[loop=ς(s:D)s.loop], the closed well-typed term d.loop reduces to itself. Self invocation therefore defines a general-recursive loop even though the calculus has no store.

Exercise 15.2

★★☆ Write the complete preservation derivation for p0.xς(s:P)s.get[x=ς(s:P)s.get,get=ς(s:P)s.x]. Your derivation must include both invocations used to type the method bodies. Also observe that the resulting object’s x invocation alternates forever with its get invocation; this divergence is not a typing error.

Width, invariance, and minimum types

An object with extra methods can answer every invocation understood by a smaller interface. This justifies width subtyping. It does not yet justify changing the result types of shared methods: every method body was checked under assumptions about every other method of self.

Definition 15.6 — Width-invariant object subtyping

Add Top to the types and subsumption to typing. Width-invariant object subtyping is the least preorder that forgets methods while requiring equal result types for retained methods; it satisfies

A<:Top
S-Top
JIBj=Cj (jJ)
[i:Bi]iI<:[j:Cj]jJ
S-Object

Ground types have only reflexive subtyping. The typing rule is

Γa:AA<:B
Γa:B
T-Sub

Shared object components are invariant: they must be the same type, not merely related by subtyping. The resulting calculus is Ob1<:. The type Top has no eliminator: a term can be forgotten to Top, but no operation can recover its hidden object interface.

Lemma 25.8 — Top is maximal in object subtyping

If Top<:A, then A=Top.

Proof of Lemma 25.8 — Top is maximal in object subtyping

Proof. Induct on the derivation in the least preorder. Reflexivity and S-Top have the stated target. Neither ground reflexivity nor S-Object can have source Top. In a transitive derivation Top<:B<:A, the induction hypothesis for the first part makes B=Top, and the hypothesis for the second then makes A=Top. ◻

Lemma 25.9 — Inversion of width-invariant object subtyping

If A<:[j:Cj]jJ, then A=[i:Bi]iI for some IJ, and Bj=Cj for every jJ.

Proof of Lemma 25.9 — Inversion of width-invariant object subtyping

Proof. Use rule induction for the least preorder, together with the shape claim that a type below an object type is itself an object type. Reflexivity is immediate, and S-Object gives both the label inclusion and the component equalities. Neither S-Top nor a ground-type reflexivity judgment can end at an object type.

For transitivity, write A<:M<:[j:Cj]jJ. Inverting the second derivation gives M=[k:Dk]kK, with JK and Dj=Cj on J. Inverting the first gives A=[i:Bi]iI, with KI and Bk=Dk on K. Composing the inclusions and equalities proves the conclusion. This argument also proves the shape claim. An intermediate Top is excluded by lemma 25.8. ◻

For example, [x:Nat,get:Nat]<:[get:Nat], so p0 may be passed to code that invokes only get. But [m:S]≮:[m:T] when S<:T and ST.

Subsumption destroys unique typing: p0 has both displayed object types. The annotations in object formation and override nevertheless determine a minimum type for every typable term.

Definition 15.7 — Syntax-directed minimum typing

Write Γmina:A. Remove T-Sub, retain the variable, ground, and invocation rules with min, and replace object formation and override by

A=[i:Bi]iIΓ,si:Aminbi:BiBi<:Bi(iI)
Γmin[i=ς(si:A)bi]iI:A
M-Object
A=[i:Bi]iIΓmina:AA<:AΓ,s:Aminb:BB<:BjjI
Γmina.jς(s:A)b:A
M-Override

Invocation is the explicit rule

Γmina:[i:Bi]iIjI
Γmina.j:Bj
M-Invoke

Thus, if the minimum receiver type is [i:Bi]iI, the result is the component Bj, and every conclusion is determined by the term, the context, and the conclusions of its immediate premises.

If an object with private methods is first viewed at a shorter interface A, an override annotated by A has minimum type A, not the hidden longer type. The operation still creates a runtime object containing the private methods; the type checker does not recover an interface that the program chose to hide.

Lemma 15.8 — Narrowing the receiver

If Γ,s:Ab:B in Ob1<: and A<:A, then Γ,s:Ab:B.

Proof of Lemma 15.8 — Narrowing the receiver

Proof. Induct on the typing derivation. In the distinguished variable case, s:A follows by the variable rule and then T-Sub; other variables are unchanged. For object formation, alpha-rename each inner self binder away from s, apply the induction hypothesis to every body, and restore T-Object. Invocation and override apply the induction hypothesis to the receiver and to every premise in which the outer s remains free; their own self binders are first made fresh. A final T-Sub is restored unchanged. Ground terms have no binding case. ◻

Lemma 15.9 — Source substitution with subtyping

If Γ,x:Ab:B and Γa:A in Ob1<:, then Γb[a/x]:B.

Proof of Lemma 15.9 — Source substitution with subtyping

Proof. Induct on the derivation of Γ,x:Ab:B. At x, use the assumed derivation Γa:A; every other variable retains its declaration. For T-Object, alpha-rename each self binder away from x and FV(a), apply the induction hypothesis to every body, and restore the same annotated object type. Invocation applies the induction hypothesis to its receiver. Override applies it once to the receiver and once to the replacement body under a freshly renamed self binder, then restores T-Override. Numerals and unit contain no variables, and a successor uses the induction hypothesis on its argument. If the last rule is T-Sub, apply the induction hypothesis to its term premise and restore the unchanged subtype judgment. Thus subsumption introduces no new substitution case. ◻

Theorem 15.10 — Minimum typing

For every context Γ and term a:

  1. a judgment Γmina:A, if derivable, determines A uniquely;

  2. if Γmina:A, then Γa:A;

  3. if Γa:B, there is a unique A such that Γmina:A, and A<:B.

Hence every typable term has a minimum declarative type.

Proof of Theorem 15.10 — Minimum typing

Proof. For item 1, induct on the term. A variable’s type is fixed by Γ, and the ground forms have fixed types. An object or override carries its result annotation A in its syntax. For invocation, the induction hypothesis fixes the minimum receiver type; distinctness of labels fixes its component type. Thus no term form admits two different conclusions.

For items 2 and 3, use simultaneous induction on the minimum and declarative derivations. In M-Object, each minimum body type Bi can be raised to Bi by T-Sub; T-Object then gives the annotated type. In M-Override, raise the receiver from A to A and the new body from B to Bj, then use T-Override. Invocation and ground cases are unchanged. This proves item 2.

Conversely, consider the last rule of a declarative derivation. If it is T-Sub, apply the induction hypothesis to its premise. If that premise has minimum type A, then A<:B for the premise type B, and transitivity with B<:B gives the required comparison.

Suppose the last rule is T-Object. The induction hypothesis gives a minimum type Bi<:Bi for each body under the annotated self context; M-Object gives the same annotated object type. Suppose it is T-Invoke. Its receiver has a minimum type A below the displayed object type A. Lemma 25.9 says that A contains every label of A with exactly the same component type. The minimum invocation rule therefore returns the displayed Bj.

Finally suppose the last rule is T-Override, whose annotation is A=[i:Bi]iI. The receiver’s minimum type A satisfies A<:A, and the body’s minimum type B satisfies B<:Bj. Rule M-Override therefore gives minimum type A. These cases exhaust the syntax. By item 1, any two types constructed this way are equal, so the minimum type is independent of the declarative derivation. ◻

Exercise 15.3

★★☆ Let Q=[x:Nat,y:Nat],q=[x=ς(s:Q)0,y=ς(s:Q)1]. Find the minimum type of q, of q.x, and of q.xς(s:[x:Nat])2. For the last term, show both the minimum derivation and a declarative typing that first subsumes q.

Subtyping changes the override contraction in one small but essential way. If the runtime literal was constructed at A, the replacement body must be reannotated by A: [i=ς(si:A)bi]iI.jς(s:A)c(OverrideSub)[j=ς(s:A)c,i=ς(si:A)bi]iI{j}. From this point on, (Override-Sub) supersedes the earlier (Override) root whenever subsumption is present. The premise that typed the receiver implies A<:A. By lemma 15.8, a body checked with s:A remains well typed with s:A. Leaving the annotation A on only the new method would not form a legal object literal.

Theorem 15.11 — Safety with width-invariant subtyping

Preservation and progress hold for closed terms of Ob1<: under (Override-Sub).

Proof of Theorem 15.11 — Safety with width-invariant subtyping

Proof. Use minimum typing to remove arbitrary final subsumption before analyzing an eliminator. For invocation, a value whose minimum type is below [i:Bi]iI is an object literal with at least those labels and the same result types. Applying lemma 15.9 to the invoked body and the actual receiver types the source reduct at Bj; a final subsumption restores any requested supertype.

For override, let the runtime receiver have construction type A, while the source override is annotated by A. Minimum typing gives A<:A. If its new body was checked at Bj under s:A, receiver narrowing gives the same judgment under s:A. Lemma 25.9 says that the j component of A is exactly Bj. Together with the retained method premises, T-Object types the reduct of (Override-Sub) at A, and T-Sub raises it to A. Compatible reduction preserves typing by the induction hypothesis. These are the only additions to the preservation proof of theorem 15.5.

For progress, use minimum typing and width inversion in place of the earlier canonical-forms lemma. A closed value usable at a nonempty object type must be a literal containing every visible label. Invocation and override therefore have a root contraction after their receivers become values. The ground cases are unchanged. ◻

Two tempting record rules fail

Width subtyping is safe because a hidden method remains present and every visible method keeps the result type assumed by all bodies. Covariance would break the second fact even though override is functional.

Let S:=[tag:Unit],T:=[],S<:T. For the covariance counterexample, define fresh local interfaces Qc:=[m:S,n:S],Pc:=[m:T,n:S]. Suppose, contrary to definition 15.6, that object components were covariant. Then Qc<:Pc. Define s0=[tag=ς(s:S)unit]:S,t0=[]:T, and q=[m=ς(s:Qc)s0,n=ς(s:Qc)s.m]:Qc. By the proposed covariance, q:Pc, so the term r:=q.mς(s:Pc)t0 would have type Pc, and r.n would have type S. Operationally, however, r.n[m=ς(s:Qc)t0,n=ς(s:Qc)s.m].n[m=ς(s:Qc)t0,n=ς(s:Qc)s.m].mt0. The alleged S-term has reduced to the empty object, and (r.n).tag is stuck. The old body of n relied on m:S; the new object retained that body while the replacement’s self parameter had type T, not the S assumed by the retained body. Functional update prevented mutation of q, but it did not protect the other methods of r.

Proposition 15.12 — Covariant method components destroy preservation

Adding JIBj<:Cj (jJ)[i:Bi]iI<:[j:Cj]jJSObjectCov to the functional calculus invalidates preservation.

Proof of Proposition 15.12 — Covariant method components destroy preservation

Proof. The constructed term r.n is typed at S. Its three-step reduct is t0=[], which is typed directly at T=[] by T-Object. It cannot be typed at S=[tag:Unit]: even the proposed covariant rule only forgets labels, so an induction on a subtype derivation shows that an object type below S must contain tag, whereas T contains none. Hence the reduct has no type S. Thus preservation fails on this closed term. ◻

A record field can be pulled out and used on its own. A self-bound method cannot, at least not at a shortened interface: its body may invoke a method that subsumption has hidden. For this failed-rule analysis only, make the conservative addition of arrows, lambda abstraction, and application with

Γ,x:Ab:B
Γλx:A.b:AB
T-Lam
Γf:ABΓa:A
Γfa:B
T-App

and the contraction (λx:A.b)vb[v/x]. Also add the total ground operation plus(m,n)m+n on numerals, with left-to-right contexts plus(E,a)andplus(n,E). None of these additions mentions objects, and none belongs to the source fragment translated in section 15.5.

Suppose there were an interface-annotated term aA with rules Γa:AA=[i:Bi]iIjIΓaAj:ABjTExtract and, for an actual object built at A, the unambiguous contraction [i=ς(si:A)bi]iIAjλs:A.bj. The annotation records the interface used by the typing derivation; without it, the A in the reduct would not be determined by the redex. For the extraction counterexample, define fresh local interfaces Px=[x:Nat,f:Nat],Qx=[x:Nat,y:Nat,f:Nat]<:Px. p=[x=ς(s:Px)1,f=ς(s:Px)1]:Px and a=[x=ς(s:Qx)1,y=ς(s:Qx)1,f=ς(s:Qx)plus(s.x,s.y)]:Qx. Subsumption gives a:Px, so extraction claims aPxf:PxNat. But (aPxf)p(λs:Px.plus(s.x,s.y))pplus(p.x,p.y), which is stuck at p.y. Invocation avoids this failure: it substitutes the actual object a:Qx, not an arbitrary argument of the shortened type Px.

Exercise 15.4

★★☆ For the covariance counterexample, draw the proposed derivation of (r.n).tag:Unit and mark the first reduction at which its preservation proof lacks a premise. For the extraction counterexample, identify the precise use of width subsumption that changes the type of the extracted self parameter.

A fluent object requires a recursive type

The point p1 can be updated from outside, but it cannot yet offer a move method that returns the updated point. If the point type is R, that method requires the equation R=[x:Nat,get:Nat,move:R]. No finite expansion closes this equation. After replacing the rightmost R once, the result contains another occurrence requiring another replacement.

We use the iso-recursive rules of definition 24.1. In particular, μX.A is not judgmentally equal to its unfolding, and the only ways to cross between them are fold and unfold. The formation rule for μX.A does not require positivity. The point equation below is in addition contractive under the definition of chapter 12, extended here by treating an object-type constructor as a guarding constructor: its occurrence of X lies beneath that constructor. No algorithmic equality between recursive types is being added.

Definition 15.13 — The recursive moving-point type

Define Point:=μX.[x:Nat,get:Nat,move:X] and its unfolding UPoint:=[x:Nat,get:Nat,move:Point]. The type variable X is bound in a type. It is unrelated to the term variables bound by ς.

Let o0:UPoint be [x=ς(s:UPoint)0,get=ς(s:UPoint)s.x,move=ς(s:UPoint)foldPoint(s.xς(t:UPoint)succ(s.x))]. Then origin:=foldPointo0:Point.

The move body is the only non-immediate typing premise. Under s:UPoint, invocation gives s.x:Nat, so under the additional fresh binder t:UPoint, succ(s.x):Nat. Override gives s:UPoints.xς(t:UPoint)succ(s.x):UPoint. Folding gives the required method result Point. The term receiver s is substituted when move is invoked; the type variable X was already replaced by Point when the recursive type was unfolded.

Define next:=(unfoldorigin).move. Write the unfolded object created by this invocation as o1:=[x=ς(t:UPoint)succ(o0.x),get=ς(s:UPoint)s.x,move=ς(s:UPoint)]. Then next:Point, and the complete observable calculation is (unfoldnext).getinvokeandunfoldo1.getinvokeo1.xoverridesucc(o0.x)projectionsucc(0)arithmetic1. The retained get body sees the replacement for x, exactly as in the finite point. Fold and unfold solve only the type equation. Invocation still substitutes the updated receiver o1 for self in every selected method body: invoking get contracts its body s.x to o1.x.

For this extension, Δ is the type-variable context and Γ is the term-variable context; a judgment has the form Δ;Γa:A. A declaration X extends only Δ, whereas a self declaration s:A extends only Γ. In contrast, chapter 8 places type bounds and term declarations in one context. Here type substitution changes Δ and the type annotations in Γ, whereas term substitution changes only terms under the fixed pair Δ;Γ.

Lemma 15.14 — The two substitutions

In the recursive object calculus:

  1. if Δ,X;Γa:A and ΔC type, then Δ;Γ[C/X]a[C/X]:A[C/X];

  2. if Δ;Γ,s:Ab:B and Δ;Γa:A, then Δ;Γb[a/s]:B.

Type substitution passes beneath a self binder after substituting in its annotation. Term substitution passes beneath μX because X binds types, not terms.

Proof of Lemma 15.14 — The two substitutions

Proof. For item 1, induct on typing and type formation. In an object premise Δ,X;Γ,s:Db:B, the induction hypothesis gives Δ;Γ[C/X],s:D[C/X]b[C/X]:B[C/X], which is precisely the premise for the substituted object. Fold and unfold use the type-substitution composition calculation from lemma 24.3. Invocation and override restore their last rules.

For item 2, induct on typing. Alpha-rename a method’s bound self variable away from the substituted s, apply the induction hypothesis to its body, and restore the object or override rule. Fold and unfold bind no term variable, so their cases apply the induction hypothesis to the payload and restore the same rule. All other cases are those of lemma 15.3. ◻

Proposition 15.15 — Safety of recursive objects

Adding the iso-recursive rules to Ob1, without adding recursive subtyping, preserves theorem 15.5.

Proof of Proposition 15.15 — Safety of recursive objects

Proof. The object roots still use item 2 of lemma 15.14. The new preservation root is unfold(foldμX.Av)v, whose typing derivation has v:A[μX.A/X] as the premise of fold and the result type of unfold. For progress, the only value form at a recursive type is foldμX.Av. Therefore, if the argument of unfold is already a value of recursive type, it exposes exactly the root contraction displayed above; otherwise the evaluation-context rule steps its argument. The fold evaluation-context case and all object cases are unchanged. ◻

Exercise 15.5

★★☆ Let p2:=(unfold((unfoldorigin).move)).move. Derive p2:Point and reduce (unfoldp2).get to 2. Keep the two successive runtime receivers distinct in the calculation.

Objects as existential recursive records

The direct safety proof leaves a structural question. Can invocation, functional override, width subtyping, and late self all be reconstructed from functions, records, existentials, and recursive types? The first attempt is A=μX.{i:XBi}iI. For width A<:B, an Amber-style comparison of the bodies assumes XA<:XB, but arrow contravariance asks for the reverse premise XB<:XA. Thus the naive record of methods cannot validate width. A second attempt stores a fixed self field self=r and implements override by changing only one method field. After overriding a coordinate method from 0 to 1, selection through the retained method still applies to the old r and returns 0, whereas source late self returns 1. In the one-coordinate fragment, the failure is already visible in the candidate target records r0=letrec r={self=r, x=0, get=λu:Unit.r.self.x}  in r,r1=r0{x:=1}. Functional update retains the closed body of get, so r1.getunitprojectionandbetar0.self.xprojectionr0.xprojection0, whereas source late self gives p1.get1. The record-update notation here belongs only to the failed candidate, not to F<:μ. The translation must therefore rebuild the suite on update and expose separate selection and update capabilities.

Rebuilding must hide the current receiver representation while retaining its public upper bound. The target calculus therefore combines a bounded existential package with an iso-recursive public interface.

Definition 15.16 — The target calculus F_<:μ

A type context Δ contains distinct bounds X<:S; a term context Γ contains distinct declarations x:S. Target types and the terms needed by the translation are S,T::=TopNatUnitXST{ki:Si}iIX<:S.TμX.T,t,u::=xnunitsucc(t)λx:S.ttu{ki=ti}iIt.kpack X<:S=R with tas X<:S.Topen t as X<:S,x:T in ufoldμX.Ttunfoldtletrec f(xi:Si)i=1n:T=tin u. All binders are modulo alpha-equivalence. Records have distinct labels and are covariant. The empty type context is well formed; if Δ is well formed, ΔS type, and X is fresh, then Δ,X<:S is well formed. Type formation is generated by

ΔS typeΔT type
ΔST type
FT-Arr
ΔSi type (iI)
Δ{ki:Si}iI type
FT-Record
ΔS typeΔ,X<:ST type
ΔX<:S.T type
FT-Exists
Δ,X<:TopT type
ΔμX.T type
FT-Mu

Variables declared in Δ, Nat, Unit, and Top are well formed. The basic subtype rules are

ΔS type
ΔS<:S
S-Refl
ΔR<:SΔS<:T
ΔR<:T
S-Trans
X<:SΔ
ΔX<:S
S-Bound
ΔS type
ΔS<:Top
S-Top

The remaining subtyping rules are

ΔS<:SΔT<:T
ΔST<:ST
S-Arr
JIΔSj<:Tj (jJ)
Δ{ki:Si}iI<:{kj:Tj}jJ
S-Rec
ΔS<:SΔ,X<:ST<:T
ΔX<:S.T<:X<:S.T
S-Exists

The recursive rule is traditionally called the Amber rule, after the recursive-type calculus in which this comparison principle was isolated:

Δ,Y<:Top,X<:YS<:T
ΔμX.S<:μY.T
S-Amber

In its premise, alpha-renaming makes X and Y fresh, and the body S is formed under X while T is formed under Y. Thus the assumption X<:Y is precisely the recursive simulation hypothesis. It says that one unfolding of the left body simulates one unfolding of the right while their recursive occurrences are related; the conclusion closes that hypothesis at the two recursive types. The chapter proves the structural lemmas and preservation cases needed by the translation, including generalized bounded-existential opening and letrec, but does not claim a standalone progress or normalization theorem for every target term. Formation deliberately admits noncontractive target types such as μX.X. The structural and translation proofs never expose a recursive head by unbounded equality, and S-Amber is used only as the displayed syntactic simulation rule. Every recursive type generated by the source translation is contractive: its recursive occurrence lies beneath the record constructor in CA(X). Thus no target progress claim for μX.X is needed or implied.

The empty term context is well formed over Δ, and it may be extended by a fresh x:S when S is well formed. Term typing includes

x:SΓ
Δ;Γx:S
F-Var
Δ;Γt:SΔS<:T
Δ;Γt:T
F-Sub

and

Δ;Γ,x:St:T
Δ;Γλx:S.t:ST
F-Lam
Δ;Γt:STΔ;Γu:S
Δ;Γtu:T
F-App
Δ;Γti:Si (iI)
Δ;Γ{ki=ti}iI:{ki:Si}iI
F-Record
Δ;Γt:{ki:Si}iIjI
Δ;Γt.kj:Sj
F-Proj

Numerals have type Nat, and unit has type Unit; if t:Nat, then succ(t):Nat. Bounded packages and opening are typed by

ΔR<:SΔ;Γt:T[R/X]
Δ;Γpack X<:S=R with t as X<:S.T:X<:S.T
F-Pack
Δ;Γp:X<:S.TΔ,X<:S;Γ,x:Tu:UXFV(U)
Δ;Γopen p as X<:S,x:T in u:U
F-Open

The opened type variable is fresh for Δ,Γ. The displayed escape condition prevents the hidden representation from occurring in the result. Iso-recursive typing is

Δ;Γt:T[μX.T/X]
Δ;ΓfoldμX.Tt:μX.T
F-Fold
Δ;Γt:μX.T
Δ;Γunfoldt:T[μX.T/X]
F-Unfold

Finally, if F=S1SnT, recursive creation has rule

Δ;Γ,f:F,x1:S1,,xn:Snt:TΔ;Γ,f:Fu:U
Δ;Γletrec f(xi:Si)i=1n:T=t in u:U
F-Letrec

This is a functional recursive binding. It allocates neither cells nor mutable record fields.

Lemma 25.20 — Top is maximal in target subtyping

If ΔTop<:T, then T=Top.

Proof of Lemma 25.20 — Top is maximal in target subtyping

Proof. Induct on the subtype derivation. Rules S-Refl and S-Top have target Top. The bound, arrow, record, existential, and Amber rules cannot have source Top. In the transitive case, apply the induction hypothesis first to Top<:S, obtaining S=Top, and then to S<:T, obtaining T=Top. ◻

The ordinary target rules already compute a complete small derivation. Put R={n:Nat}. Under x:Nat, its lines are ;x:Natx:Natby F-Var,;x:Nat{n=x}:Rby F-Record,;x:Nat,r:Rr:Rby F-Var,;x:Nat,r:Rr.n:Natby F-Proj,;x:Natλr:R.r.n:RNatby F-Lam,;x:Nat(λr:R.r.n){n=x}:Natby F-App. Target translation uses F-Pack to hide each self witness, F-Fold to form its recursive public interface, and F-Letrec to define the recursive record builder; source terms have no corresponding constructors.

The bounded package/open mechanism has a complete smaller instance. With R={n:Nat}, put p=pack X<:R=R with {n=x} as X<:R.X. The record derivation above and reflexivity of R<:R give ;x:Natp:X<:R.X. Inside open p as X<:R,r:X in r.n, rule S-Bound and F-Sub type r:R, so F-Proj types r.n:Nat. The result type contains no X; F-Open therefore gives the whole term type Nat. This is the same hidden-witness/public-bound invariant used by the object translation.

The translation is scoped to the nonrecursive source calculus Ob1<:. Its target F<:μ has arrows, covariant records, bounded existentials, iso-recursive types, and an unrestricted functional letrec. Unlike the full F<: of chapter 8, it omits bounded universals and Bot. Unlike the kinded existentials of chapter 10, a hidden witness is known to lie below a public interface. The operation is called open, and its result may not mention the fresh representation variable. Fold and unfold have the iso-recursive meaning fixed in chapter 12.

Definition 15.17 — Target reduction

Root contraction is generated by succ(n)n+1,(λx:S.t)ut[u/x],{ki=ti}iI.kjtj,jI,unfold(foldμX.Tt)t. The existential root is open(pack Y<:S0=R with t as Y<:S0.T0)as X<:S,x:T in uu[R/X][t/x]. Here Y and X are chosen fresh and independently. In particular, the actual package annotation Y<:S0.T0 need not be syntactically equal to the existential type expected by the open; a well-typed redex may have reached the latter type by package subsumption. The contraction uses the actual witness R and actual payload t, not the opening annotations. For a recursive declaration Df(xi:Si)i=1n:T=t, where F=S1SnT, put rD:=letrec D in f,hD:=λx1:S1.λxn:Sn.t[rD/f]. The recursive root is letrec D in uu[hD/f]. Taking u=f shows rDhD, so a recursive call can unfold again. The relation is the full compatible closure of these roots: a step is permitted in every subterm—the argument of a successor; the function or argument of an application; any record field; the receiver of a projection; the payload of a package; the package or body of an open; the payload of a fold or unfold; and the defining term or body of a letrec. It is also closed beneath lambda, open, and letrec binders after alpha-renaming away from captured variables. This congruence relation is not a deterministic evaluation strategy. Although and the source’s currently print the same base arrow, their roles are never interchangeable: the former is the nondeterministic full compatible target closure, while the latter is weak deterministic source evaluation. Translation statements name both relations explicitly.

The target rules are used immediately. For example, x:NatΓΔ;Γx:NatFVarΔ;Γunit:UnitΔ;Γ{a=x,b=unit}:{a:Nat,b:Unit}FRecordΔ;Γ{a=x,b=unit}.a:NatFProj. At the closed instance x=0, F-Proj contracts to 0.

Lemma 15.18 — Target narrowing and substitution

The following hold for F<:μ.

  1. If a formation, subtyping, or typing judgment is derivable under Δ0,X<:S,Δ1, and Δ0R<:S, it remains derivable after replacing the bound of X by R.

  2. If Δ;Γ0,x:S,Γ1t:T and ΔR<:S, then Δ;Γ0,x:R,Γ1t:T.

  3. If a target judgment is derivable under Δ0,X<:S,Δ1 and Δ0R<:S, then substituting R for X throughout the judgment and the trailing contexts yields a derivable judgment under Δ0,Δ1[R/X].

  4. If Δ;Γ,x:St:T and Δ;Γu:S, then Δ;Γt[u/x]:T.

Proof of Lemma 15.18 — Target narrowing and substitution

Proof. The four proofs are simultaneous inductions on derivation height, with the judgment form as a secondary case split. For item 1, the distinguished bound-variable case changes X<:S into X<:R<:S by transitivity. Other variables keep their bounds. Arrow and record subtyping apply the induction hypotheses component by component. In S-Exists, alpha-rename the existential variable, narrow the outer bound in both premises, and restore the rule. In S-Amber, make its two recursive variables fresh for X, narrow the body comparison, and restore the same simulation hypothesis. The typing rules merely propagate the narrowed type context.

For item 2, the distinguished term variable is typed at R and then raised to S by subsumption. Other variables are unchanged. Lambda, open, and letrec binders are first alpha-renamed away from x; the induction hypothesis applies to each premise in which the outer x remains free. Pack, fold, unfold, records, projection, and application restore their last rules. This proves item 2 of lemma 15.18.

For item 3, the variable-bound case uses the premise R<:S. The arrow and record cases are componentwise. For bounded existentials, choose the bound variable fresh for R, apply the induction hypothesis to its bound and body, and restore S-Exists, F-Pack, or F-Open. The open escape condition is preserved because its freshly bound variable is not substituted. For S-Amber, first freshen both recursive variables; substitution then commutes with their binding, and the substituted premise restores Amber. Fold and unfold use the composition equality (T[μY.T/Y])[R/X]=αT[R/X][μY.T[R/X]/Y]. The letrec case substitutes in every parameter type, result type, defining term, and body before restoring F-Letrec.

For item 4, the distinguished variable case is exactly the assumed typing of u; every other variable retains its declaration. Alpha-renaming handles lambda, open, and letrec binders. In a package, substitute in its payload and retain the same witness and existential family; in an open term, substitute independently in the package and body and restore the escape condition. Successor, records, projection, application, fold, and unfold are homomorphic. Subsumption restores its unchanged target subtype judgment. These cases exhaust the target syntax. ◻

Lemma 25.23 — Existential inversion and the generalized open root

After alpha-renaming the bound variables to X, if ΔX<:S0.T0<:X<:S.T, then ΔS0<:S,Δ,X<:S0T0<:T. Moreover, every instance of F-Open whose package is the package value in definition 15.17 gives its reduct the same result type.

Proof of Lemma 25.23 — Existential inversion and the generalized open root

Proof. Prove inversion together with this shape claim: if an existential is below C, then C is an existential or Top. Rule induction proves the shape claim, with transitivity using it at both subderivations. Reflexivity gives S0=S and T0=T. If the last rule is S-Exists, its premises are exactly S0<:S and Δ,X<:S0T0<:T. In the transitive case the intermediate type cannot be Top: no derivation can conclude ΔTop<:C with CTop, by lemma 25.20. Write it as X<:S1.T1. The two induction hypotheses give S0<:S1<:S,X<:S0T0<:T1,X<:S1T1<:T. Narrow the last judgment from S1 to S0 by lemma 15.18 item 1, then compose both pairs by transitivity.

Now type a generalized open redex at U. Inversion of the package typing, including any final chain of F-Sub, gives ΔR<:S0,Δ;Γt:T0[R/X],ΔX<:S0.T0<:X<:S.T. This package inversion is an induction on typing: F-Pack gives the first two judgments, and every F-Sub composes one more subtype step. The outer-constructor inversion just proved ensures that each intermediate type on a path ending in an existential is again an existential.

Existential inversion gives S0<:S and X<:S0T0<:T. Substitute R for X in the latter judgment by lemma 15.18 item 3 and use subsumption to obtain Δ;Γt:T[R/X]. The other premise of F-Open is Δ,X<:S;Γ,x:Tu:U,XFV(U). Since R<:S0<:S, type substitution gives Δ;Γ,x:T[R/X]u[R/X]:U; the escape condition removes X from the result type. Term substitution with the payload now gives Δ;Γu[R/X][t/x]:U, which is precisely the generalized open reduct. ◻

The recursive root is well typed. Rule F-Letrec gives rD:F. By substitution (item 4 of lemma 15.18), the defining judgment gives t[rD/f]:T, so the lambdas give hD:F. A second use of item 4 in the body judgment gives u[hD/f]:U, exactly the type of the reduct.

For A=[i:Bi]iI, define CA(X):={isel:XBi,iupd:(XBi)X(iI),self:X}, and A:=μY.X<:Y.CA(X). Ground types and Top translate to their target counterparts. Selection makes Bi occur covariantly; update makes it occur contravariantly. Their combination explains why the source component is invariant. The bound X<:Y hides the object’s full interface while recording that it implements the public one.

Lemma 15.19 — Width is preserved by the type translation

If A<:B in the nonrecursive source calculus, then A<:B in F<:μ.

Proof of Lemma 15.19 — Width is preserved by the type translation

Proof. Reflexivity, transitivity, and Top are preserved by the corresponding target rules. Consider width. Write A=[i:Bi]iI,B=[j:Bj]jJ,JI. The shared result types are identical. For any representation variable X, S-Rec gives CA(X)<:CB(X): it drops both sel and upd for every hidden label, while retaining self:X. To apply Amber, work under YB<:Top,YA<:YB. Rule S-Exists uses the latter bound and, under X<:YA, the record comparison just proved. Hence X<:YA.CA(X)<:X<:YB.CB(X). Rule S-Amber now yields μYA.X<:YA.CA(X)<:μYB.X<:YB.CB(X). No component variance is used: the common selection and update field types are literally the same. ◻

For a label and a result type B, put L,B:=μY.X<:Y.{sel:XB,self:X}.

Lemma 15.20 — The visible-method target bound

If the minimum type of a is A0=[i:Bi]iI and =j with B=Bj, then A0<:L,B.

Proof of Lemma 15.20 — The visible-method target bound

Proof. Use S-Amber with recursive variables Y0,YL. Work in the type context YL<:Top,Y0<:YL. There S-Exists reduces the desired body comparison to CA0(X)<:{sel:XB,self:X}(X<:Y0). This is one application of covariant record width: retain the selection and self fields, and discard every update field and every field for a different label. The retained selection type is literally XB because source object components are invariant. Restoring S-Exists and then S-Amber proves the displayed subtype. ◻

For a fixed object type A=[i:Bi]iI, take I={1,,n} and write o=[i=ς(si:A)bi]iI. First fix the recursive declaration DA create(fi:ABi)iI:A=foldA(pack X<:A=A with{isel=fi, iupd=λg:ABi.create(f1,,fi1,g,fi+1,,fn), self=create(f1,,fn)}as X<:A.CA(X)). Recall rDA=letrec DA in create. The object translation is the application of that recursive function: trΓ(o):=rDA(λsi:A.trΓ,si:A(bi))iI. If f¯=(fi)iI, the letrec root followed by beta roots gives rDA(f¯)foldA(pack X<:A=A with{isel=fi,iupd=λg.rDA(f¯[i:=g]),self=rDA(f¯)}as X<:A.CA(X)), with the bound and existential annotation exactly as in equation 25.2. Thus the stored self and the separately translated object are literally the same term rDA(f¯), not two letrec expressions that merely unfold to similar records. Each update calls the same recursive function with one replacement, so its stored self is the new object rather than a frozen predecessor. The occurrence of create in the self field is not evaluated eagerly. The target relation is a compatible congruence, not a strategy; one letrec unfolding produces the finite term displayed above, and later reductions unfold another copy only when that occurrence is used.

For a label , minimum typing and lemma 25.9 make its component type B independent of which declarative object supertype was used to type the receiver. Put u:=a.ς(s:A)b. Invocation and override translate as trΓ(a.):=openunfold(trΓ(a))asX<:L,B, z:{sel:XB,self:X}(TInv)inz.selz.self,trΓ(u):=openunfold(trΓ(a))asX<:A, z:CA(X)(TUpd)inz.upd(λs:X.trΓ,s:A(b)). Variables translate to themselves. The environment translation replaces every x:A by x:A. In (TUpd), the opened representation X cannot escape: the update field first returns X, but target subsumption uses X<:A before the open closes, so the whole term has the public type A.

Lemma 25.26 — Translation is invariant under receiver narrowing

If Γ,s:Aa:B and A0<:A, then, after choosing all generated binders fresh, trΓ,s:A(a)=αtrΓ,s:A0(a). The right-hand translation is defined because lemma 15.8 gives Γ,s:A0a:B.

Proof of Lemma 25.26 — Translation is invariant under receiver narrowing

Proof. Induct on the source term. Variables and ground forms translate identically. For an object, its self annotations, declaration DA, and generated create-parameter types are fixed by its syntax. Alpha-rename each method’s self binder away from the distinguished s, use item 3 of lemma 15.3 to put the outer s last, and apply the induction hypothesis to the body. Thus every translated method function, and hence the application of rDA, is alpha-equal in the two environments.

For an invocation r., let P and P0 be the minimum types of r before and after narrowing. The old minimum derivation gives Γ,s:Ar:P; source narrowing gives Γ,s:A0r:P. Minimum typing in the narrowed context therefore gives P0<:P. If the invoked component of P is :C, lemma 25.9 says that the same component of P0 is exactly C. Consequently both translations use L,C, while their receiver translations are alpha-equal by induction.

For an override, the public object annotation and component type used in (TUpd) are written in the source term and hence unchanged. Apply the induction hypothesis to the receiver and, after freshening its bound self variable and using item 3 of lemma 15.3, to the replacement body. The generated open and lambda binders may then be alpha-renamed to agree. These cases exhaust the nonrecursive source syntax. ◻

Lemma 15.21 — Translation commutes with source substitution

If Γ,x:Ab:B and Γa:A, then, after choosing the generated target binders fresh, trΓ(b[a/x])=αtrΓ,x:A(b)[trΓ(a)/x].

Proof of Lemma 15.21 — Translation commutes with source substitution

Proof. Induct on b. The distinguished variable gives the displayed substituend; another variable is unchanged. For an object, alpha-rename every self binder and every generated create parameter away from x and the free variables of a. Apply the induction hypothesis to each method body. The declaration DA and surrounding application of rDA are syntactically identical on both sides, so the translated method functions are equal up to the chosen alpha-renaming.

For invocation, suppose the receiver r has minimum type P in Γ,x:A, and its -component is B. Minimum typing first gives Γ,x:Ar:P. Source substitution then gives Γr[a/x]:P. If P0 is the minimum type of the substituted receiver, the minimum-typing theorem yields P0<:P. Lemma 25.9 therefore says that the -component of P0 is the same B, even though substitution may have refined the receiver minimum. The receiver translations agree by the induction hypothesis, and both sides consequently open the identical bound L,B.

For override, apply the induction hypothesis to the receiver and, after making its self binder fresh, to the replacement body. The public annotation and its selected component are fixed in the source syntax, so term substitution changes neither. Variables, object literals, invocation, override, and ground constructors commute with substitution by the cases just given, exhausting the nonrecursive source term grammar. ◻

Minimum typing chooses the source shape from which the target package is built. In the invocation and override cases, target narrowing changes the opened self assumption from the public interface to the hidden witness type. For each source root, the simulation proof first performs the target administrative reductions that expose the translated method body and then uses translation-substitution on that body.

Theorem 25.28 — Scoped typing of the object translation

If Γa:A in the nonrecursive source calculus, then ΓtrΓ(a):A.

Proof of Theorem 25.28 — Scoped typing of the object translation

Proof. Let A0 be the unique minimum type of a. Induct on its minimum-typing derivation to obtain ΓtrΓ(a):A0. Since A0<:A, lemma 15.19 and target subsumption then give the stated type A. Variables use the translated context.

Objects. For an object, M-Object gives minimum body types Bi<:Bi. The induction hypotheses and target subsumption therefore type λsi:A.tr(bi):ABi. Assume these functions as the parameters fi of create. With existential witness X=A, the selection field isel=fi has type ABi, and the update field iupd=λg.create(f¯[i:=g]) has type (ABi)A. The self field create(f¯) also has type A. Hence the record has type CA(A). Rule F-Pack, with witness A<:A and existential family CA(X), gives X<:A.CA(X); F-Fold then returns A. Rule F-Letrec therefore types rDA at (AB1)(ABn)A. Applying it to the translated method functions proves the object case.

Invocation. For invocation, let A0 be the minimum type of the receiver. It contains :B, so lemma 15.20 gives A0<:L,B. Target subsumption followed by F-Unfold therefore gives an existential package whose hidden representation X is below L,B. Opening it yields z.sel:XB,z.self:X, so their application has type B. The result does not contain X, which discharges the existential escape condition.

Override. For override, let A0 be the minimum receiver type. Rule M-Override gives A0<:A, so lemma 15.19 permits target subsumption from A0 to A before unfold. Opening gives z.upd:(XB)X,X<:A. The same source rule gives a minimum body type B<:B. The induction hypothesis followed by target subsumption types its translation at B under s:A. In the opened type context, X<:A; target term-context narrowing, lemma 15.18 item 2, changes that assumption to s:X. Thus the lambda has type XB. The update field returns X, and target subsumption returns the public result type A, again free of the opened X. ◻

Theorem 15.22 — Scoped typing and simulation

If Γa:A and aa, then trΓ(a)trΓ(a) in the target. The theorem claims neither reflection of every target reduction nor full abstraction for contextual equivalence.

Proof of Theorem 15.22 — Scoped typing and simulation

Proof. Simulation contexts. For simulation, induct on the source reduction. The compatible-context cases require one check because the translation is type directed. Suppose a receiver step aa occurs under invocation by . Inversion of the enclosing typing yields an interface K containing :B, together with the derivation Γa:K. Source preservation gives Γa:K. If P and P are the respective minimum receiver types, minimum typing gives P<:K and P<:K. Lemma 25.9 recovers the identical component B in both minima. Thus both outer translations use exactly L,B, and target congruence lifts the induction-hypothesis sequence from tr(a) to tr(a).

For a receiver step under override, the public annotation, selected label, and selected component are fixed syntactically by the override term. The two outer target contexts are therefore identical, and target congruence again lifts the induction hypothesis. Ground evaluation contexts, such as succ(), translate homomorphically. Weak evaluation has no context beneath a method binder. This exhausts the compatible contexts.

Invocation root. At an invocation root, write the receiver translation as rDA(f¯). The letrec and beta roots in equation 25.4, followed by unfold and the generalized open root justified by lemma 25.23, expose its record. Projection selects fj, while self selects the literal term rDA(f¯). Beta reduction therefore gives (λsj:A.tr(bj))rDA(f¯). The argument is definitionally tr([i=ς(si:A)bi]iI), so this reduces by target substitution to the translation of the source substitution in (Invoke), by lemma 15.21.

Override root. At an override root, let the runtime receiver have construction type A0 and let the override carry the public annotation A. As in (Override-Sub), minimum typing gives A0<:A. The receiver translation therefore contains an actual package annotated X<:A0.CA0(X), whereas (TUpd) opens it at X<:A.CA(X). The generalized open root is applicable: its typing is exactly the package-subsumption case of lemma 25.23, and it substitutes the actual witness A0 and actual record payload.

Projection selects the actual jupd field. Its application and beta steps reduce to rDA0(f1,,fj1,g0,fj+1,,fn),g0=λs:A0.trΓ,s:A(b). Source receiver narrowing reannotates the replacement method by A0 in the reduct. By lemma 25.26, trΓ,s:A(b)=αtrΓ,s:A0(b), so g0 is alpha-equal to the method function in the translation of that reannotated source object. Thus the displayed rDA0-application is literally the translation of the source reduct. This proves the direct directional step tr(a)tr(a); no reverse target reduction is used. Its stored self is that same application, so subsequent invocations see the replacement. Ground contractions translate homomorphically. These cases prove item 2. ◻

Exercise 15.6 — *

★★★ In equation 15.2, abbreviate the two original method functions of p0 by fx,fget, and put g:=λs:P.1. Translate p1.get. Using only the root contractions of definition 15.17, first reduce the override to rDP(g,fget). Then reduce the invocation until fget is applied to the self field rDP(g,fget) of that same unfolding, and continue to 1. Name every letrec, beta, unfold, open, and projection root.

The translation gives a semantic explanation, not a proposal for an efficient object layout. Its bounded existential hides private methods; its recursive record connects the hidden representation to the public interface; its paired selection and update components enforce invariance; and recursive creation re-ties self after every functional override. These four jobs cannot be performed by the naive recursive record alone.

The extension obstruction

The recursive point returns exactly Point from move. Add a color method and one expects the moved colored point to remain colored. Define the two candidate equations Point=μX.[x:Nat,get:Nat,move:X],ColorPoint=μX.[x:Nat,get:Nat,move:X,color:Nat]. After unfolding, one would like width subtyping to forget color. Invariance blocks the comparison because the common move components are ColorPoint and Point, not the same type. Replacing invariance by covariance is not a remedy: proposition 15.12 shows that another retained method can rely on the more precise result and be invalidated by override through the shorter view. This is the precise contrast with covariant immutable records in chapter 8: record projection only reads a component, whereas an object method can be replaced and every retained method may invoke that replacement through self. The read/write combination forces invariance.

One may instead declare the colored point’s move method to return Point. That permits forgetting color after a move, but it does not express the desired fluent interface. The missing phrase is “the exact type of the present receiver, as refined by extension.” A recursively bound type variable fixes one equation; it does not vary with later extension. This is the Point/ColorPoint obstruction. No solution to it is assumed in this chapter.

The obstruction is a fixed recursive equation where extension requires a type that varies with the current receiver. F-bounded quantification and matching provide that varying type for immutable records. The reusable ingredients are late self and the shape μY.X<:Y.C(X); covariance becomes safe once functional update is removed.

Exercise 15.7

★★☆ Unfold Point and ColorPoint once. Attempt the width-invariant subtyping derivation in both directions and identify the first unequal shared component. Then replace the result of the colored move method by Point and state exactly what useful typing is recovered and what fluent typing is lost.

Sources.

The primitive syntax, self substitution, functional invocation and override, first-order typing, width-invariant subtyping, minimum typing, the covariance and extraction failures, and explicit recursive-object examples follow Abadi and Cardelli, A Theory of Primitive Objects: Untyped and First-Order Systems, author full version, Sections 2.1, 3.1–3.2.2, 4.1.1–4.1.2, 4.5.1–4.5.2, and 4.6–4.8. The two typed safety developments are at PDF pp. 15–17 and 21–23; the failed covariance/extraction rules and recursive objects are at PDF pp. 24–29 [AC96b]. The package/open typing and generalized contraction are in Table 1 of Abadi, Cardelli, and Viswanathan, An Interpretation of Objects and Object Types. Its target type μY.X<:Y.CA(X), separate selection, update, and self fields, recursive create, and exact translation scope are in Sections 3.2–3.3, Table 2, and Theorems 3.1–3.3, PDF pp. 3–6 [ACV96]. That source proves computational adequacy but explicitly does not claim full abstraction; this chapter makes no stronger claim.

Suggested first pass.

Begin with exercise 15.8, exercise 15.9, exercise 15.13; then use the remaining problems to reconstruct safety and the translation.

Exercise 15.8

★★☆ Let T=[a:Nat,b:Nat,c:Nat] and t=[a=ς(s:T)0,b=ς(s:T)s.a,c=ς(s:T)s.b]. Override a with the constant method returning 4, then reduce c on the new object. Give the typing derivation and every invocation step, keeping the runtime receiver visible.

Exercise 15.9

★★☆ For A=[p:Unit],B=[p:Unit,q:Unit],b=[p=ς(s:B)unit,q=ς(s:B)s.p], compute the minimum types of b, b.p, and b.pς(s:A)unit. Prove that every other declarative type of each term is a supertype of the one you give.

Exercise 15.10

★★☆ Reprove preservation for invocation in Ob1<: without saying “by inversion” as a single step. Start from an arbitrary declarative derivation, extract its minimum receiver type, use width invariance to recover the selected component, display the body-substitution judgment, and restore the requested result supertype.

Exercise 15.11

★★☆ Modify the covariance counterexample so that Qc has a third method k:S whose body invokes n, and the final stuck term invokes k before selecting tag. Give all types and show the full reduction. Explain why the extra indirection does not change the source of the failure.

Exercise 15.12 — *

★★★ Let A=[:B], Γ,s:Ab:B, and Γ,s:Ac:B. Put o=[=ς(s:A)b] and u=o.ς(s:A)c. Type every field of CA(A), its package as X<:A.CA(X), and the enclosing fold. Put o=[=ς(s:A)c]. First show the source roots uo and o.c[o/s]. Then use the target roots and the translation theorem to simulate trΓ(u.)trΓ(o.)trΓ(c[o/s]). Each arrow is a nonempty many-step target reduction; name every letrec, beta, unfold, open, and projection root it contains. Display the rDA-application containing the translated replacement method and use lemma 15.21 only for the final invocation beta step.

Exercise 15.13

★★☆ Construct origin, next, and a colored analogue whose move method returns ColorPoint. Type each object at its own recursive type. Then show that width-invariant subtyping cannot pass the colored object to a context requiring Point, although both objects separately satisfy preservation and progress.

Exercise 15.14

★★☆ Erase the self annotations from object literals and let A=[:[]], A=[:A], and a=[=ς(s)[=ς(t)[]]]. Work in Ob1<: with subsumption and the erased formation rule A=[i:Bi]iIΓ,si:Abi:Bi (iI)Γ[i=ς(si)bi]iI:ATObjectErased. For the typing at A, show explicitly how width subtyping and T-Sub type the inner one-method object at the empty object result type. Give another typing of a at A. Prove that A and A have no common subtype, so the unannotated calculus has no minimum type for a. Identify the two points at which the proof of theorem 15.10 can no longer recover a type from syntax.

Exercise 15.15

★★☆ Consider the incorrect target scheme that recursively defines one record r with selection fields isel, sets r.self=r, and implements source override by functional record update of one selection field. Translate p1.get with this scheme and reduce it to 0. Then place beside it the source reduction to 1 and locate the frozen occurrence of r responsible for the discrepancy.

Exercise 15.16

★★☆ Under Δ,X;Γ,s:Ab:B, ΔC type, and Δ,X;Γa:A, freshen every binder away from the substituends and prove b[C/X][a[C/X]/s]=αb[a/s][C/X]. Work out the object-body and fold-payload cases. Explain why no condition XFV(a) is needed and why a type C cannot contain the term variable s.

Exercise 25.17

★★★ Practical project.object-override-simulator Implement a finite object checker and evaluator for the point, override, fluent-self, and frozen-self cases. Preserve the invariant that override restores the runtime minimum annotation and every method body sees the rebuilt receiver. Include seven named observations that isolate late self, minimum typing, override reconstruction, and rejection of the frozen candidate. Then change creation to retain the old receiver, retain only the apparent annotation, and make components covariant in three independent variants; each must falsify its corresponding observation. The finite machine checks these traces, not the translation or safety theorems. Appendix E records the commands, and appendix F develops the implementation.

Search the book

Type to search the local edition.