Lectures onType Theory
Chapter 14
Chapter 14Optional

ML Modules: Abstraction, Functors, and Sharing

Prerequisites. Direct starred prerequisites: none. No later core chapter depends on this route.

The existential packages of chapter 10 can hide one representation. Reusing that chapter’s counter, for example, gives the interface t::Ty.t×((tt)×(tN)). After unpacking, a client receives a fresh type name, an initial state, a step, and an observation. This is the right lexical account of one abstract value. It does not yet describe structure NatOrder:ORDERED,functor SetFn(X:ORDERED):SET sharing type elem=X.s,structure NatSet=SetFn(NatOrder). The sharing clause requires the result’s element type to be the very constructor selected by the argument’s component s, not merely a constructor of the same kind. An ML functor takes one statically classified module to another, and its result signature may mention the argument’s type component. A signature is a module’s static classifier; a module is the packaged implementation. The result interface must remember that its element type is the very type selected by the argument. Packing the argument and result independently creates unrelated existential witnesses. Unpacking the argument inside the functor body makes its witness lexical: the name cannot occur in the result type after that unpacking ends. Adding an equation outside the packages merely asserts the sharing that the encoding failed to transport.

The failure can be displayed before adding module signatures. If O and S are independently packed order and set implementations, the attempt unpack[t,o]=O in unpack[u,s]=S inpack[t,o,s]:a::Ty.(aa2)×Set(a) does not type: its second component has type Set(u), not Set(t). Choosing witness u makes the order component fail instead. The two lexical witnesses provide no equation between t and u.

A projectible path is a module path whose static projections carry a stable type identity. Signature matching propagates that identity into later components before any dynamic code runs. In the reduced calculus, opaque sealing is generative and functor application is nonprojectible.

Exercise 12.1

★★☆ Write separate existential packages for NatOrder and a set of its elements. Unpack the order package in the smallest possible scope. Mark the point at which its witness would have to escape to type the set package. Explain why replacing both witnesses by N destroys abstraction.

One reduced module calculus

The constructor and pure-term core is the Fω calculus of chapter 9, restricted to Ty, singleton kinds, arrows, products, and the pure terms appearing in module components. The only new constructor feature is a singleton kind Sing(c). A mixed context Γ contains constructor assumptions u::κ, expression assumptions x:τ, and module assumptions X:σ. Its exact singleton delta is Γc::TyΓSing(c) kindSingKind,Γc::TyΓc::Sing(c)SingI, Γc::TyΓSing(c)kTySingSub,Γd::Sing(c)Γdc::TySingE. Thus d::Sing(c) entails dc::Ty: a singleton kind records a static constructor equation. Subkinding is the least reflexive and transitive relation containing Sing-Sub and closed under kind equivalence; constructor kinding admits subsumption along it. Dynamic-type subtyping in this reduced calculus is conversion: Γτ1<:τ2 exactly when Γτ1τ2 type. Constructor equivalence and pure-term typing otherwise use the inherited Fω rules, but Sing-E adds the directed conclusion Γdc::Ty from Γd::Sing(c). A singleton kind is therefore a static equation, not a runtime test.

The selected fragment uses an acyclic manifest environment. Read the context from left to right. A manifest atom is either a declaration u::Sing(c) or a projectible component Q.s whose signature carries that singleton kind; its manifest equation is uc or Q.sc. The right-hand side c must be well kinded in the earlier context. Constructor variables and projectible static projections are the only atoms. Define expandΓ(d) by replacing each manifest atom by its earlier right-hand side, recursively, and then taking the inherited Fω beta-eta normal form. The declaration index strictly decreases at every manifest replacement.

Algorithmic constructor conversion first synthesizes the inherited kind of both inputs, erasing Sing(c) to Ty, and then compares expandΓ(d1) and expandΓ(d2) up to alpha-equivalence. Algorithmic subkinding accepts equal normalized kinds and the one strict shape Sing(c)kTy. Checking d at Sing(c) synthesizes d::Ty and applies the conversion test to d and c; checking at any other kind uses inherited Fω kinding.

Proposition 14.1 — Decision for the singleton constructor fragment

On well-formed acyclic manifest contexts, the preceding kinding, subkinding, and conversion procedures terminate. They are sound and complete for the displayed singleton rules together with the inherited Fω rules.

Proof of Proposition 14.1 — Decision for the singleton constructor fragment

Proof. Manifest expansion terminates because every replacement decreases the declaration index. The inherited normalization and comparison terminate by the Fω normalization and conversion result of corollary 7.26; the subkinding test is a finite shape comparison.

For soundness, induction on expansion replaces q by c using the singleton equation recorded when q entered the context. Inherited beta-eta normalization preserves constructor equality, so equal normal forms give declarative conversion. The strict subkind case is Sing-Sub, and singleton checking ends with Sing-I followed by conversion.

For completeness, induct on a declarative derivation. The inherited cases are complete by the Fω procedure. In the new Sing-E case, the manifest atom and its recorded right-hand side expand to the same normal form. Reflexivity, symmetry, and transitivity preserve equality of normal forms, and congruence follows because expansion is homomorphic before normalization. The only strict generated subkind is Sing-Sub. After equality steps are contracted, a subkinding derivation contains at most one strict step: no rule derives TykSing(c). The finite test therefore covers every derivation. ◻

Definition 12.1 — Signatures

The signatures of the reduced calculus are σ::=B(u::κ;τ)Sigma(X:σ1).σ2Pi(X:σ1).σ2. A basic signature contains one static constructor component u and one dynamic component of type τ, which may mention u. A hierarchy signature lets the second component mention the first path. A functor signature lets its result mention its argument path.

The formation rules state each dependency explicitly: the dynamic type τ in B-Sig is formed under its static component u, while the codomain signature σ2 in Sigma-Sig and Pi-Sig is formed under the module path X: Γκ kindΓ,u::κτ typeΓB(u::κ;τ) sigBSig, Γσ1 sigΓ,X:σ1σ2 sigΓSigma(X:σ1).σ2 sigSigmaSig. Γσ1 sigΓ,X:σ1σ2 sigΓPi(X:σ1).σ2 sigPiSig. Signature equivalence contains alpha-equivalence and the inherited kind, constructor, and type equivalences. It is closed under the congruence rules below. In B-Eq, write βi:=B(u::κi;τi). Its new congruence rules are Γκ1κ2 kindΓ,u::κ1τ1τ2 typeΓβ1β2 sigBEq, Γσ1σ1 sigΓ,X:σ1σ2σ2 sigΓSigma(X:σ1).σ2Sigma(X:σ1).σ2 sigSigmaEq. Γσ1σ1 sigΓ,X:σ1σ2σ2 sigΓPi(X:σ1).σ2Pi(X:σ1).σ2 sigPiEq.

Example 14.3 — A first basic signature

Let s::Ty and suppose the inherited constructor rules derive ssBool type. Then the complete signature formation is ΓTy kindΓ,s::TyssBool typeΓB(s::Ty;ssBool) sigBSig. This is the one-field core of the order signature B(s::Ty;ssBool): the static component chooses the carrier and the dynamic component may mention it.

Surface records with several type and value fields are right-associated hierarchies of basic signatures. This convention is structural, not an equation identifying differently associated hierarchies.

Definition 12.2 — Modules and projectible values

The module phrases, together with the two projections from a basic module, are M::=Xc;eMσ(let X=M1 in M2):σM1;M2M.1M.2λX:σ.MM1(M2). The static constructor grammar additionally admits M.s, and the dynamic term grammar admits M.d. These projections are defined only when M has a basic signature. The basic structure c;e has static part c and dynamic part e. The seal Mσ is opaque. The annotation on module let is part of the language, not a hint.

A module value and a projectible path are different notions. In an open context the open module-value judgment, written ΓM mval, is generated by X:σΓΓX mvalVVar,Γv valΓc;v mvalVBasic, ΓV1 mvalΓV2 mvalΓV1;V2 mvalVHierarchy. XΓλX:σ.M mvalVFunctor. A projectible phrase is generated by Q::=Xc;vQ1;Q2Q.1Q.2, where each projection is well typed. Its judgment is generated by X:σΓΓX projectiblePVar,Γv valΓc;v projectiblePBasic, ΓQ1 projectibleΓQ2 projectibleΓQ1;Q2 projectiblePHierarchy. ΓQ projectibleΓQ.i projectiblePProjection. A seal, annotated let, functor, and functor application are not projectible. A functor abstraction is a value but not projectible. Only projectible phrases may occur to the left of the static selector .s.

The judgment ΓM:σ is generated by the following module rules. Basic structures expose singleton kinds; sealing checks a match and exports only the sealed signature: X:σΓΓX:σVar,Γc::κΓe:τ[c/u]Γc;e:B(u::Sing(c);τ)Basic, ΓM:σ0D::Γσ0sσΓMσ:σSeal. Γσ sigΓM1:σ1Γ,X:σ1M2:σΓ(let X=M1 in M2):σLet, ΓM:σ1D::Γσ1sσ2ΓM:σ2Sub. Hierarchy introduction is intentionally nondependent. Dependency is added by matching and self-recognition, not guessed by the introduction rule: ΓM1:σ1ΓM2:σ2ΓM1;M2:Sigma(:σ1).σ2Hierarchy. ΓM:Sigma(X:σ1).σ2ΓM.1:σ1First, ΓM:Sigma(X:σ1).σ2XFV(σ2)ΓM.2:σ2Second. Direct second projection is therefore forbidden while the range mentions the first path. The abbreviation Sigma(:σ1).σ2 records this displayed freshness premise; it does not encode an additional rule.

The two-sorted basic projections have distinct judgments: ΓQ projectibleΓQ:B(u::κ;τ)ΓQ.s::κStatic. ΓM:B(u::κ;τ)uFV(τ)ΓM.d:τDynamic. Thus Static requires a stable path, and Dynamic requires a nondependent basic signature. A dependent dynamic type is first made nondependent by self-recognition and signature equivalence: ΓQ:B(u::κ;τ)ΓQ projectibleΓQ:B(u::Sing(Q.s);τ)Self. Hierarchy self-recognition propagates the signatures of projectible components: ΓQ projectibleΓQ:Sigma(X:σ1).σ2ΓQ.1:σ1Γσ1sσ1ΓQ:Sigma(X:σ1).σ2SelfFirst, ΓQ projectibleΓQ:Sigma(:σ1).σ2ΓQ.2:σ2Γσ2sσ2ΓQ:Sigma(:σ1).σ2SelfSecond. Finally, functors are checked by Γ,X:σ1M:σ2ΓλX:σ1.M:Pi(X:σ1).σ2Functor. ΓF:Pi(X:σ1).σ2ΓA:σ1XFV(σ2)ΓF(A):σ2Apply. Thus the range dependency must be eliminated before application. As above, an underscore in this binder abbreviates the displayed freshness premise.

The conclusion of Basic recognizes the constructor it contains. The conclusion of Seal has exactly the written interface, and the result is not a path. Transparent ascription is not a second term former: it is matching by subsignature, which retains every singleton written in the target. Opaque sealing forgets every equation absent from its target.

A surface constraint with type s = c is represented by the singleton signature B(s::Sing(c);τ). A sharing type constraint uses Sigma-dependency followed by singleton matching, while opaque ascription is Mσ.

Example 14.5 — Order modules

For c::Ty, define ORDERED:=B(s::Ty;ss2),ORDERED[c]:=B(s::Sing(c);ss2). If leNat:NN2, then NatOrder0:=N;leNat:ORDERED[N]. Transparent matching against ORDERED[N] preserves s=N. The opaque module NatOrder0ORDERED:ORDERED hides it.

Exercise 12.2

★☆☆ Give a Boolean implementation of ORDERED[2]. Type it once by transparent matching and once by opaque sealing to ORDERED. For each result, decide whether a client may pass true directly to the comparison operation, citing its exported static kind.

Matching, hierarchies, and sharing

Write Γσ1sσ2 when every module matching σ1 may be used at σ2. Subkinding is written κ1kκ2; ordinary dynamic-type subtyping retains τ1<:τ2. These judgments express signature matching, kind inclusion, and dynamic type conversion, respectively; none entails either of the others. Subsignature matching is the least relation generated by Γσ sigΓσsσSigRefl,Γσ1sσ2Γσ2sσ3Γσ1sσ3SigTrans, Γσ1σ1 sigΓσ1sσ2Γσ2σ2 sigΓσ1sσ2SigConvert, and these variance rules: Γ,u::κ1τ1<:τ2Γκ1kκ2ΓB(u::κ1;τ1)sB(u::κ2;τ2)BMatch, Γσ1sσ1Γ,X:σ1σ2sσ2ΓSigma(X:σ1).σ2sSigma(X:σ1).σ2SigmaMatch, Γσ1sσ1Γ,X:σ1σ2sσ2ΓPi(X:σ1).σ2sPi(X:σ1).σ2PiMatch. In Sigma-Match, self-recognition of the first component propagates its singleton equations while the second components are compared. This is the sharing mechanism.

Definition 14.6 — Algorithmic signature matching

Write matchΓ(σ1,σ2)=D when the following partial procedure returns a declarative matching derivation D. First alpha-normalize binders and normalize every constructor with expandΓ.

  1. For two basic signatures, decide κ1kκ2 and, under u::κ1, decide τ1τ2 type. On success return B-Match.

  2. For two hierarchy signatures, recursively match their first components. Then extend the context by X:σ1, so the source component’s singleton facts are available, normalize both continuations, and recursively match them. On success return Sigma-Match.

  3. For two functor signatures, recursively match the target domain against the source domain. Under X:σ1, recursively match the source codomain against the target codomain, and return Pi-Match.

  4. Signatures with different outer constructors do not match.

Here τ1<:τ2 is the conversion test fixed in the core calculus. The output derivation records every normalization equality and the structural rule used at each node.

Proposition 14.7 — Termination and soundness of algorithmic matching

The procedure of definition 14.6 terminates. If it returns D, then D::Γσ1sσ2.

Proof of Proposition 14.7 — Termination and soundness of algorithmic matching

Proof. Order calls by the sum of the numbers of B, Sigma, and Pi nodes in the two inputs. Every recursive call compares proper component signatures, so this measure decreases. Constructor normalization and all leaf decisions terminate by proposition 14.1.

For soundness, induct on the returned trace. A basic trace contains exactly the two premises of B-Match. A hierarchy trace contains the first component derivation and the continuation derivation under X:σ1, exactly as required by Sigma-Match. A functor trace records the reversed domain derivation and covariant codomain derivation required by Pi-Match. The normalization records are constructor and signature conversions, so Sig-Convert transports the structural derivation back to the input signatures. ◻

For a fixed element constructor a::Ty, let SET[a]:=B(r::Ty;r×(arr)×(ar2)). The shared hierarchy signature is ORDSET:=Sigma(X:ORDERED).SET[X.s]. Suppose pO:ORDERED[N] and pS:SET[N] are projectible values. Their explicit pair is first checked at the nondependent signature Sigma(:ORDERED[N]).SET[N]. Matching the first component to X:ORDERED makes X.sN while the second is checked, so the pair also matches ORDSET. The equality was transported by the hierarchy; it was not reconstructed from dynamic operations.

The algorithmic trace has three nodes. At the root, the two signatures are Sigma-signatures. Their first components match by B-Match: Sing(N)kTy, and expanding the dynamic comparison type replaces its carrier by N. For the second recursive call the context is X:ORDERED[N], whose static projection contributes the manifest equation X.sN. Expansion therefore changes SET[X.s] to SET[N], and the basic-signature comparison succeeds reflexively. The returned root is Sigma-Match with exactly these two subderivations.

Here is the complete derivation in the selected rules. Let τS(a,r):=r×(arr)×(ar2), and suppose vS:τS(N,ListN). Rule Basic, followed by B-Match and Sub, gives ListN::TyvS:τS(N,ListN)ListN;vS:B(r::Sing(ListN);τS(N,r))BasicB(r::Sing(ListN);τS(N,r))sSET[N]pS:SET[N]Sub. Together with pO:ORDERED[N], Hierarchy yields pO:ORDERED[N]pS:SET[N]pO;pS:Sigma(:ORDERED[N]).SET[N]Hierarchy. Finally, under X:ORDERED[N], rule Static and the singleton elimination give X.sN. Therefore SET[N]SET[X.s], and the final matching and subsumption are pO;pS:Sigma(:ORDERED[N]).SET[N]ORDERED[N]sORDEREDX:ORDERED[N]SET[N]sSET[X.s]Sigma(:ORDERED[N]).SET[N]sORDSETSigmaMatchpO;pS:ORDSETSub.

Second projection is deliberately restricted. A phrase M.2 may be typed directly only after the range signature is nondependent. For the pair above, matching first specializes SET[X.s] to SET[N], and only then is the projection formed. This order prevents a local path X from escaping.

Proposition 12.4 — Sharing preservation

Suppose ΓM:Sigma(X:ORDERED).SET[X.s],ΓM projectible,ΓM.1:ORDERED[c]. Then the second projection has signature SET[c]. Its insert operation therefore accepts exactly the element type recognized at M.1.s.

Proof of Proposition 12.4 — Sharing preservation

Proof. Rule B-Match gives ORDERED[c]sORDERED: its static premise is Sing(c)kTy, and its dynamic premise is reflexive after singleton elimination. Rule Self-First, using this match and the last premise of the proposition, derives ΓM:Sigma(X:ORDERED[c]).SET[X.s]. Under X:ORDERED[c], rules Static and Self give X.s::Sing(c), hence X.sc::Ty. By Sigma-Eq, Sigma(X:ORDERED[c]).SET[X.s]Sigma(:ORDERED[c]).SET[c]. Rule Second now derives ΓM.2:SET[c]. Its dynamic product contains crr, which is the stated insert type. ◻

Exercise 12.3

★☆☆ Let pO:ORDERED[N]andpB:SET[2]. Attempt to match pO;pB against ORDSET. Write the two singleton equations forced while checking the second component and identify the unsatisfied constructor-equivalence judgment.

Generative functors

Rule Apply requires a nondependent result. A dependent functor is used by first matching its projectible argument to a manifest signature and reducing the result dependency.

Assume the core list constructor and its usual empty, insertion, and membership operations. In a context X:ORDERED, let memberX be list membership computed with the comparison in X.d, and define ListSetX:=ListX.s;nil,cons,memberX,SetFn:=λX:ORDERED.ListSetXSET[X.s]. The three dynamic fields have types ListX.s, X.sListX.sListX.s, and X.sListX.s2. Hence Basic, Seal, and Functor derive SetFn:Pi(X:ORDERED).SET[X.s]. Given pO:ORDERED[N], Pi-Match compares the domain contravariantly and specializes the result under the manifest argument; Sub then gives SetFn:Pi(X:ORDERED[N]).SET[N]. Now Apply derives SetFn(pO):SET[N].

The application is not projectible. If the body creates an abstract representation, each annotated binding (let Si=SetFn(pO)SET[N] in Mi):σ introduces a fresh abstract representation path for Si.s. Even with the same functor and argument, S1.s and S2.s are unrelated unless an outer hierarchy explicitly shares them. This is the principal generative discipline.

Exercise 12.4

★★☆ Assume SetFn represents sets by lists but seals its result at SET[X.s]. Bind two applications at the same manifest argument. Determine which element types and representation types are equal across the two results. Construct a hierarchy signature that shares the element type without sharing the representation type.

Elaboration of the matched fragment

Dependent paths must be resolved before ordinary existential packages can be a target. We therefore state elaboration only for signatures whose hierarchy and functor dependencies have already been eliminated by matching.

The module reduction relation D is call by value. Its operational module values, written V, are V::=c;vV1;V2λX:σ.M. Unlike the open module-value judgment, this grammar has no variable case. The let and application contractions below use only this operational grammar. In particular a seal is not a value, matching PFPL’s abstraction boundary.

Definition 14.9 — Hereditary module substitution

Capture-avoiding substitution must also preserve the path premises carried by a typing derivation. Write hsubst(M;V;X) for hereditary module substitution: first substitute V for X, then contract only hierarchy projections whose receiver was exposed by that substitution. On derivations, a substituted basic value rebuilds Basic; a substituted hierarchy value rebuilds Hierarchy, recursing into the component selected by a former Self-First, Self-Second, or P-Projection premise. It performs no seal, functor, or arbitrary dynamic reduction. This restricted normalization is structural on the original projection spine, so it terminates and agrees with ordinary substitution when no projectible occurrence of X is selected.

The sorted root contractions are exactly VσDV,(c;v).dv,V1;V2.1V1,V1;V2.2V2,(let X=V in M):σhsubst(M;V;X),(λX:σ.M)(V)hsubst(M;V;X). The seal step is administrative erasure at runtime. Its label retains the matching derivation D::σ0sσ: the reduct is typed at σ by Sub, so erasure does not restore a hidden static equation. Static projection computes by constructor equivalence, (c;v).sc; hierarchy-path projections compute in the same way. They are not dynamic steps.

Module evaluation contexts are module-sorted: EM::=[]EMσ(let X=EM in M):σEM;MV;EMEM.1EM.2EM(M)V(EM). Ordinary expression contexts are those of the core. Two cross-sort rules evaluate a module inside a dynamic projection and an expression inside a basic structure. Compatible closure is therefore the following complete family: RREM[R]EM[R]MContext. MMM.dM.dDContext. eec;ec;eBasicContext. No reduction crosses a functor body or the body of an annotated let before its binder is discharged. Equations (12.1)(12.5) and the three context rules are the entire selected module dynamics.

The rules compute rather than merely classify. Let σ=B(u::κ;τ), let V=c;v match σ, and let W be a module value of a functor’s domain. Then (let X=(λY:σ0.V)(W)σ in X.d):τ(12.5)(let X=Vσ in X.d):τ(12.1)(let X=V in X.d):τ(12.4)V.d(12.2)v. By contrast, a functor abstraction is a value, so no rule reduces inside its body before application.

Definition 12.5 — Closed package target

A naive dependent clause would require Pack(Sigma(X:σ1).σ2)=?Pack(σ1)×Pack(σ2), but X is free in the right-hand occurrence of Pack(σ2) and has no target binder. Resolving that path dependency before translation is therefore necessary. A signature is closed-result when every Sigma- or Pi-range is independent of its bound module variable. Define Pack(B(u::κ;τ)):=u::κ.τ,Pack(Sigma(:σ1).σ2):=Pack(σ1)×Pack(σ2),Pack(Pi(:σ1).σ2):=Pack(σ1)Pack(σ2). The target is the pure call-by-value existential core of chapter 10, extended by the singleton kinds, subkinding, and constructor conversion displayed in section 12.1, together with call-by-value let, products, N, 2, 1 with , and List. These ordinary data formers use their formation, introduction, elimination, congruence, and beta rules from the preceding core chapters. Package and function constructors form values at value arguments; ordinary target type safety is the earlier package proof applied to these conservative static and data extensions.

Definition 12.6 — Matching coercion

Let D::σ1sσ2 be a matching derivation between closed-result signatures. Its target coercion mcoeD:Pack(σ1)Pack(σ2) is defined by the last rule of D. For B-Match, mcoeD(z):=unpack[u,x]=z in pack[u,mcoeτ(x)], where mcoeτ is the coercion named by the displayed core subtyping premise; kind weakening checks the same witness at the target kind. For the closed-result hierarchy and functor rules, mcoeD(z):=mcoeD1(π1z),mcoeD2(π2z),mcoeD(f):=λx.mcoeD2(f(mcoeD1(x))). The functor domain coercion is reversed because Pi-Match is contravariant. Reflexivity gives the identity, transitivity composes coercions, and signature equivalence transports without computation. This definition is deliberately absent for unresolved dependent ranges.

Definition 14.12 — Elaboration-admissible derivation

A module typing derivation E::ΓM:σ is elaboration-admissible when every signature translated to a target package has a closed result: no result type mentions a module path eliminated by the translation. Require this invariant recursively of contexts, premises, and conclusions. In particular, Dynamic, Second, and Apply must have nondependent result signatures.

Definition 14.13 — Package opening and open translation contexts

Package opening is part of the translation environment. For a closed-result signature σ, write Openσ(z,X;q) for the target term defined by these three clauses: OpenB(u::κ;τ)(z,X;q):=unpack[uX,xX]=z in q,OpenSigma(:σ1).σ2(z,X;q):=Openσ1(π1z,X.1;Openσ2(π2z,X.2;q)),OpenPi(:σ1).σ2(z,X;q):=let fX=z in q. The corresponding context operation is distinct from this term former. Write OpenCtxρ(Γ) for the telescope obtained by translating constructor and expression assumptions pointwise and replacing each module assumption X:σ by a fresh package variable zX:Pack(σ) followed by the constructor, expression, and functor binders introduced by Openσ(zX,X;q). At the same step, extend ρ with XzX and with the static and dynamic component projections named by those binders. Recursion on σ fixes the telescope order, so OpenCtx is a context operation rather than an abbreviation for a term.

The environment extension ρσ(Xz), available under the binders introduced by Openσ, records X.suX, X.dxX, the component projections obtained by applying these clauses recursively through nested signatures, and Xz or fX when the whole module is used. The result type of q may not contain a freshly opened constructor. This is the avoidance premise enforced by the result annotation on Let and by definition 14.12.

For a projectible path Q, let statρ(Q) be the target constructor denoted by Q.s. An opened variable is read from ρ, an explicit basic path denotes its written constructor, and hierarchy projections select the corresponding component.

Definition 14.14 — Projectible views and derivation-indexed elaboration

The whole existential package does not determine its witness judgmentally: opening the same package variable twice introduces two fresh abstract constructors. Self-recognition therefore uses the components already exposed by the projectible path, not a second unpack. For a derivation E::ΓQ:B(u::κ;τ), define its projectible view Viewρ,E(Q)=(cQ,eQ) simultaneously with the derivation-indexed translation below, by recursion on the projectible syntax and after stripping final Sub, Self, Self-First, and Self-Second rules. For an opened variable use the stored entries ρ(Q.s) and ρ(Q.d); for c;e use c and the translation of e; for a hierarchy projection recurse into the selected component. A stripped Sub inserts the dynamic coercion selected by its matching derivation. Put SelfPackρ,E(Q):=pack[cQ,eQ]where (cQ,eQ)=Viewρ,E(Q).

For an elaboration-admissible derivation E, write TρE(M) for its translation. The derivation index selects the matching coercion and the subderivation at every recursive call. If Ei are the immediate subderivations, the principal clauses are TρEX(X):=ρ(X),TρE(c;e):=pack[c,TρE1(e)],TρE(Mσ):=mcoeD(TρE1(M)),TρE(M1;M2):=TρE1(M1),TρE2(M2). TρE(M.1):=π1(TρE1(M)),TρE(M.2):=π2(TρE1(M)),TρE((c;e).d):=TρE1(e),TρE(Q.d):=ρ(Q.d)(Q an opened projectible path),TρE(M.d):=unpack[u,x]=TρE1(M) in x(uFV(τ)). For the three binding forms, the clauses are TρE((let X=M1 in M2):σ):=let z=TρE1(M1) inOpenσ1(z,X;Tρσ1(Xz)E2(M2)),TρE(λX:σ.M):=λz:Pack(σ).Openσ(z,X;Tρσ(Xz)E1(M)),TρE(M1(M2)):=TρE1(M1)TρE2(M2). The three self-recognition clauses are TρESelf(Q):=SelfPackρ,E0(Q),TρESelfFirst(Q):=TρE1(Q.1),π2(TρE0(Q)),TρESelfSecond(Q):=π1(TρE0(Q)),TρE1(Q.2). Here E0 is the premise typing Q, while E1 types the selected component at its refined signature. The latter is retranslated because the displayed matching premise points from the refined signature to the original one; it cannot coerce an already forgotten package in the opposite direction. Projectibility ensures that retranslating Q does not allocate a fresh generative witness. Elaboration admissibility requires each hierarchy range to be independent of its bound path, so its translation is an ordinary product and the unchanged component retains the required type. Here D is the matching premise of Seal; transparent subsumption has the same coercion clause without adding a source constructor. In the second dynamic-projection clause, Dynamic’s premise says that the result type omits the existential witness, exactly the side condition for existential elimination. In the first clause the package was already opened by the enclosing module binder, so its witness remains in scope. Thus neither clause returns a term whose type contains an escaping existential witness. The view clauses and translation clauses above form one simultaneous recursive definition. Order calls lexicographically by (h(E),|Q|), where h is derivation height and |Q| is projectible-path size, taken as zero for a translation call without a distinguished path. Stripping a final Sub, Self, Self-First, or Self-Second rule strictly lowers h at the same path. A hierarchy projection and the translation of the expression in an explicit basic path use strict premise derivations; the former also shortens the selected path. Every ordinary translation clause recurs on an immediate premise, and a self-recognition clause calls View on its strict premise derivation. Thus each mutual call lowers the displayed measure; no clause presupposes an operation defined later.

Lemma 14.15 — Projectible-witness coherence

Let E::ΓQ:B(u::κ;τ) be elaboration-admissible, with Q projectible, and let Viewρ,E(Q)=(cQ,eQ). In OpenCtxρ(Γ), cQ::κ,cQstatρ(Q)::κ,eQ:τ[cQ/u]. Consequently SelfPackρ,E(Q) has type u::Sing(statρ(Q)).τ.

Proof of Lemma 14.15 — Projectible-witness coherence

Proof. Use the recursion defining View. For an opened variable, all three judgments are entries installed together by ρσ(Xz). For an explicit basic path they are the premises of Basic. A projection selects the corresponding recursively typed component. A final Sub preserves the static constructor and applies its displayed dynamic coercion; signature conversion transports the three judgments without computation. The three self rules select or reassemble the same recursively computed view. These cases exhaust the projectible grammar and its possible final typing rules.

The conversion cQstatρ(Q) lets singleton checking view cQ at Sing(statρ(Q)). Existential introduction with the already typed eQ gives the final package judgment. No equality between two independently unpacked witnesses is used. ◻

Theorem 12.7 — Supported elaboration

If E::ΓM:σ is elaboration-admissible, then OpenCtxρ(Γ)TρE(M):Pack(σ), where ρ is the path environment built simultaneously with the displayed context telescope. This theorem does not claim an elaboration for an unresolved dependent signature or matching derivation.

Proof of Theorem 12.7 — Supported elaboration

Proof. Induct on E. The Var case uses its corresponding opened component. The Basic case is existential introduction, using c::κ and e:τ[c/u]. Seal and subsumption use the matching coercion defined above. Hierarchy introduction and projection are product introduction and elimination. The projectible-witness lemma gives the Self case. Each hierarchy-self case uses product introduction, with induction hypotheses for the original hierarchy and its refined component.

Functor cases are function introduction and elimination. The term former Openσ places the argument witness around the translated body. Annotated let uses the same opening operation after one target let has evaluated its definition; its annotated result excludes X, so existential elimination is well scoped. For Dynamic, if ρ(X.d)=e, elaboration returns the already opened field e; otherwise the nondependent result type permits unpacking the module locally. Basic, hierarchy, functor, seal, let, self, static-projection, and dynamic-projection cases exhaust the selected elaboration forms. ◻

Lemma 14.17 — Operational values elaborate to target values

If EV::ΓV:σ is elaboration-admissible and V belongs to the operational module-value grammar, then TρEV(V)W for some target value W.

Proof of Lemma 14.17 — Operational values elaborate to target values

Proof. Induct on V. A basic value translates to a package whose payload is a target value by the inherited expression-value lemma. A hierarchy translates componentwise; reduce each component by the induction hypotheses to obtain a pair of values. A functor abstraction translates to a target abstraction. Final matching adds only the terminating package coercions displayed above; their unpack redexes contract once the induction hypothesis has produced the package value. Self-recognition instead repacks the already exposed projectible view and introduces no fresh unpack redex. ◻

Lemma 14.18 — Derivation substitution

Let EV::ΓV:σ1 and EM::Γ,X:σ1M:σ2 be elaboration-admissible, where V is an operational module value from the displayed grammar, not merely a derivation of the open module-value judgment. Alpha-rename their binders apart. The hereditary source substitution construction replaces each Var leaf for X by EV, contracts the finite projection spine exposed at a projectibility premise, and rebuilds every other rule recursively. It alpha-renames a let or functor binder before descending and produces an elaboration-admissible derivation E:=hsubst(EM;EV;X). Put ρz:=ρσ1(Xz). Then let z=TρEV(V) in Openσ1(z,X;TρzEM(M))TρE(hsubst(M;V;X)).

Proof of Lemma 14.18 — Derivation substitution

Proof. By lemma 14.17, the translated V reaches a target value, so every package opening below reaches its unpack contraction. Induct on EM. If the last rule is Var for X, the opening clauses expose exactly the static, dynamic, and whole-module components of V. A different variable is unchanged. The Basic case is the inherited expression-substitution lemma under existential introduction. Seal and subsumption retain their displayed matching derivation, so the induction hypothesis is closed under the same mcoeD. Hierarchy formation and both projections use product congruence. In a Self case, hereditary substitution either preserves the projectible path or exposes a basic value and rebuilds Basic; the view recursion of lemma 14.15 gives the same static and dynamic components on both sides. For either hierarchy-self rule, an exposed hierarchy value is decomposed, the selected component is rebuilt recursively, and Hierarchy reassembles the result. Thus a functor in an unselected component causes no false projectibility premise. The two Dynamic cases use respectively the installed field and existential beta-reduction. In annotated let and functor abstraction, alpha-renaming prevents capture and the induction hypothesis applies beneath the new opening. Application uses function congruence. This exhausts the module grammar. ◻

Theorem 12.8 — Simulation and module safety

Let E::ΓP:σ be elaboration-admissible. Suppose one selected module or cross-sort expression step gives PP, and let E be the reduct derivation obtained by retaining every Sub and self-recognition rule with its matching premises, and by applying the substitution construction of lemma 14.18 at let or application. Then TρE(P)TρE(P). Consequently, if P is closed, its target translation is a value or takes a target step; it cannot get stuck at a package, product, or function elimination.

Proof of Theorem 12.8 — Simulation and module safety

Proof. The dynamic projection of an explicit basic module takes zero target steps; a hierarchy projection takes one product step. Let and application use lemma 14.18 after the target let or beta step. For the seal root, the reduct is translated through the retained Sub derivation, so both sides contain the same mcoeD; zero target steps suffice. If E ends in Self, its reduct derivation retains the rule or rebuilds Basic after hereditary substitution. In both cases the induction hypothesis is closed under the projectible view by lemma 14.15. For either hierarchy-self rule, hereditary substitution decomposes an exposed value as in lemma 14.18; product congruence then applies to the original hierarchy and to the retranslated selected component. The three context rules follow by target compatible closure. Target progress gives the final claim after theorem 12.7; preservation retains its package type. This is a target-safety consequence, not a proof of source progress. ◻

Exercise 12.5

★★★ Let pO:ORDERED[N], and specialize the displayed definition of SetFn at pO. For the annotated module L:=(let S=SetFn(pO) in 2;π2(π2(S.d))0(π1(S.d))):B(::Sing(2);2), give the source application beta-step, seal step, let step, dynamic projection in L.d, and the product projections that select the empty set and membership operation. Give the corresponding package translation and reductions, with the source or target rule on every line. Identify the existential-elimination side condition that would fail if the annotated result type mentioned S.s.

Representation independence at one interface

Let COUNTER:=B(t::Ty;t×((tt)×(tN))). Consider CN=N;0,succ,λn.n,CP=N×1;0,,λp.succ(pr1p),pr2p,λp.pr1p. Relate n:N to (m,):N×1 exactly when n=m. The initial states are related, the step functions preserve this relation, and the read functions return equal naturals.

Definition 14.20 — Counter representation relation

Define R(n,m,) exactly when n=m.

The three interface obligations are calculations: R(0,0,),R(n,m,)n=msucc(n)=succ(m)R(succ(n),succ(m),),R(n,m,)n=m. Thus initial states are related, steps preserve R, and reads of related states agree.

Definition 12.9 — Counter clients

A counter client is a term K:N in the simply typed target language with products, arrows, unit, Booleans, and naturals, under exactly the context z:t,step:tt,read:tN. The type t is abstract: the term contains no constants, equality, or eliminators specialized to t. The ordinary closed primitives at N, 2, products, and unit preserve equality. Instantiation substitutes one implementation’s state and operations for these variables and thereby produces a closed term.

Definition 14.22 — Value and computation relations for counter clients

For the abstract-state relation R, define a relation VA on closed values by recursion on client types: Vt:=R,VN:==N,V2:==2,V1:==1,(a1,a2)VA×B(b1,b2)a1VAb1  a2VBb2,fVABga,b. aVAbfaCBgb. Here the computation closure is eCAev,v. ev  ev  vVAv. For a client context Ξ, write ρNVΞρP when both environments map variables to closed values and every x:A in Ξ satisfies ρN(x)VAρP(x).

Lemma 12.10 — Counter fundamental relation

If Ξe:A and ρNVΞρP, then e[ρN]CAe[ρP].

Proof of Lemma 12.10 — Counter fundamental relation

Proof. First record the compatibility calculation for applications. Assume fCABgandaCAb. Evaluate the four terms to f0,g0,a0,b0. Their value relations and the arrow clause give f0a0CBg0b0. Call-by-value compatibility prefixes the four evaluation sequences. Products and projections have the corresponding calculation.

Now induct on the typing derivation. Variables reduce in zero steps to the related values from the environment; constants relate to themselves. Pairing and projection use product compatibility. For abstraction, if aNVAaP, the body induction hypothesis proves e[ρN,xaN]CBe[ρP,xaP]. Therefore the two abstraction values satisfy the arrow clause; application uses the compatibility calculation above. The three distinguished variables satisfy their clauses by the initial-state, step-preservation, and observation calculations. No other case inspects a value of abstract type t. ◻

Theorem 12.11 — Counter representation independence

Seal CN and CP separately at COUNTER. For every counter client K, the two instantiated closed terms evaluate to the same natural number. Termination is automatic for the simply typed client language fixed in definition 12.9.

Proof of Theorem 12.11 — Counter representation independence

Proof. Opening either translated sealed package introduces exactly the abstract state, initial state, step, and read components named in definition 12.9. Existential elimination keeps the witness out of the result type, so a client in that grammar cannot name it. Apply lemma 12.10 to the environments containing the related states and operations. The resulting computations evaluate to VN-related values, which are the same numeral. Strong normalization of the simply typed client calculus gives the two values, and determinacy of pure evaluation makes them the stated observations. ◻

Exercise 12.6

★★☆ Define a client that steps three times and reads. Evaluate it against both implementations. Then add hypothetical equality at the abstract type and identify the new relation-preservation hypothesis required by lemma 12.10.

Principal matching, exactly where it exists

Opaque seals and generative applications erase or create static identity. Principal recognition is therefore about projectible paths, not arbitrary module phrases.

Assume principal core typing: each closed value v has a partial principal type pty(v), and every other type of v is a supertype. A closed constructor c has principal kind Sing(c). Core typing has inversion modulo equivalence and subsumption.

Definition 12.12 — Principal signature of a projectible value

For a closed projectible path, define psig(c;v):=B(u::Sing(c);pty(v)),psig(p1;p2):=Sigma(:psig(p1)).psig(p2),psig(p.1):=σ1,if psig(p)=Sigma(:σ1).σ2,psig(p.2):=σ2,if psig(p)=Sigma(:σ1).σ2. The definition is partial when pty is undefined, a projection does not select a hierarchy, or the phrase is not projectible. Principal hierarchies are nondependent; dependent sharing is recovered by Sigma-Match from the singleton equations in their components.

Lemma 14.26 — Static shape of every principal basic component

Let p be closed and projectible. A hierarchy position ι is a finite word over {1,2}; put p.ϵ=p and read the word from left to right as successive projections. At every hierarchy position ι for which the selected component path p.ι and its principal signature are defined, if psig(p.ι)=B(u::κ;τ), then κSing((p.ι).s) kind. The empty position ι=ϵ gives the basic-path case.

Proof of Lemma 14.26 — Static shape of every principal basic component

Proof. Induct on the derivation that computes the principal-signature tree of p, with the induction claim quantified over every position in that tree. At a basic leaf c;v, the first clause of psig and (c;v).sc give the equation. At a hierarchy, a position begins with 1 or 2; apply the corresponding component induction hypothesis to the remaining position. A projection selects exactly that subtree, so the same component hypothesis applies without requiring the whole hierarchy to have a basic principal signature. These are all clauses that compute psig. ◻

Lemma 14.27 — Narrowing

Suppose Γκ1kκ2. Replacing u::κ2 by u::κ1 in a well-formed continuation of the context preserves constructor kinding, constructor equality, subkinding, signature formation, subsignature matching, and module typing. Likewise, if Γσ1sσ2, replacing X:σ2 by X:σ1 preserves those judgments.

Proof of Lemma 14.27 — Narrowing

Proof. Prove both statements simultaneously by induction on the affected derivation. Variable cases use the assumed subkind or subsignature and one transitivity step. Binder cases first alpha-rename and apply the induction hypothesis to the extended context. Conversion cases use preservation of constructor equality under the narrower context, established by the simultaneous induction; termination of conversion is irrelevant here. Singleton elimination is the only new case beyond the inherited core. In the dependent Sigma-Match case, first narrow the first-component matching premise; then apply the induction hypothesis to the second-component judgment in the narrowed context and rebuild Sigma-Match. The B-Match and Pi-Match cases follow the variance in their premises. The typing rules use the signature half of the simultaneous statement. ◻

Lemma 14.28 — Structural matching inversion

Every derivation ΓB(u::κ1;τ1)sB(u::κ2;τ2) can be normalized to one B-Match. Every derivation between two Sigma-signatures can be normalized to one Sigma-Match, and every derivation between two Pi-signatures to one Pi-Match, with the premises shown in the defining rules.

Proof of Lemma 14.28 — Structural matching inversion

Proof. Induct on the matching derivation. Reflexive and conversion steps disappear. In a transitive composite, apply the induction hypotheses and compose the kind, type, or component premises. The bound context in the second component may change at the middle signature; lemma 14.27 transports that premise before transitivity is applied. Shape preservation of the assumed conversion procedure rules out a basic-to-hierarchy or hierarchy-to-functor conversion. The remaining last rule is the corresponding structural rule. ◻

Corollary 14.29 — Decision and completeness of structural matching

For well-formed signatures in the reduced grammar, Γσ1sσ2matchΓ(σ1,σ2)=D for some returned derivation D. Thus subsignature matching is decidable on this finite fragment.

Proof of Corollary 14.29 — Decision and completeness of structural matching

Proof. The reverse implication is proposition 14.7. For the forward implication, apply lemma 14.28. Its normalized last rule is B-Match, Sigma-Match, or Pi-Match; the corresponding algorithm clause invokes the induction hypothesis on exactly its premises. Signature-conversion steps are absorbed by the initial normalization. Induction on the total signature-node count terminates with the basic case. ◻

Lemma 14.30 — Typing factorization for projectible paths

Let p be closed and projectible with psig(p) defined. Every derivation P::⊢p:σ factors as the canonical introduction-and-projection derivation p:psig(p), followed by a matching derivation DP::⊢psig(p)sσ.

Proof of Lemma 14.30 — Typing factorization for projectible paths

Proof. Induct first on the height of P, using the grammar of p in the introduction and projection cases. A final Sub or signature-equivalence conversion composes the induction matching with its displayed match by Sig-Trans or Sig-Convert.

Suppose the final rule is Self. Write psig(p)=B(a::κ0;τ0). The induction hypothesis factors the premise typing through B(a::κ0;τ0)sB(u::κ;τ). After self-recognition the required match is B(a::κ0;τ0)sB(u::Sing(p.s);τ), whose premises are κ0kSing(p.s) and τ0<:τ[a/u]. Structural matching inversion proves the second premise under a::κ0. By lemma 14.26, κ0Sing(p.s), which proves the first. These premises form a B-Match to the conclusion B(u::Sing(p.s);τ).

Suppose the final rule is Self-First. Normalize the induction match for its hierarchy premise. Its second component gives the required match from the nondependent second component of psig(p) to σ2. The induction hypothesis for the premise p.1:σ1 gives psig(p.1)sσ1. These two premises combine by Sigma-Match. The displayed σ1sσ1 premise of Self-First justifies checking the reused range σ2 under its refined binder. The Self-Second case is symmetric: its original hierarchy match proves the first-component premise, and the induction hypothesis for p.2:σ2 proves the refined second-component premise; then apply Sigma-Match.

The remaining last rules follow the path grammar. For p=c;v, inversion leaves Basic. Core factorization gives Sing(c)kκ and pty(v)<:τ[c/u]; singleton substitution and B-Match finish. For p=p1;p2, inversion leaves Hierarchy, and the component induction hypotheses combine by Sigma-Match. For p=q.i, inversion leaves First or Second; normalize the induction match for q and select its corresponding component premise. A closed derivation cannot end in Var, and no other typing rule concludes a projectible phrase. ◻

Theorem 12.13 — Principal matching for closed projectible values

Let p be closed and projectible, with psig(p) defined. Then p:psig(p). For every p:σ, psig(p)sσ. If p:σ and every typing p:σ induces σsσ, then psig(p)sσandσspsig(p). Thus the least signature is unique up to mutual matching. We do not identify mutually matching signatures by judgmental equivalence. No conclusion is claimed for seals, lets, functors, or applications.

Proof of Theorem 12.13 — Principal matching for closed projectible values

Proof. The canonical derivation and the displayed match are lemma 14.30. If σ has the same universal property, then p:σ gives psig(p)sσ. Applying the property of σ to the canonical typing p:psig(p) gives the reverse match. No antisymmetry principle is among the selected rules, so mutual matching is the strongest conclusion. ◻

Exercise 12.7

★★☆ Compute psig(NatOrder0) and the principal signature of its pair with a list-based SET[N]. Explain which clause of definition 12.12 fails for its opaque seal and for SetFn(NatOrder0). Do not look through either boundary. If you propose another least signature for either projectible example, prove both matching directions; do not infer judgmental equivalence.

Proof-relevant phase distinction: a backward comparison

The reduced calculus keeps static constructors and dynamic terms in one judgment but restricts dependency to projectible paths. Sterling and Harper instead make the phase boundary an internal proposition, written bst: under that assumption dynamic inhabitants of a fixed type are identified, while type and signature components retain their static information. A static-extent signature then classifies modules whose restriction under bst agrees with a specified module. This can constrain a whole nested structure, not only one type projection [SH21].

Here is one concrete nested family on which the distinction matters. This is comparison notation for ModTT, not an extension of the grammar in section 12.1. It uses ModTT’s dependent products over dynamic signatures, not an assumed object-language function type [SH21]. For a closed type T, put σ(T):=Sigma(X:type).Sigma(pack:Pi(:X).T).Pi(:T).X,τ(T):=Sigma(X:type).Pi(:T).T. The angle brackets classify dynamic values at the displayed type from the object language, and Pi classifies a module functor. Thus a value of σ(T) is a nested module U=[X,[pack,unpack]], whose outer static component is X and whose inner structure contains the two dynamic maps. Define the closed module functor V(T,U):=[X,λz:T.pack(unpackz)]:τ(T). The result preserves the input’s static type component and computes its dynamic round trip.

Fix closed types T0,T1, modules Ui=[Xi,[packi,unpacki]]:σ(Ti), and a proof-relevant relation T~(t0,t1), an α-small set of witnesses rather than a truth value, indexed by pairs of closed dynamic values ti:Ti. An input relation witness consists of an α-small family of sets X~(x0,x1) and maps wpack:X~(x0,x1)T~(pack0x0,pack1x1),wunpack:T~(t0,t1)X~(unpack0t0,unpack1t1). The nested output witness is calculated, not merely asserted: wround(r):=wpack(wunpack(r)),wround(r):T~(pack0(unpack0t0),pack1(unpack1t1)). If a fiber of T~ or X~ has two inhabitants, the construction retains which witness was supplied. Replacing each fiber by a mere proposition would erase that distinction.

The phase sensitivity appears in the two components of this calculation. The map on static components sends the witness family X~ for the input type components to the same family for the output type components. The dynamic map sends each witness r to wround(r). Under bst, the dynamic maps are identified and only the tracked static component remains observable. Outside that open phase, the dynamic witness transformation is retained.

Theorem 14.32 — Phase-sensitive transport—imported

Let σ and τ be closed signature families of type Val(type)Sig. Let V:T:typeσ(T)τ(T) be a closed module functor. For closed Ti:Val(type), let Ui:σ(Ti) be closed inputs. For every family of α-small sets T~ indexed by pairs of closed values of T0 and T1, there is a function of phase-separated sets [[σ]](T~)[U0,U1][[τ]](T~)[V(T0,U0),V(T1,U1)], tracked by a function between the static components.

Proof of Theorem 14.32 — Phase-sensitive transport—imported

Proof. The displayed function of phase-separated sets is Sterling and Harper’s generalized abstraction theorem [SH21]. Unfolding the dependent-sum and dependent-product relational actions specializes its dynamic action to wround, while its static action preserves X~. The model construction and fundamental theorem that justify the general transport are imported; the two-line calculation is local. Neither is a consequence of principal matching in MLMod0. ◻

Exercise 14.8

★★☆ For the nested modules above, suppose a particular pair (t0,t1) has two distinct witnesses r1,r2:T~(t0,t1). Calculate the two output witnesses. State what remains under bst, what is erased by replacing the relation families with propositions, and why theorem 12.11 is not an instance of theorem 14.32 as presented in this chapter.

This comparison adds neither ModTT nor its model theory to the principal calculus. In particular, its generalized abstraction theorem does not transfer to Standard ML, the reduced matching judgment, applicative functors, modular implicits, or separate compilation. The local representation theorem above remains proof-irrelevant and tied to one counter interface.

When a generated name must leave scope

The annotation on module let is forced by an extrusion problem. The following is Crary’s comparison-language example, not a phrase of the reduced grammar in definition 12.1: that grammar has neither parameterized type components nor datatypes. In the comparison notation, intu means postfix application of the unary type component u to the type int, and datatype v=D of t introduces a fresh result type v with constructor D:tv. Consider a local generative declaration of t, followed by a sealed result with type au=t,datatype v=D of t,x:intu,y:boolu. The local name t cannot occur in the exported signature. Replacing it by one visible component is not principal. A signature may reveal au=v, or retain different relationships among u, v, x, and y; here u is a unary type component and D:tv is the datatype constructor. Two ordinary avoiding signatures can retain all four visible fields while assigning the constructor, respectively, D:intuvandD:booluv. The first signature types Dx but not Dy; the second types Dy but not Dx. Each therefore exposes a typing fact absent from the other, so neither signature subsigns the other. This incomparability does not by itself exclude a third ordinary signature below both. It does show that the two visible candidates do not determine a unique answer, and the reduced algorithm specified here has no least-avoidance construction. It therefore rejects the phrase or requests an annotation.

Focused avoidance.

Crary extends the signature language with existential signatures. In his calculus an existential signature is the least avoiding supersignature of the dependent body; consequently it lies below every admissible ordinary avoiding answer in the subsignature order. For user modules in synthesis contexts, focused synthesis is sound and complete for the declarative system, with subsignature completeness restricted to synthesis signatures on the left and analysis signatures on the right [Cra21]. Our reduced PFPL calculus has neither existential signatures nor that algorithm. We use the counterexample to justify annotations; no avoidance theorem is imported into theorem 12.13.

Exercise 12.8

★★☆ Give the constructor D type intuv in one ordinary avoiding signature and booluv in another, retaining x:intu and y:boolu. Check that the first types Dx but not Dy, while the second does the reverse; conclude that neither signature subsumes the other. Then write Crary’s existential answer t.σ(t), where σ(t) contains the unary type component u with au=t, the datatype v with constructor D:tv, and the fields x:intu and y:boolu; identify the existential binder that prevents t from escaping. This last signature belongs to Crary’s comparison calculus, not to the reduced grammar of definition 12.1.

Generativity and the applicative alternative

Applicative functors.

PFPL’s alternative adds exactly ΓF projectibleΓA valΓF(A) projectibleAppP,ΓQ projectibleΓQσ projectibleSealP. The first premise asserts that the functor expression is a stable path; the argument need only be a value. The second rule is required because an applicative functor body may seal its result. The price is a new equivalence problem: equality of result paths depends on equality of the functor, argument, and sealed module expressions, which may expose executable code to static comparison. A runtime conditional module therefore has no stable path. This is the complete boundary developed in PFPL Section 45.4, rules 45.7–45.8. We add neither rule to the principal calculus, and none of the generativity, elaboration, or principality results above should be reread applicatively.

The main calculus therefore follows a Standard-ML-style generative policy: each functor application may allocate fresh abstract identities. A common OCaml-style applicative policy instead equates repeated applications F(P).s only when the argument is a stable module path P; generativity can then be requested by applying a unit functor. PFPL’s displayed App-P is broader because its argument premise admits any value. These are three distinct policies, and the companion’s optional applicative mode is an experiment rather than a claim that they coincide.

Generated interfaces and incremental compilation.

Crary’s algorithm writes synthesized interfaces that may contain existential signatures. A downstream unit may compile against such a generated synthesis interface because it occurs in the context, while programmer-written modules and annotations remain existential-free user syntax. The right side of each subsignature query is therefore an analysis signature, exactly the condition used by completeness. This supports the incremental-compilation scenario of [Cra21]; it is not a separate-compilation theorem for Standard ML, MixML, or the reduced calculus of this chapter.

Recursive mixin linking.

MixML unifies structures and signatures as mixins with specified and defined components. Its published system supports recursive, higher-order, and first-class linking, proves soundness and completeness of a three-pass checker, and elaborates to an internal language with single-assignment references, recursive type generativity, and linear definedness [RD13]. This language is richer than our acyclic Sigma/Pi fragment. Its theorems are source-gated facts about MixML, not missing cases of theorem 12.7, theorem 12.13.

Cardelli and Wegner’s classification of universal, existential, inclusion, and ad-hoc polymorphism orients the package boundary [CW85]; it does not supply path, sharing, or matching theorems. The load-bearing module rules and projectibility boundary are the reduced PFPL system. The avoidance, applicative, and MixML paragraphs mark separate proof boundaries where a reader might otherwise conflate them.

None of the following problems is a prerequisite for a later chapter.

Suggested first pass.

Begin with exercise 12.9, exercise 12.10. They test the sharing calculation and the exact domain of elaboration. Then implement exercise 12.11; the final problem changes the functor policy explicitly.

Exercise 12.9

★★☆ Design a hierarchy containing an ordered element module, a set module, and a map module. Require both collection key types to share the ordered module’s static component while their representations remain distinct. Give one natural-number match and one near miss rejected by a singleton equation.

Exercise 12.10

★★☆ Choose a closed-result hierarchy containing two counters. Elaborate it to packages and products and trace one dynamic projection. Make the second component depend on the first path. Perform the manifest matching required before translation, or explain why the unresolved phrase lies outside theorem 12.7.

Exercise 12.11

★★★ Practical project.ml-module-checker Build a Kappa checker and elaborator for finite ordered, set, hierarchy, and functor signatures. Represent singleton identities by nominal stamps. Implement transparent matching, opaque sealing with fresh stamps, hierarchy sharing, and generative application. Print accepted and rejected cases, including a wrong-element set and two applications whose representation stamps differ. Only after the generative corpus passes, add an explicit applicative mode and document the equality it changes. The companion is artifacts/ch14-ml-modules/corpus.kp; its inline output oracle is the acceptance criterion. Record kappa check, kappa test, kappa run, and kappa audit, plus three independently replayed, typechecking semantic mutations. Maintain the invariant that a transparent path preserves its stamp, whereas every opaque seal and every generative functor application allocates a fresh stamp; matching may equate stamps only through an explicit sharing path. The exact successful output is:

PASS transparent preserves Nat
PASS dependent hierarchy sharing accepted
PASS wrong sharing rejected
PASS opaque seals allocate fresh names
PASS generative applications allocate fresh names
PASS applicative repeated application equal
PASS applicative arguments distinguished
PASS applicative functors distinguished
PASS hierarchy elaborates to pair
All 9 ML-modules corpus cases passed.

Exercise 12.12

★★☆ Temporarily add App-P and Seal-P. Explain why extending psig to F(A) requires an equality test on both F and the value A, rather than the four structural clauses of definition 12.12. Now suppose the syntax is extended by if b then M1 else M2, with runtime Boolean b. Show that neither new projectibility rule derives a stable path for this conditional, even when both branches have the same signature.

Search the book

Type to search the local edition.