Lectures onType Theory
ch:oo-self-types: ch:oo-self-types
appendix sectionsolutions

ch:oo-self-types: ch:oo-self-types

Exercise 16.1.

With the added field, the comparison required by S-Self contains XX<:YYunder X<:Y. Arrow subtyping asks for the domain premise (Y<:X) and the codomain premise (X<:Y). Only the latter is available. Thus the binary method is not covariant in the changing receiver type. If both interfaces instead use the fixed argument type, the comparison is PointX<:PointY. Its premises are (Point<:Point), by reflexivity, and (X<:Y), by the recursive assumption. Hence the fixed-argument method is compatible with the Self-subtyping proof.

Exercise 16.2.

The classifications, together with the comparisons induced by (C<:D), are as follows. B(X)polarityXpositiveNatXpositiveXBoolnegative(XBool)Natpositive{a:X,b:NatX}positive. The positive cases induce C<:D,NatC<:NatD,(CBool)Nat<:(DBool)Nat,{a:C,b:NatC}<:{a:D,b:NatD}. The negative case instead induces DBool<:CBool. The fourth line has two variance reversals: (C<:D) first gives (DBool<:CBool); this is exactly the contravariant domain premise for the displayed outer-arrow comparison. The rejected positive use is (XBool). To derive (CBool<:DBool), arrow subtyping would require (D<:C), the unavailable orientation. None of these five families is both positive and negative: each contains a genuine occurrence of (X), and the occurrence paths all have one fixed parity.

Exercise 16.3.

Let rC={x=0,color=true,clone=λu:Unit.c0,move=λd:Nat.c0}. Unfolding (c0) in the receiver position and expanding derived selection gives (c0move)3(useSelf(packSelf C with rC as C)as X<:C,z:ColorPointF(X) in z.move)3. The use body has type (NatC): projection first gives (z.move:NatX), and (X<:C) gives (NatX<:NatC). At the root, first perform the type substitution, (z.move)[C/X]=z.move,z:ColorPointF(C), and only then the term substitution, (z.move)[rC/z]=rC.move. Consequently (useSelfin z.move)3: C(rC.move)3: C(λd:Nat.c0)3: Cc0: C. Before application, each function in the trace has type (NatC), and (3:Nat); hence every displayed application has result type (C). One further call-by-value step would unfold (c0) to its package value, but the requested named endpoint is (c0).

Exercise 16.4.

Write Si=SelfX.Ri(X)(i=0,1),S=SelfX.R(X). Typing inversion for the redex supplies C<:S0,v:R0(C),X<:S,x:R(X)b:D,XFV(D). The first application of transitivity combines (C<:S0) and (S0<:S1), giving (C<:S1). The second combines this judgment with (S1<:S), giving (C<:S). Separately, transitivity gives (S0<:S); Self-subtyping inversion then yields X<:TopR0(X)<:R(X). Type substitution with the well-formed hidden witness C gives R0(C)<:R(C). Payload subsumption therefore changes v:R0(C) into v:R(C).

Use the derived (C<:S) in the bound-variable case of type substitution on the body premise. Since (X) does not escape (D), this gives x:R(C)b[C/X]:D. Term substitution with (v:R(C)) finally gives b[C/X][v/x]:D, which is exactly the (UseSelf) reduct at the original result type. Replacing (C) by (S) would instead produce (b[S/X]) and require a payload of type (R(S)). The runtime package contains (v:R0(C)), and neither packaging nor subsumption changes its hidden representation witness; there is no equality (C=S). Such a replacement would erase the very abstraction that the package records.

Exercise 16.5.

Put (Q={ok:Bool}) and replace the two equality fields by (XQ). Use payloads eq=λq:PE.{ok=false}andeq=λq:CE.{ok=qcolor} for the plain and colored packages. If the forbidden selection coercion is inserted, the supposed term of type (Q) reduces as follows: rP={x=0,eq=λq:PE.{ok=false}}. ((cE:PE)eq)pE(λq:CE.{ok=qcolor})pE{ok=pEcolor}{ok=rP.color}. The last record is not a value, and its field term is neither a value nor a redex.

The missing judgment occurs when selection tries to turn the projected method XQintoPEQ(X<:PE). Arrow subtyping would require (PE<:X), whereas the hidden bound gives only (X<:PE). The pointwise premise of S-Self compares the two record families at the same formal parameter (X), so both equality fields there have the identical type (XQ); record width can discard the color field. That premise says nothing about the distinct comparison (XQ<:PEQ) needed by selection, and therefore is not a term coercion.

Exercise 16.6.

The required client is r=ΛX<:ResetF(X).λp:X.(p.same)(p.reset). Under (X<:ResetF(X)), subsumption gives p:ResetF(X)={x:Nat,reset:X,same:XBool}. Projection therefore gives (p.reset:X) and (p.same:XBool); application gives the body type (Bool). Abstraction and F-bound introduction yield r:X<:ResetF(X).XBool. The derivation chooses one (X) and uses it for both receiver and argument. It never compares two different post-fixpoints, so it implies no subtype relation between them; in particular, the contravariant domain of (same) blocks the usual width-style recursive comparison.

Let (R=μX.ResetF(X)) in the equi-recursive calculus. First, conversion is an equality judgment: R=ResetF(R). Only afterward do reflexivity and conversion yield the subtyping premise needed for F-elimination: R<:ResetF(R). Thus (r[R]:RBool). The equality unfolds the recursive type; the subtype judgment discharges the bound. They are not one rule.

Exercise 16.7.

Define MarkedMinMaxF(X)={n:Nat,max:XX,min:XX,marked:Bool},MarkedMinMax=μX.MarkedMinMaxF(X),MarkedMinMaxOp=λX:.MarkedMinMaxF(X). For an arbitrary (Z:), record width, with reflexivity on every retained field, gives MarkedMinMaxOp(Z)<:MinMaxOp(Z),MinMaxOp(Z)<:MaxOp(Z). The translation of MarkedMinMax is μMarkedMinMaxOp. Rule S-OpPoint turns the pointwise judgments into operator comparisons, hence MarkedMinMax#MinMaxandMinMax#Max. Transitivity of operator subtyping gives (MarkedMinMax#Max) directly. Instantiating the client at the third operator gives, with M=MarkedMinMax, preMax[M]:MMM. Thus both arguments and the result are exactly of type (MarkedMinMax). No argument or result is coerced to either (MinMax) or (Max).

Exercise 16.8.

The match-bound variable exposes the (n)-field, so g=ΛX#Max.λp:X.p.n:X#Max.XNat. Because MinMax#Max, match application gives g[MinMax]:MinMaxNat.

In the target, write the bound operator as (ΦXMaxOp). The translation is g=ΛΦXMaxOp.λp:μΦX.(unfoldΦX(p)).n. For (p:μΦX), the derivation has the three requested steps: unfoldΦX(p):ΦX(μΦX),ΦX(μΦX)<:MaxOp(μΦX),unfoldΦX(p):{n:Nat,max:μΦXμΦX},(unfoldΦX(p)).n:Nat. The second line is S-OpApp; the third is term subsumption; the fourth is projection. Instantiation substitutes (MinMaxOp) for (ΦX). Matching authorizes that operator instantiation, not a source subsumption rule. Thus an independently supplied (m:MinMax) may be passed to (g[MinMax]), but the judgment (m:Max) is still not derivable.

Exercise 16.9.

Allocation starts the calculation: ,ref 0{0},. Take (Σ1={:Nat}), replace (r) by () in (kr), and call the resulting value (k). The invariant holds because the two domains are ({}) and (;Σ1;0:Nat). Writing (a2=k), put r={contents=,set=λn:Nat.(λw:Unit.k)(:=n)},p=packSelf K with r as K,Uj(a)=useSelf a as X<:K,z:RK(X) in z.j,B2=λu:K.!(a2contents). Here RK is the record family in K, and aj expands to Uj(a). Compatible-context and root steps give {0},B2((kset)7)={0},B2(Uset(k)7){0},B2(Uset(p)7){0},B2((r.set)7){0},B2((λn:Nat.(λw:Unit.k)(:=n))7){0},B2((λw:Unit.k)(:=7)){7},B2((λw:Unit.k)unit){7},B2k{7},B2p{7},!Ucontents(a2){7},!Ucontents(p){7},!(r.contents){7},!{7},7. The only equality is expansion of derived selection. The subsequent arrows are, in order, fixpoint unfolding, (UseSelf), projection, beta, assignment, beta, fixpoint unfolding, beta, fixpoint unfolding, (UseSelf), projection, and dereference, all in the displayed left-to-right contexts. After assignment, the store and (Σ1) still have the same domain and (7:Nat), so the invariant is preserved. By contrast, (:=true) would require (true:Nat) in T-Assign; it is rejected before a configuration step exists.

Exercise 16.10.

Starting at each field type and multiplying the variance signs along the path to (X) gives: fieldpolarity of Xclone:X+map:(NatBool)X+equal:XBoolchoose:(XBool)X+. The positive comparisons are C<:D for clone, (NatBool)C<:(NatBool)D,(CBool)C<:(DBool)D for map and choose, respectively. The negative field has DBool<:CBool. For (choose), the occurrence in the inner domain crosses two arrow domains and is positive; the result occurrence is directly positive. The largest legal positive subrecord therefore contains (clone), (map), and (choose), and removes only (equal). Keeping (equal) in a covariant record comparison would require (D<:C) in order to derive (CBool<:DBool); only (C<:D) is available.

Exercise 16.11.

Let T=SelfX.{n:Nat,b:Bool,toggle:UnitX},t0=fix t:T.packSelf T with{n=0,b=false,toggle=λu:Unit.t} as T. The family is positive. Under (t:T), the record has the payload type at witness (T); reflexivity discharges (T<:T), so T-PackSelf and T-Fix derive (t0:T). If (rT) denotes this record with (t0) in its last field, then (t0toggle)unit(rT.toggle)unit(λu:Unit.t0)unitt0.

Omit the natural field and put T=SelfX.{b:Bool,toggle:UnitX}. At an arbitrary formal (X), record width gives the full payload family below the smaller one, so S-Self gives (T<:T). Opening the runtime package annotated (T) at expected type (T), inversion yields hidden witness (C=T), payload (rT:RT(T)), and (T<:T). Self-family inversion gives (RT(X)<:RT(X)); type substitution gives (RT(T)<:RT(T)); payload subsumption gives (rT:RT(T)). Finally substitute (T) for the hidden type binder and (rT) for the payload binder in the use body. This is the generalized (UseSelf) preservation calculation, with the omitted field discarded only by record subsumption.

Exercise 16.12.

Let OrdOp=λX:.Ordered(X),NamedOrdOp=λX:.NamedOrdered(X). For every (Z:), record width gives (NamedOrdered(Z)<:Ordered(Z)). Hence (NamedOrdOpOrdOp), so μNamedOrdOp#μOrdOp. Ordinary recursive subtyping need not hold. After unfolding, comparison of the (le) fields would compare (μNamedOrdOpBool) with (μOrdOpBool); the arrow-domain premise has the reverse orientation and is not supplied by record width.

The source match-polymorphic client and its target are cmp=ΛX#μOrdOp.λp:X.λq:X.(p.le)q,cmp=ΛΦOrdOp.λp:μΦ.λq:μΦ.((unfoldΦp).le)q. For the target body, (unfoldΦp:Φ(μΦ)), (Φ(μΦ)<:OrdOp(μΦ)), and subsumption exposes (le:μΦBool); applying it to (q:μΦ) gives (Bool). Construction is explicit too. For example, if rN={name=0,le=λq:μNamedOrdOp.false}, then (rN:NamedOrdOp(μNamedOrdOp)) and n=foldNamedOrdOp(rN):μNamedOrdOp. Instantiating (cmp) at (NamedOrdOp) and applying it to (n,n) unfolds (n), exposes the record, projects (le), and beta-reduces to (false).

The F-bounded analogue is ΛX<:Ordered(X).λp:X.λq:X.(p.le)q:X<:Ordered(X).XXBool. It may be instantiated at (N=μX.NamedOrdered(X)), because equi-recursive conversion and record width give (N<:Ordered(N)). Neither construction derives the absent subsumption judgment (m:μOrdOp) from (m:μNamedOrdOp): matching discharges a polymorphic bound; it does not coerce independently supplied values.

Exercise 16.13.

For preservation, induct on the reduction derivation. In each compatible context, invert the outer typing rule, apply the induction hypothesis to the unique active subterm, and rebuild that same rule; for record fields this is done at the leftmost nonvalue component. Beta and fixpoint unfolding use term substitution. The (succ), conditional, and record projection roots follow from inversion of their typing derivations.

For the Self root, canonical forms and typing inversion write the redex as a package with hidden witness C, runtime annotation S0=SelfX.R0(X), payload v:R0(C), and expected type S=SelfX.R(X), with C<:S0<:S,X<:S,x:R(X)b:D,XFV(D). Self-subtyping inversion gives (R0(X)<:R(X)) under (X<:Top). Substitution of (C) gives (R0(C)<:R(C)), and payload subsumption gives (v:R(C)). Transitivity gives (C<:S), so hidden-witness type substitution in the body yields (x:R(C)b[C/X]:D). Payload substitution then derives (b[C/X][v/x]:D), exactly the reduct. This covers the only root for which the public annotation and runtime annotation may differ.

For progress, induct on a closed typing derivation after stripping final subsumption. Constants, lambdas, record values, and packages are values. For (succ), conditionals, applications, and projections, the induction hypotheses step the leftmost active subterm; otherwise canonical forms supply respectively a numeral, Boolean, lambda, or record with the requested label, and the corresponding root fires. A record steps its leftmost nonvalue field. A fixpoint always unfolds. For packing, the payload either steps or the package is a value. For a Self use, the receiver either steps or the Self canonical form supplies (packSelf C with v as S0), so the generalized (UseSelf) root fires even when (S0<:S). These cases exhaust the grammar. Iterating preservation and applying progress at the last term proves that no closed well-typed term reaches a stuck term.

Exercise 16.14.

Extend the type to K+=SelfX.{contents:RefNat,set:NatX,bump:UnitX}. After allocating (r:RefNat), define rk+={contents=r,set=λn:Nat.(λw:Unit.k)(r:=n),bump=λu:Unit.(λw:Unit.k)(r:=succ(!r))},kr+=fix k:K+.packSelf K+ with rk+ as K+. The annotations make the two sequencing lambdas explicit. From (r:RefNat) one gets (!r:Nat), then (succ(!r):Nat), then (r:=succ(!r):Unit); the inner application therefore returns (k:K+). Thus (bump:UnitK+), the payload has the family instantiated at (K+), and T-PackSelf plus T-Fix types (kr+:K+).

Allocation gives ,ref 0σ0,,Σ1={:Nat}. Let (a1=a2=k+), and put r^={contents=,set=λn:Nat.(λw:Unit.k+)(:=n),bump=λu:Unit.(λw:Unit.k+)(:=succ(!))},p+=packSelf K+ with r^ as K+,σ0={0},σ1={1},Uj+(a)=useSelf a as X<:K+,z:RK+(X) in z.j,B2+=λz:K+.!(a2contents),e=B2+(Ubump+(a1)unit). Here RK+ is the family displayed in K+, and aj expands to Uj+(a). The outer function has type (K+Nat); selection gives a1bump:UnitK+, so (e:Nat). The configuration trace is σ0,eσ0,B2+(Ubump+(p+)unit)σ0,B2+((r^.bump)unit)σ0,B2+((λu:Unit.(λw:Unit.k+)(:=succ(!)))unit)σ0,B2+((λw:Unit.k+)(:=succ(!)))σ0,B2+((λw:Unit.k+)(:=succ(0)))σ0,B2+((λw:Unit.k+)(:=1))σ1,B2+((λw:Unit.k+)unit)σ1,B2+k+σ1,B2+p+σ1,!Ucontents+(a2)σ1,!Ucontents+(p+)σ1,!(r^.contents)σ1,!σ1,1. The intermediate types are determined at every root: unfolding k+ yields p+:K+; opening that package exposes r^:RK+(K+); projection yields r^.bump:UnitK+; the two beta roots preserve result type K+; dereference, successor, and assignment have types Nat, Nat, and Unit; the sequencing beta returns k+:K+. The second alias is unfolded and opened in the same way, after which contents projection has type RefNat and the final dereference has type Nat. Before allocation the store typing and store are empty. After allocation, after assignment, and after dereference the store typing remains (Σ1); its unique cell contains respectively (0), (1), and (1), all of type (Nat). The two aliases therefore share the same invariant cell.

If a field is replaced by (RefX), the occurrence of (X) lies beneath an invariant reference. By the chapter’s polarity grammar it is neither positive nor negative, so Self formation fails. A hypothetical covariant comparison would require (RefC<:RefD) from (C<:D), but reference subtyping permits that judgment only when (C=D); read/write covariance would be unsound.

Search the book

Type to search the local edition.