Lectures onType Theory
Chapter 12
Chapter 12Core route

Existential Types, Abstract Data, and Representation Independence

Universal polymorphism lets the client choose a type. Abstract data requires the implementation to choose and hide one while exporting operations that share it.

Here an implementation is a package value: a chosen representation constructor together with operations on it. An abstract interface is the existential type visible outside that package, and a client is a closed term whose input type mentions the interface but not its hidden constructor. Opaque sealing means that typing gives the client no syntax for naming that constructor outside an unpacking.

Existential packages hide a constructor

Kinds let an interface mention a representation constructor with its proper arity. They do not hide that constructor. For example, if a counter is published with state type N, a client may apply suc directly to its initial state. Such a client depends on the representation, even if the library intended the state to be manipulated only through its step and read operations.

The desired interface has the shape X×((XX)×(XN)), but the name X should be local to one implementation and to one use of that implementation. An existential type binds precisely such a name. Its introduction rule seals a witness constructor together with data at the corresponding instance of the interface. Its elimination rule opens the package under a fresh constructor name, but forbids that name from occurring in the result type.

Definition 7.31 — Existential package terms and typing

An existential package seals a constructor witness together with a term at the corresponding instance of an interface. Extend the terms of definition 7.6 by e::=pack[C,e] as u::κ.Aunpack[u,x]=e1 in e2. In a package annotation, u is bound in A. In an unpacking, u and x are bound in e2, but neither is bound in e1. Terms are still identified up to consistent renaming of bound variables.

The typing rules are

ΔC::κΔ,u::κA::TyΔ;Γe:A[C/u]
Δ;Γpack[C,e] as u::κ.A:u::κ.A
T-Pack
Δ;Γe1:u::κ.AΔ,u::κ;Γ,x:Ae2:BΔB::Ty
Δ;Γunpack[u,x]=e1 in e2:B
T-Unpack

The bound variables are chosen fresh for the surrounding contexts. In particular, the last premise of T-Unpack forms B in Δ, not in Δ,u::κ. This is the no-escape premise: the result may depend on what the client computes with x, but its type cannot reveal the hidden constructor u.

Under Curry–Howard, T-Pack is existential introduction: the witness C and payload at A[C/u] correspond to the witness term and proof in -introduction. Rule T-Unpack is existential elimination. Its premise ΔB::Ty is the no-escape condition corresponding to the eigenvariable condition of the first-order -elimination rule in chapter 3.

The core has constructors 0 and suc(e) for N, but no natural-number eliminator or recursor. Thus a client can return an observed number, apply suc to it, or pass it to another interface operation. No typing rule derives a branch or recursive call by inspecting that number. Representation independence quantifies over exactly these well-typed clients.

The no-escape premise is required by preservation, not merely by interface etiquette. If the body were allowed to return type u, then opening pack[N,0] as u::Ty.u with body x would assign the whole unpacking the locally scoped type u. The computation step produces 0:N, but the alleged result type u is free after the binder disappears. The reduct could not even be stated at the source judgment’s type. Forming B in the outer Δ prevents exactly this escape.

Rule T-Pack permits a witness of any kind, not only Ty. Recall the operators Map and P and the term dmap from example 7.7. Define Mapper:=c::TyTy.Mapc. The derivations already constructed give P::TyTy,;dmap:MapP. Therefore one use of T-Pack derives the genuinely higher-kinded package ;pack[P,dmap] as Mapper:Mapper. The witness is the unary constructor P, not a term and not a type of kind Ty.

Exercise 7.6

★☆☆ Derive the displayed Mapper package with T-Pack, including the kinding premise for its existential body. Then replace the witness P by N and identify the first premise that fails. State the kind inferred for N, and explain why it cannot serve as the witness for this unary-constructor package.

A base-kind witness exhibits the no-escape premise in the smallest useful program.

Definition 7.32 — The abstract counter interface

Define Counter:=X::Ty.X×((XX)×(XN)). For a fixed representation X, a payload consists, in order, of an initial state, a step operation, and a read operation. The association of the products is part of the definition.

Here are two implementations. The first stores only the visible count: iN:=0,sN:=λn:N.suc(n),rN:=λn:N.n,qN:=(iN,(sN,rN)), and CN:=pack[N,qN] as Counter. The second stores a count together with a shadow count that advances in lockstep: P:=N×N,iP:=(0,0),sP:=λp:P.(suc(pr1p),suc(pr2p)),rP:=λp:P.pr1p,qP:=(iP,(sP,rP)),CP:=pack[P,qP] as Counter. Every term abbreviation on the right is a term of the core calculus; P is a constructor of kind Ty. This plain P names the pair-state type; it is distinct from the earlier operator P, which maps a type to its diagonal product. For the first payload, the rules for naturals, arrows, and products give ;qN:N×((NN)×(NN)). This is exactly the existential body with N substituted for X. Hence T-Pack gives ;CN:Counter. For the pair-state payload, ;qP:P×((PP)×(PN)),;CP:Counter.

An unpacking client may use all three operations without knowing the state type. For a payload variable z, write iz:=pr1z,sz:=pr1(pr2z),rz:=pr2(pr2z). These are abbreviations, not new term forms. Define tick2:=λc:Counter.unpack[X,z]=c in rz(sz(sziz)). The nested expression is easier to check by naming its projections. Under X::Ty;z:X×((XX)×(XN)) the projection and application rules derive, in order, iz:X,sz:XX,rz:XN,sziz:X,sz(sziz):X,rz(sz(sziz)):N. The final type N is formed before X is introduced. Thus T-Unpack types the unpacking at N, and T-Lam gives ;tick2:CounterN.

Two tempting clients fail for two different, precise reasons. In the body of the unpacking, suc(pr1z) is ill typed: the projection has type X, whereas T-Suc requires N. The variable X does not convert to N, by distinctness of constructor heads. On the other hand, λc:Counter.unpack[X,z]=c in pr1z has a body of type X, but cannot use T-Unpack: its proposed result type is not formed in the outer, empty kind context. The first error tries to use a representation-specific operation; the second tries to return the representation itself.

Exercise 7.7

★★☆ Define a term reseal:CounterCounter that unpacks a counter and immediately packs the same hidden witness and payload again. Give its T-Unpack derivation. Explain why the hidden X does not escape even though X occurs in the inner T-Pack premise.

Substitution through packages

The computation rule for unpacking will perform a constructor substitution and then a term substitution. We therefore record their action on the two new forms before defining evaluation. After renaming binders to satisfy the displayed freshness conditions, (pack[D,e] as v::κ.A)[C/u]=pack[D[C/u],e[C/u]] as v::κ.A[C/u],(unpack[v,x]=e1 in e2)[C/u]=unpack[v,x]=e1[C/u] in e2[C/u], where vu and vfv(C). Term substitution changes no constructor annotation and satisfies (pack[C,e] as u::κ.A)[d/y]=pack[C,e[d/y]] as u::κ.A,(unpack[u,x]=e1 in e2)[d/y]=unpack[u,x]=e1[d/y] in e2[d/y], where xy, xfv(d), and the constructor binder u has first been chosen fresh for every constructor occurring free in d. These are equations of capture-avoiding substitution, not reduction rules.

Lemma 7.33 — Structural lemmas for package terms

The extended typing judgment has the following properties.

  1. If Δ;Γ0,Γ1e:B and y is fresh, then Δ;Γ0,y:D,Γ1e:B, provided ΔD::Ty.

  2. If Δ0,Δ1;Γe:B and u is fresh, then Δ0,u::κ,Δ1;Γe:B.

  3. If Δ;Γ0,y:D,Γ1e:B and Δ;Γ0d:D, then Δ;Γ0,Γ1e[d/y]:B.

  4. If Δ0,u::κ,Δ1;Γe:B and Δ0C::κ, then Δ0,Δ1;Γ[C/u]e[C/u]:B[C/u].

Proof of Lemma 7.33 — Structural lemmas for package terms

Proof. Term-context weakening, constructor-context weakening, and term substitution are inductions on the typing derivation; constructor substitution is an induction on the same derivation together with kinding substitution. The package-free cases are the four interfaces of lemma 11.34. For a T-Conv premise, weakening uses lemma 11.33. Constructor substitution instead uses lemma 7.27; kinding substitution alone would not transport its equality premise. We give both new package-rule cases, because each contains a constructor binder.

The constructor-substitution induction uses the split context Δ0,u::κ,Δ1. It carries this split unchanged through every premise. This is essential beneath T-TLam, K-All, and the two package binders: a new declaration is appended to Δ1, rather than exchanged across u. If that declaration mentions u, exchanging it to the left of u would make its annotation ill formed because u would no longer occur in the prefix where the annotation is checked. For T-Pack, rename the package binder v away from u and C. Suppressing the common split, its premises have the form Δ0,u::κ,Δ1D::κ,Δ0,u::κ,Δ1,v::κA::Ty,Δ0,u::κ,Δ1;Γe:A[D/v]. Kinding substitution and the induction hypothesis give the corresponding premises for D[C/u], A[C/u], and e[C/u]. The substitution-composition law lemma 7.17(1) gives the payload-type equation (A[D/v])[C/u]=A[C/u][D[C/u]/v]. Rule T-Pack now reconstructs the substituted package.

For constructor substitution in T-Unpack, rename its hidden binder v away from u and C. Apply the induction hypothesis to the scrutinee. In the body premise use the split-context induction hypothesis with Δ1,v::κ as the suffix. Freshness makes substitution pass through v, giving Δ0,Δ1,v::κ;Γ[C/u],x:A[C/u]e2[C/u]:B[C/u]. Kinding substitution forms B[C/u] in Δ0,Δ1, so T-Unpack reconstructs the conclusion.

For term substitution, T-Pack applies the induction hypothesis only to the payload. In T-Unpack, rename its term binder x away from y and d. Apply the induction hypothesis to the scrutinee and to the body. For the latter, first apply constructor-context weakening to the derivation of d, then term-context weakening through the declaration x:A; use the body induction hypothesis with that declaration in the trailing context. Reapply T-Unpack. Both weakening inductions repeat these two reconstructions without a replacement step. Thus all four inductions cover the new grammar. ◻

Exercise 7.8

★★☆ Suppose Δ;Γ,y:Dunpack[u,x]=p in e:BandΔ;Γd:D, with xy and xfv(d). Invert the unpacking derivation, write the two premises obtained after substituting d for y, and rebuild the final T-Unpack rule. State where weakening d under u::κ is used.

Call-by-value opening and safety

A package hides a constructor; it should not hide an unfinished computation. Call-by-value evaluation therefore evaluates the payload before regarding the package as a value. It also evaluates the scrutinee of an unpacking before opening it.

Definition 7.34 — Call-by-value evaluation with packages

Values and evaluation contexts for the full term grammar are v::=λx:A.eΛu::κ.e(v1,v2)0suc(v)pack[C,v] as u::κ.A,E::=[]EevEE[C](E,e)(v,E)priEsuc(E)pack[C,E] as u::κ.Aunpack[u,x]=E in e. The root contractions are (λx:A.e)ve[v/x],(Λu::κ.e)[C]e[C/u],pri(v1,v2)vi,unpack[u,x]=(pack[C,v] as a::κ.A0) in ee[C/u][v/x]. If ee, then EeEe. There is no reduction beneath a term abstraction, a type abstraction, or the body of an unopened unpacking. Write for the reflexive–transitive closure.

Lemma 12.5 — Determinism of package evaluation

If ee1 and ee2, then e1=e2.

Proof of Lemma 12.5 — Determinism of package evaluation

Proof. First, no value takes a call-by-value step. This is an induction on the value grammar: no root rule has a value as its source, and the evaluation contexts do not descend beneath a value former. Induct on the term. The left-to-right evaluation-context grammar selects at most one immediate subterm: it advances past a position only after that position is a value. Once all required subterms are values, their outer constructor selects at most one of the four root rules. Those roots have disjoint outer forms, and each has one right-hand side. The induction hypothesis gives a unique step in the selected proper subterm. ◻

Call-by-value reduction answers how a program is run. Representation independence needs a larger equality: two terms are beta-convertible even when the decisive contraction lies beneath a binder or in a branch that call-by-value evaluation does not enter.

Definition 7.35 — Compatible term beta conversion

Compatible term beta reduction β, called an e-beta step when the syntactic level must be explicit, is the least relation containing the four roots (λx:A.e)dβe[d/x],(Λu::κ.e)[C]βe[C/u],pri(e1,e2)βei,unpack[u,x]=(pack[C,d] as a::κ.A0) in eβe[C/u][d/x], and compatible with every term constructor. Thus a step may occur beneath λ or Λ, in either child of an application or pair, inside a successor or package, and in either the scrutinee or body of an unpacking. None of the four roots requires its argument or payload to be a value.

Write =β for the reflexive, symmetric, transitive closure and β for the reflexive–transitive closure. Every call-by-value step of definition 7.34 is a compatible beta step, but not conversely.

The operational relations have the following distinct domains and closures: eecall-by-value; no reduction under binderseβe(e-β)compatible term beta at every term formereβe(e-β)reflexive--transitive term betae=βesymmetric, transitive term-beta conversionAβB(A-β)compatible constructor betaABformed constructor conversion The role macros β and β deliberately share the same printed beta arrow. Their operand sorts and the e-beta and A-beta tags distinguish directed reductions at the term and constructor levels. The relation =β is reserved for term conversion, while compares formed constructors.

Proposition 7.36 — Subject reduction for compatible term beta

If Δ;Γe:A and eβe, then Δ;Γe:A.

Proof of Proposition 7.36 — Subject reduction for compatible term beta

Proof. Peel the final chain of T-Conv uses from the typing of the whole expression. Induct on the compatible step at the remaining syntax-directed result type, then restore the conversion chain. The root cases use inversion through any conversions on their operator premises.

For term beta, suppose the application rule expects an operator of type DA. Strip conversions from the typing of λx:C.e0. Its introduction rule gives Δ;Γ,x:Ce0:B, while the stripped conversion gives CBDA. Arrow-head injectivity yields CD and BA. Convert the operand from D to C, apply term substitution, and finally convert the result from B to A.

For type beta, suppose type application expects an operator of type u::κ.A. Stripping conversions from the typing of Λv::κ.e0, followed by alpha-renaming, gives Δ,u::κ;Γe0:B and u::κ.Bu::κ.A. Universal-head injectivity gives BA in the extended constructor context. Constructor substitution types e0[C/u] at B[C/u]; equality substitution and T-Conv change that result to A[C/u].

For projection beta, suppose the projection rule expects D1×D2. Strip conversions from the typing of the displayed pair. Product-head injectivity gives CiDi for i{1,2}. The selected pair premise has type Ci, and one use of T-Conv gives the required Di.

For the unpacking root, invert T-Unpack. Write its scrutinee type as u::κ.A1 and its body premise as Δ,u::κ;Γ,x:A1e0:B,ΔB::Ty. Peeling final conversions from the package typing and using existential-head injectivity gives, after alpha-renaming, ΔC::κ,Δ;Γd:A0[C/u],Δ,u::κA0A1::Ty. Equality substitution and T-Conv type d at A1[C/u]. Constructor substitution types e0[C/u] at B in Γ,x:A1[C/u]; because u is absent from the well-formed outer Γ and B, their substitutions by C are literally Γ and B. Term substitution now types e0[C/u][d/x] at B, as required.

For a step in a proper subterm, apply the induction hypothesis to its typing premise and rebuild the same typing rule. Beneath λ, Λ, or the body of an unpacking, the induction hypothesis is applied in the corresponding extended context. These cases cover every term constructor. ◻

The quotient by =β is taken separately at each fixed type. The proposition ensures that every forward reduction used in a conversion calculation remains at that type.

The existential type displayed in the first premise of T-Unpack need not use the same bound-variable name or the same spelling of its body as the type synthesized for the scrutinee. Typing shows that the two existential types are convertible; the preservation proof above used injectivity of the existential head to reconcile their bodies.

Lemma 7.37 — Canonical package values

Let v be a value and suppose Δ;v:u::κ.A. Then, after renaming a bound constructor, there are C, w, and A0 such that v=pack[C,w] as u::κ.A0, w is a value, ΔC::κ,Δ;w:A0[C/u],Δ,u::κA0A::Ty. For each other type whose outer head is a constructor former, stripping final uses of T-Conv selects its unique value-introduction rule: an arrow selects T-Lam, a universal selects T-TLam, a product selects T-Pair, and N selects T-Zero or T-Suc. The selected rule supplies the corresponding typing premises, while head injectivity converts each component of its result type to the component demanded by the assumed type. A neutral constructor head is excluded in every later use of this clause: the typing rule being inverted demands one of the displayed former heads.

Proof of Lemma 7.37 — Canonical package values

Proof. Peel off all final uses of T-Conv, composing their equalities. The last remaining rule is determined by the value form. Its syntax-directed result type has outer head , , ×, N, or , respectively. Distinct heads do not convert (corollary 7.26), so only the form with the requested head can occur.

In the existential case the remaining rule is T-Pack, whose premises give ΔC::κ and the required payload typing. Its result type a::κ.A0 converts to u::κ.A. Existential-head injectivity gives κ=κ and, after opening both binders with one fresh u, A0A. Alpha-renaming the annotation produces the required package. For the four other heads, replace T-Pack respectively by T-Lam, T-TLam, T-Pair, and T-Zero/T-Suc; head disjointness rules out every other value form, and head injectivity gives the component equalities described in the statement. ◻

Theorem 7.38 — Preservation

If Δ;Γe:A and ee, then the reduct has the same type: Δ;Γe:A.

Proof of Theorem 7.38 — Preservation

Proof. Every call-by-value step is a compatible term-beta step by definition 7.35. Apply proposition 7.36. Notice that this argument also covers a reduction in an evaluation context: compatible closure already contains every call-by-value context. ◻

Theorem 7.39 — Progress

If Δ;e:A, then e is a value or there is an e with ee.

Proof of Theorem 7.39 — Progress

Proof. Induct on the typing derivation. A variable case is impossible, and a final T-Conv uses the induction hypothesis of its premise. Term and type abstractions are values. For an application, first apply the induction hypothesis to the operator and then to the operand. If both are values, the arrow clause of canonical forms gives an operator λx:B.s, so term beta applies. For type application, a universal-typed value has form Λu::κ.s, so type beta applies. For a pair, evaluate its components from left to right; it is a value when both are values. For a projection, evaluate its argument; a value of product type is a pair, so projection beta applies. The zero term is a value, and a successor evaluates its argument or is a value.

For a package, apply the induction hypothesis to its payload. A payload step occurs in the package context; if the payload is a value, the package is a value by the value grammar. For unpack[u,x]=e1 in e2, apply the induction hypothesis to e1. A step occurs in the unpacking context. If e1 is a value, lemma 7.37 proves that it has the form pack[C,d] as u::κ.A with value payload d, so the unpacking contraction applies. These cases exhaust the typing rules. ◻

Corollary 7.40 — Safety

If Δ;e:A and ee, then e is a value or it takes another step. In particular, a closed well-typed package client—closed in term variables, though Δ may contain constructor variables—cannot become stuck because it chose an operation incompatible with the hidden witness.

Proof of Corollary 7.40 — Safety

Proof. Repeated preservation types e at A; progress gives the alternative. ◻

An open unpacking can be blocked, as an eliminator should be. In the context y:Counter the term unpack[X,z]=y in (pr2(pr2z))(pr1z) is well typed at N but takes no step: its scrutinee is the variable y, not a package value. This does not contradict progress, whose term context is empty.

Running the two counters

The client reduction realizes its typing derivation as follows. After the outer term beta step, opening CN substitutes N for X and qN for z. The three projection abbreviations satisfy iqNiN,sqNsN,rqNrN. For example, sqN=pr1(pr2qN) first reduces its inner projection to (sN,rN) and then reduces to sN. The client calculation is therefore tick2CNβunpack[X,z]=CN in unpackβrqN(sqN(sqNiqN))projectionβrN(sN(sNiN))βrN(sN(suc(0)))βrN(suc(suc(0)))βsuc(suc(0)).

For the pair implementation, put p0:=(0,0),p1:=(suc(0),suc(0)),p2:=(suc(suc(0)),suc(suc(0))). One step operation performs the following complete calculation: sPp0β(suc(pr1p0),suc(pr2p0))firstprojection(suc(0),suc(pr2p0))secondprojection(suc(0),suc(0))=p1. The same three contractions with p1 in place of p0 yield sPp1p2. Consequently, tick2CPβunpack[X,z]=CP in unpackβrqP(sqP(sqPiqP))projectionβrP(sP(sPp0))sPp0andsPp1rPp2βpr1p2projectionβsuc(suc(0)). Thus this particular client obtains the same observation from the two packages, although their state values have different types.

This agreement is a calculation, not a representation-independence theorem. Existential typing enforces opacity; by itself it does not assert that any two packages of the same existential type behave alike. To see the difference, replace sN by sN+2:=λn:N.suc(suc(n)) and seal the resulting payload at Counter. It has the same existential type as CN, but the calculation above now ends at suc(suc(suc(suc(0)))). A relation between witnesses and operations is the additional premise needed to prove representation independence.

Exercise 7.9

★★☆ Define the fast counter CN+2:Counter using sN+2. Display every call-by-value contraction in tick2CN+2 and prove that its result is the numeral four. Conclude that this one well-typed client distinguishes CN from CN+2: deterministic call-by-value evaluation returns the distinct numeral values two and four.

Relations at higher kinds

The relation environment of chapter 6 assigns to a type variable X two closed types and a relation between their closed terms. That assignment is insufficient for c::TyTy. The expression cA is a type, so its interpretation must be a relation; but the relation may depend on the relation chosen for A. Thus c must carry an action: it sends related arguments to a relation between the two results. This is the one new idea. The clauses for constructor abstraction and application will merely form and apply such actions.

Constructor-beta endpoints require the following indexing convention.

Convention 12.12 — Constructor-beta endpoints

For a closed type A, let Tm(A):={e;e:A}/=β, where =β is exactly the compatible term-beta conversion of definition 7.35, not the directed call-by-value evaluation relation of definition 7.34. It closes all four root contractions under every term context. If A and A satisfy the corresponding closed constructor-conversion judgment, the two sets Tm(A) and Tm(A) have the same terms and beta-classes; T-Conv changes only their assigned type. The index A of Tm(A) denotes its constructor-conversion class. The same convention applies to closed constructors of every kind. It makes (λu::κ.A)C and A[C/u] literally the same endpoint for the relational definitions below. Constructor conversion is always written as a judgment with ; =β below therefore always relates terms.

For relations R:A0A1 and S:B0B1, recall arrow lifting and define product lifting by [f](RS)[g][a]R[b][fa]S[gb],[p](R×S)[q][pr1p]R[pr1q] and [pr2p]S[pr2q]. In the first line the implication is required for every [a]R[b]. Both definitions are independent of representatives because beta conversion is compatible with application and projection. Put EqN:={([m],[m])[m]Tm(N)}.

Definition 7.41 — Relations at a kind

For closed A0,A1::κ, define the collection Relκ(A0,A1) by induction on κ: RelTy(A0,A1):=P(Tm(A0)×Tm(A1)). An element ΦRelκκ(F0,F1) is an operation which, for every closed C0,C1::κ and every RRelκ(C0,C1), returns Φ(C0,C1,R)Relκ(F0C0,F1C1). A relational object of kind κ is a triple Q=(Q0,Q1,QR) with QRRelκ(Q0,Q1).

Lemma 12.14 — Constructor-convertible relation endpoints

If AiAi::κ(i=0,1), then Relκ(A0,A1)=Relκ(A0,A1).

Proof of Lemma 12.14 — Constructor-convertible relation endpoints

Proof. Induct on κ. At Ty, Tm(Ai)=Tm(Ai) by T-Conv, so the two power sets are literally equal. At κ0κ1, the same operations occur on both sides: for each pair C0,C1 and input relation R, constructor congruence proves AiCiAiCi, and the induction hypothesis at κ1 gives equality of the required output collections. ◻

The following notation distinguishes beta-classes, type application, and substitution: [e]outer beta-equivalence classe[C]object-language type applicatione[C/u]single constructor substitutione[ρi][γi]endpoint constructor and term substitutionspack[C,e], unpack[u,x]package syntax Thus the outer brackets in [e[ρi][γi]] form one beta-class after the two substitutions.

These collections are meta-level sets for the same reason as in definition 6.1: closed constructors and terms are countable syntax, the base clause uses a power set, and the arrow clause is a set of operations between already constructed sets at proper subkinds.

The naive arrow-kind attempt assigns only a bare set of pairs of closed constructor beta-classes at endpoints F0 and F1, without an operation describing how that set acts on a related argument. At an application it then asks for [[cA]]ρfrom onlyρ(c)=(F0,F1,R), but R contains no relation between F0C0 and F1C1—in particular none at the concrete instance MapP. The repair is an action Φ that maps each related input triple to a well-indexed output relation. No functor law is imposed: the abstraction theorem needs only that every related input is sent to a well-indexed output relation. In particular, we do not require Φ to preserve identity relations or composition of relations; “functor” here refers to that categorical preservation condition. Parameterized ML modules use the same word for a different construction, and neither use concerns modules over a ring.

Definition 7.42 — Higher-kinded environments

Let Δ=u1::κ1,,un::κn. A relation environment ρ assigns to each ui a relational object ρ(ui)=(Ci0,Ci1,Ri)of kind κi. Its endpoint substitutions are ρ0(ui)=Ci0 and ρ1(ui)=Ci1. Following definition 6.4, we write ρ:ρ0ρ1 over Δ.

As in chapter 6, ϵ denotes the unique relation environment over the empty constructor context; it is not an empty relation.

The relational interpretation of a constructor A will be written [[A]]ρ. If ΔA::κ, it must belong to Relκ(A[ρ0],A[ρ1]). The following definition gives all its clauses. In the binder clauses, the bound variable is first renamed away from the finite ranges of the endpoint substitutions.

Definition 7.43 — Relational interpretation of constructors

For variables and the primitive type, [[u]]ρ:=ρ(u)R,[[N]]ρ:=EqN. The type-former clauses are [[AB]]ρ:=[[A]]ρ[[B]]ρ,[[A×B]]ρ:=[[A]]ρ×[[B]]ρ. Constructor abstraction creates an action, and constructor application uses one: [[λu::κ.A]]ρ(C0,C1,R):=[[A]]ρ[u(C0,C1,R)],[[AB]]ρ:=[[A]]ρ(B[ρ0],B[ρ1],[[B]]ρ). For beta classes [p]Tm((u::κ.A)[ρ0]) and [q]Tm((u::κ.A)[ρ1]), define [p][[u::κ.A]]ρ[q] to mean that, for every relational object Q=(C0,C1,R) of kind κ, [p[C0]][[A]]ρ[uQ][q[C1]]. First read the existential clause in its literal-annotation special case. Packages pack[Ci,ai] as u::κ.A[ρi] are related when some R connects C0 and C1 and the payloads are related at [[A]]ρ[u(C0,C1,R)]. The longer clause below allows the written annotations to be any constructor-convertible presentations of those endpoint types; its two equality lines record exactly that extra flexibility. For beta classes [p]Tm((u::κ.A)[ρ0]) and [q]Tm((u::κ.A)[ρ1]), define [p][[u::κ.A]]ρ[q] to mean that there are a relational object Q=(C0,C1,R) of kind κ, closed payloads a0,a1, and package bodies D0,D1 whose only possible free constructors are their displayed binders, such that p=βpack[C0,a0] as v0::κ.D0,q=βpack[C1,a1] as v1::κ.D1,v0::κ.D0u::κ.A[ρ0]::Ty,v1::κ.D1u::κ.A[ρ1]::Ty,[a0][[A]]ρ[uQ][a1]. Thus a package annotation may be any constructor-convertible presentation of the corresponding endpoint type. Existential-head injectivity and T-Conv identify each payload with the endpoint expected by the last line. This flexibility is necessary because package annotations are part of term syntax, whereas compatible term beta does not rewrite constructors inside annotations.

The existential clause is asymmetric with the universal clause for a reason. A polymorphic term must work for every relational object. A package hides one pair of witnesses, so related packages need exhibit one relation between those witnesses. Requiring every relation would reject ordinary abstract data types; requiring no witness relation would leave the payloads unconnected.

Example 7.44 — The diagonal constructor acts on relations

Recall P=λu::Ty.u×u. For every RRelTy(C0,C1), [[P]]ϵ(C0,C1,R)=abstraction[[u×u]]u(C0,C1,R)=productR×R. Thus the abstract action demanded by definition 7.41 performs the familiar operation: it relates pairs componentwise. Constructor application recovers the same calculation, [[PN]]ϵ=EqN×EqN.

Exercise 7.10

★★☆ Let RRelTy(A0,A1). Unfold the action, application, and product clauses in that order to prove [[CompPP]]ϵ(A0,A1,R)=(R×R)×(R×R). State the two endpoint types of the relation on the right. Then let D:=λa::Ty.aa and prove [[D]]ϵ(A0,A1,R)=RR. Explain why this second action genuinely depends on the supplied relation R, rather than merely relating two fixed endpoint constructors.

Three lemmas are needed before the term induction. The first prevents silent index changes; the second is the constructor-level analogue of the type-substitution lemma from chapter 6; the third handles rule T-Conv.

Lemma 7.45 — Endpoints and irrelevant constructor variables

Let ΔA::κ and ρ:ρ0ρ1 over Δ.

  1. [[A]]ρ belongs to Relκ(A[ρ0],A[ρ1]).

  2. If ρ and ρ agree on every constructor variable free in A, then [[A]]ρ=[[A]]ρ.

Proof of Lemma 7.45 — Endpoints and irrelevant constructor variables

Proof. Prove both claims simultaneously by induction on the kinding derivation. The variable, N, arrow, and product cases follow directly from their defining clauses. For λu::κ0.A0, take an arbitrary relational object Q of kind κ0. The induction hypothesis under ρ[uQ] indexes the body relation by A0[ρ0,uQ0]andA0[ρ1,uQ1]. These are beta-convertible to the applications of the two endpoint abstractions to Q0,Q1, so the action has the required arrow-kind index. Agreement away from the free variables of the abstraction remains agreement after extending both environments by the same Q.

For A1A2, the induction hypothesis for A1 gives an action and the one for A2 gives an admissible relational argument. Applying the former to the latter gives exactly the required endpoint applications.

For u::κ0.A0, type application at Qi has endpoint type A0[ρi,uQi]; the body induction hypothesis therefore types every relation required by the universal clause.

For u::κ0.A0, suppose [p] and [q] satisfy the existential clause, witnessed by Q=(C0,C1,R), payloads a0,a1, and annotations vi::κ0.Di. The body induction hypothesis says that [a0][[A0]]ρ[uQ][a1] is indexed by A0[ρ0,uC0]andA0[ρ1,uC1]. Hence T-Pack, using the endpoint body A0[ρi], types the two literal packages at u::κ0.A0[ρi]. The annotation equalities in the definition, existential-head injectivity from corollary 7.26, and T-Conv give the same endpoint typing for the displayed Di annotations. The domain restriction in the defining clause already places [p] and [q] in the two corresponding Tm endpoints; the two term-beta equalities choose the displayed package representatives of those classes. Thus all five conditions of the defining clause contribute: two expose the packages, two align their annotations, and the last relates well-typed payloads.

In both binder cases, irrelevance follows by extending the two environments with the same arbitrary Q and applying the body hypothesis. These cases exhaust the constructor grammar. ◻

Lemma 7.46 — Relational constructor substitution

Suppose Δ0,u::κ,Δ1A::κ,Δ0B::κ, and let ρ be a relation environment over Δ0,Δ1. Define the relational object QB:=(B[ρ0],B[ρ1],[[B]]ρ). Then, under the constructor-beta identification of endpoints, [[A[B/u]]]ρ=[[A]]ρ[uQB].

Proof of Lemma 7.46 — Relational constructor substitution

Proof. Induct on the kinding derivation of A. At the variable u, both sides are [[B]]ρ; another variable and N are unchanged. For arrows and products, apply the induction hypothesis at both components and form the corresponding relational lifting.

For application A1A2, the left side applies the interpreted action of A1[B/u] to the interpreted relational object of A2[B/u]. The two induction hypotheses replace these by the interpretation of A1 and the relational object of A2 under ρ[uQB], which is the right-hand application clause.

For a binder, first rename its variable v so that vu and vfv(B). In the constructor-abstraction case, apply both sides to an arbitrary relational object Q for v. The body induction hypothesis applies under ρ[vQ]. Irrelevance and vfv(B) give equality of relational objects (B[ρ0,vQ0],B[ρ1,vQ1],[[B]]ρ[vQ])=QB. The two environment extensions commute, so the resulting body relations are equal. This proves equality of the actions. For a universal, fix an arbitrary Q; the body induction hypothesis equates the two relations applied to p[Q0] and q[Q1], so the universally quantified membership conditions coincide. For an existential, keep its witnessing Q, payloads, and written annotations fixed; the body induction hypothesis equates the two payload conditions. Its endpoint calculation (A0[B/u])[ρi]=A0[ρi,uB[ρi]](i=0,1) is the simultaneous/single substitution-composition equation. It makes the left clause’s annotation condition v.Diw.A0[B/u][ρi] identical to the right clause’s condition v.Diw.A0[ρi,uB[ρi]] after freshening v,w. The payload condition is the body equality just proved. ◻

Lemma 7.47 — Invariance under constructor equality

If ΔAB::κ, then for every relation environment ρ over Δ, [[A]]ρ=[[B]]ρ after the canonical identification of their constructor-beta-convertible endpoints.

Proof of Lemma 7.47 — Invariance under constructor equality

Proof. At arrow kind, equality of actions is ordinary function extensionality in the fixed ZF metatheory. Write Φ(Q) for Φ(C0,C1,R) when Q=(C0,C1,R). Then Φ=ΨΦ(Q)=Ψ(Q) for every relational object Q. First prove invariance under one constructor reduction. Induct on the constructor reduction (an A-beta step). At the root redex, [[(λu::κ0.A0)C]]ρ=[[A0]]ρ[u(C[ρ0],C[ρ1],[[C]]ρ)]=[[A0[C/u]]]ρ by lemma 7.46. A congruence step uses the induction hypothesis in the corresponding compositional clause of definition 7.43; at arrow kind apply the displayed extensionality principle and the body induction hypothesis to each arbitrary relational object.

By the common-reduction characterization of constructor equality, theorem 7.25, A and B reduce to one constructor C. Repeated one-step invariance gives [[A]]ρ=[[C]]ρ=[[B]]ρ. At an existential endpoint, keep the witness, payloads, and payload relation fixed. If a written endpoint annotation is Ei, its old and new side conditions are EiA[ρi],EiB[ρi]. Applying constructor-equality substitution successively to the closed images of ρi gives A[ρi]B[ρi]. Transitivity converts the old condition to the new; symmetry and transitivity give the reverse implication. Thus the two existential membership conditions are equivalent. ◻

The abstraction theorem with packages

For existential elimination, the two endpoint substitutions and the closing term substitutions must be related. Let ΔΓ ctx and let ρ:ρ0ρ1 over Δ. Endpoint substitutions γi close the term variables when ;γi(x):A[ρi]for each x:AΓ. They are related, written γ0[[Γ]]ργ1, when [γ0(x)][[A]]ρ[γ1(x)]for each x:AΓ.

Theorem 7.48 — Abstraction for F_ω with existentials

Suppose Δ;Γe:A,ρ:ρ0ρ1 over Δ,γ0[[Γ]]ργ1. Then [e[ρ0][γ0]][[A]]ρ[e[ρ1][γ1]].

Proof of Theorem 7.48 — Abstraction for F_ω with existentials

Proof. Induct on the typing derivation with the displayed relational judgment as the induction property. At a type binder, endpoint irrelevance preserves the relation on Γ. At constructor application, relational constructor substitution rewrites the interpreted result type. At T-Conv, conversion invariance rewrites the endpoint relations. Capture-avoiding substitution commutes with the closing substitutions in every term-binder case.

Variable. The conclusion is the corresponding component of γ0[[Γ]]ργ1.

Arrow introduction. For a last premise Δ;Γ,x:Be0:C, take arbitrary closed a0,a1 with [a0][[B]]ρ[a1]. The substitutions γi[xai] are related at the extended context, so the induction hypothesis relates the two substituted bodies at [[C]]ρ. At endpoint i, term beta gives (λx:B[ρi].e0[ρi][γi])ai=βe0[ρi][γi[xai]]. Thus the two applications satisfy the arrow-lifting clause.

Arrow elimination. The operator induction hypothesis lies in [[B]]ρ[[C]]ρ, and the argument hypothesis lies in [[B]]ρ. Apply the former to the latter.

Type abstraction (T-TLam). Suppose the premise is Δ,u::κ;Γe0:B, with u fresh for Γ. To establish the universal relation, take an arbitrary relational object Q=(C0,C1,R) of kind κ and extend ρ by uQ. Since u is absent from every declaration in Γ, endpoint irrelevance says that the original γ0,γ1 remain related. The induction hypothesis gives the body relation. The type-application beta law identifies its endpoints with ((Λu::κ.e0)[ρi][γi])[Ci]. As Q was arbitrary, this is the universal clause.

Type application (T-TApp). Suppose the operator has type u::κ.B and the argument constructor is C. Its interpretation forms the relational object QC=(C[ρ0],C[ρ1],[[C]]ρ). The operator induction hypothesis is universal over relational objects. Instantiating it at QC gives the body relation [[B]]ρ[uQC], which equals [[B[C/u]]]ρ by lemma 7.46.

Product introduction. The two induction hypotheses relate the components. The projection beta laws therefore put the two pairs in [[A1]]ρ×[[A2]]ρ.

Product elimination. The premise induction hypothesis is a pair in the product lifting. Its ith conjunct is exactly the required relation between the ith projections.

Natural numbers. The two occurrences of 0 determine the same beta-class, hence are in EqN. In the successor case the induction hypothesis says the two arguments are beta-equal; compatible conversion makes their successors beta-equal as well.

Conversion. The premise induction hypothesis gives the relation interpreted at the premise type. By lemma 7.47, the conclusion type has the same interpreted relation after the endpoint conversions performed by T-Conv.

Existential introduction. Suppose the package witness is C::κ and its payload premise has type B[C/u]. Form QC as in the constructor-application case. The payload induction hypothesis lies in [[B[C/u]]]ρ=[[B]]ρ[uQC], where the equality is lemma 7.46. The two substituted package terms have witnesses C[ρ0] and C[ρ1], with payloads related by this equation. Hence they are related at [[u::κ.B]]ρ.

Existential elimination. Write the last two premises as Δ;Γe1:u::κ.B,Δ,u::κ;Γ,x:Be2:D, where u is absent from Γ and D. By the first induction hypothesis, the two closed instances of e1 are related existential packages. Unfolding the existential clause gives a relational object Q=(C0,C1,R) and payloads a0,a1 such that the endpoint instances of e1 are beta-equal to the corresponding packages and [a0][[B]]ρ[uQ][a1]. Endpoint irrelevance keeps the substitutions γi related at Γ after extending ρ by Q. Extending them further by xai therefore satisfies the hypotheses for the body induction hypothesis, which gives [e2[ρ0,uC0][γ0,xa0]][[D]]ρ[uQ][e2[ρ1,uC1][γ1,xa1]]. Since u is not free in D, irrelevance changes the middle relation to [[D]]ρ. Finally, compatible conversion replaces each endpoint instance of e1 by its package, and the package computation rule gives unpack [u,x]=(pack[Ci,ai]) in e2=βe2[Ci/u][ai/x]. This is exactly the pair of terms in the theorem’s conclusion. ◻

Corollary 7.49 — Self-parametricity

If ;e:A, then [e][[A]]ϵ[e].

Proof of Corollary 7.49 — Self-parametricity

Proof. Use theorem 7.48 with empty constructor and term environments. ◻

Representation independence for an existential counter

Representation independence means that clients cannot distinguish implementations related at their abstract interface. The Church-encoded counter of section 6.6 quantified the representation type in every client. The existential type Counter of definition 7.32 moves that quantifier to the implementation boundary. The preceding calculations compared its N implementation CN with its shadow-count implementation CP for one client. The logical relation proves that the two instances of every closed natural-number client are beta-convertible.

The state constructors are N and P=N×N. Relate their closed terms by [n]R[p]p=β(n,n). This is a relation in RelTy(N,P). It records the lockstep invariant used by the second implementation: both stored coordinates are the visible count.

Theorem 7.51 — Existential counter representation independence

For every closed ;k:CounterN, kCN=βkCP.

Proof of Theorem 7.51 — Existential counter representation independence

Proof. Apply corollary 12.23 to lemma 7.50. ◻

No identity-extension theorem is hidden in this proof. Such a theorem would identify the relation assigned to a closed type with equality at that type; section 6.4 showed why beta equality does not validate that principle in general. Equality of the observations follows only because the result constructor is the primitive N and its relational clause was defined to be EqN. Replacing N by an arbitrary closed type O would yield related results at [[O]]ϵ; it would not by itself yield beta equality. The calculations for tick2 above are the visible instance of the theorem. The theorem now establishes their conclusion for every well-typed closed natural-number client.

The result is stated in beta equality, not silently in evaluation equivalence. This chapter has not established term-level normalization and confluence for the full Fω package calculus. If those standard metatheorems are supplied, any call-by-value evaluations kCNm and kCPn are compatible beta reductions to normal forms; confluence and the theorem force m=n. Without that additional metatheory, the proved observation is exactly the displayed beta equality.

Exercise 7.11 — *

★★☆ Keep the first representation N and the second representation P=N×N, but define [n]R+[p]p=β(n,suc(n)). Take iP+=(0,suc(0)), sP+=λp:P.(suc(pr1p),suc(pr2p)), and rP+=λp:P.pr1p. Verify, in order, the initial-state, step, and observer relation hypotheses. Package this implementation as CP+:Counter and prove that every closed k:CounterN satisfies kCN=βkCP+. Your proof must identify the single use of self-parametricity and must not appeal to identity extension.

Sources.

The package rules, escape condition, and representation independence pattern follow the development of abstract types in [Har16]. Relation environments and the abstraction argument originate with Reynolds; the relation interpretation and abstraction theorem occur on pp. 515–517, and the representation semantics and theorem on pp. 518–519 of [Rey83]. The arrow-kind action, constructor substitution, and conversion proofs above are the extension needed for this chapter’s Fω signature. Those steps were proved locally rather than attributed to the System F statement in the cited paper.

Optional route.

A higher-rank client hidden inside existential elimination

The native unpacking rule binds an abstract constructor directly. There is another way to expose the same client boundary: ask a package to accept a polymorphic continuation. This does not replace the native calculus under call-by-value evaluation, but it shows exactly where a higher-rank argument enters the package encoding.

Suppose Δ,u::κA::Ty. With R fresh, define the Church package type Someκ(u.A):=R::Ty.(u::κ.AR)R. If ΔC::κ and Δ;Γv:A[C/u], define cpack[C,v]:=ΛR::Ty.λk:u::κ.AR.k[C]v. Rule T-TApp gives k[C]:A[C/u]R; its domain is well formed by constructor substitution. Together with Δ;Γv:A[C/u], rule T-App gives k[C]v:R. Repeated uses of T-TLam and T-Lam derive Δ;Γcpack[C,v]:Someκ(u.A).

Here rank counts how deeply a universal quantifier occurs to the left of arrows. A prenex type has all of its quantifiers at the front and has rank one; placing such a polymorphic type in an arrow domain raises the rank to two. A monomorphic type in this paragraph contains no universal quantifier.

Now suppose ΔΓ ctx,Δ;Γp:Someκ(u.A),Δ,u::κ;Γ,x:Ae:B,ΔB::Ty. Formation of Γ before u makes the constructor binder fresh for the surrounding term context; formation of B before u is the escape condition. These are the two corresponding side conditions of native unpacking. The continuation Λu::κ.λx:A.e:u::κ.AB may be passed to a Church package p by copenB(p;u,x.e):=p[B](Λu::κ.λx:A.e). Here a polymorphic term appears in the domain of the package’s continuation arrow. When A and B contain no nested universal types, the continuation u::κ.AB has rank one, while the enclosing arrow (u::κ.AB)B has rank two. If A or B already has higher-rank structure, the rank may be larger. Higher kinds and higher rank are independent notions: κ may be Ty; the rank rise comes from placing the polymorphic continuation to the left of an arrow.

For a value payload, the entire administrative calculation is visible: copenB(cpack[C,v];u,x.e)=(ΛR.λk.k[C]v)[B](Λu.λx.e)typeβ(λk.k[C]v)(Λu.λx.e)β(Λu.λx.e)[C]vtypeβ(λx.e[C/u])vβe[C/u][v/x]. Constructor and term annotations have only been suppressed on the middle three lines. The result is the native unpacking contraction.

This calculation does not prove a general call-by-value encoding theorem. A native package evaluates its payload before becoming a value; a Church package begins with ΛR and can hide an unfinished payload beneath that value. Thus we have proved typing and the value-payload calculation, not operational equivalence for arbitrary effects or divergence. Church checking remains syntax directed because every binder is annotated. The undecidability of Curry-style System F inference from theorem 5.23 is unaffected.

Exercise 7.13

★★☆ Write the Church encoding of CN at SomeTy(X.X×((XX)×(XN))). Use the displayed body of tick2 as the continuation, derive the continuation’s polymorphic type, and reproduce all four contractions above. Identify the polymorphic-continuation subterm, reproduce its type, and explain why the enclosing arrow has rank two when the counter payload and result types are monomorphic.

Sources.

The existential-as-universal definition and its beta calculation appear in [GLT89]. Harper gives the same client encoding and states the lazy-dynamics qualification in [Har16]. The value restriction in the calculation above is therefore part of the theorem, not an implementation footnote.

First-class packages

A package is an ordinary term, so an eliminator may select between package values at run time. Using the Church booleans of definition 5.15, with their quantified variable kinded by Ty, define chooseCounter:BoolFCounterCounterCounter,chooseCounter:=λb:BoolF.λm:Counter.λn:Counter.b[Counter]mn. All three lambda annotations are the types displayed on the first line. The universal elimination for b, followed by two applications, proves the typing judgment.

The true branch is a complete first-class-package calculation: tick2(chooseCountertrueFCNCP)unfoldchooseCountertick2(trueF[Counter]CNCP)Booleanβtick2CNcountercalculationsuc(suc(0)). Replacing trueF by falseF chooses CP and reaches the same displayed numeral by the earlier shadow-count calculation. The choice occurs before unpacking; both branches have the common existential type Counter.

First class does not make the hidden constructor projectible. From an arbitrary m:Counter there is no type expression m.X in the core grammar. One must unpack m, receiving a fresh lexical name X whose scope is the unpacking body. The core also has no syntax relating the hidden constructors of two packages or naming a component of one package in the type of another. Module systems with static names and sharing judgments add those capabilities; lexical packages alone establish none of them.

Exercise 7.14

★★☆ Give the full type derivation of chooseCounter. Then calculate the false branch through the boolean’s three beta contractions and the two counter steps. Finally explain why the proposed assignment chooseCounterbCNCP:N would violate package opacity.

Source.

The passage between a second-class module and a first-class existential package is given in [Har16]. That comparison does not identify packages with the full ML module hierarchy, and neither do we.

Two record failures, two different missing judgments

Products can encode an ordered tuple, but label-based records expose a different problem. To make the failure exact, temporarily add a static fixed-record fragment with record types and two term forms: A::={i:Ai}iI,e::={i=ei}iIe.. The finite label set has no duplicates. Its formation and typing rules are ΔAi::Ty (iI)Δ{i:Ai}iI::TyRecFΔ;Γei:Ai (iI)Δ;Γ{i=ei}iI:{i:Ai}iIRecI and Δ;Γe:{i:Ai}iIjIΔ;Γe.j:AjRecE. Its only new equality rule is componentwise equality after sorting labels: ΔAiBi::Ty(iI)Δ{i:Ai}iI{i:Bi}iI::TyQRec. We use this fragment only for static typing counterexamples; no record-value or operational theorem is claimed here. In particular, there is still no subtyping judgment.

The first failure asks for an unknown remainder. The intended type of record extension would have the following shape in a hypothetical higher-kinded extension with a row kind: ξ::Row.(ξ\secure)Rec(ξ)Rec({secure:BoolFξ}). Here is the qualified-type constraint arrow of chapter 4, not the relation lifting used in the preceding logical relation. This is not merely unprovable in Fω plus fixed records: it is not a formed type. The kind grammar contains no Row, the constructor grammar contains no row extension, and the judgments contain no lacks predicate. Quantifying over c::TyTy does not create field selection or extension operations. The calculus of chapter 4 has a second syntactic sort of rows, row extension, and lacks predicates. Internalizing those ingredients here would add a kind Row and require the constructor metatheory to be rerun for it.

The second failure needs no unknown tail. As already previewed for the row calculus in subsection 7.8.3, define portOf:{port:N}N,portOf:=λr:{port:N}.r.port, and the closed larger record server:={port=0,secure=trueF}:{port:N,secure:BoolF}. The application portOfserver fails. Its argument has the larger fixed record type, while T-App requires the exact domain {port:N}. The two sorted record constructors are distinct normal forms, so T-Conv cannot remove the extra field. A width relation {port:N,secure:BoolF}<:{port:N} would say that the larger record type is usable where the smaller one is expected; in general, A<:B proposes that A is usable where B is expected. Adding that width judgment and a subsumption rule would derive the application: width gives the displayed subtype premise, and subsumption changes the argument’s fixed record type to {port:N}. Neither <: nor subsumption belongs to the Fω fragment of definition 7.31; constructor equality cannot derive the width premise. The missing term rule would take premises Γe:A and A<:B to Γe:B.

The remedies are independent. Width subtyping forgets fields but does not name and preserve an unknown tail through extension. Row polymorphism names that tail but, without a subsumption rule, does not allow every fixed larger record wherever a smaller fixed record is expected.

Existential packages hide a constructor behind typed operations. Package preservation and progress establish the operational boundary; the higher-kinded logical relation proves representation independence for the two counter implementations. First-class selection is an ordinary consequence of native packages. Constructor equality still cannot forget a fixed record field; a subtype judgment and subsumption lie outside this calculus.

Suggested first pass.

None of these problems is a prerequisite for later chapters. Begin with exercise 7.20; it is the shortest route through the two distinct record boundaries and the handoff to subtyping. Then implement exercise 12.10.

Exercise 7.20

★★☆ For the unknown-remainder type, name the three symbols or judgments that are absent from the fixed-record calculus. For the width application, derive the types of portOf and server, then show exactly where T-App fails. Add only a width rule and subsumption; rederive the application and explain why the unknown-remainder type is still unformed.

Exercise 12.10

★★★ Practical project.existential-package-checker Implement kind inference and explicit Fω term checking for the constructor and term grammars recalled in this chapter, then add a compatible evaluator including T-Pack, T-Unpack, and the package call-by-value contractions. A checker from exercise 11.9 may be reused, but is not a premise of this project. Preserve the invariant that the result type of an unpacking is formed outside the hidden-constructor scope. The acceptance test must type and evaluate both counter packages through two increments and an observation, reject the two representation-leaking clients described after definition 7.31, and reject portOfserver in the fixed-record fragment. Compare the two successful observations at N; this checks the concrete instance of representation independence without claiming to decide the full logical relation.

Search the book

Type to search the local edition.