Lectures onType Theory
Chapter 116
Chapter 116Optional

Typed Metaprogramming and Hygienic Elaboration

Prerequisites. Direct starred prerequisites: Proof-producing tactics are required only for the tactic-state interface; quotation, staging, and hygienic expansion use the smaller syntax-and-substitution route. No later core chapter depends on this route.

Consider a macro twice(e) that expands to let x=e in x+x. Expanding it inside a program that already binds x is unsafe when identifiers are represented by strings: the macro’s binder may capture occurrences supplied by the caller, or a caller binder may capture occurrences introduced by the macro. Renaming the printed variable after expansion is too late. The expander must record binding information while it constructs syntax.

Syntax objects and scope resolution

Definition 116.1 — Scoped identifiers and occurrence identity

Fix disjoint countable sets of atoms, raw binder tokens β, raw reference tokens ω, expanded origin tokens o, and scope stamps. Every raw syntax tree uses each binder token at most once. This global uniqueness condition is part of raw well-formedness, even when two binders lie in disjoint subtrees or at different staging levels. A raw binder is written aβ. Every raw reference is written aβω: its stable token is ω, and its lexical target is the particular binder token β, not the printed atom a. Thus in λaβ0.λaβ1.aβ0ω, the reference still targets β0 despite the intervening binder named a.

An expanded binding is a triple (a,Sb,o), where Sb is a finite scope set. An origin environment Ξ is a finite partial map o(a,Sb). Its entry is a candidate for a scoped reference aSr when SbSr. The partial operation resolveΞ(aSr) selects the origin of the unique candidate maximal by inclusion. It is undefined when there is no candidate or when two distinct candidates are maximal. Expanded references retain their raw token ω or carry the marker new(ot) naming a declared target origin ot.

The subset clause makes lexical information monotone. Comparing only the atom would resolve every printed x to the nearest textual binder and would reintroduce capture.

Definition 116.2 — Raw and scoped syntax

Raw terms are trees with typed binders r::=aβωλ(aβ:A).rrrquote(r)splice(r)m(r1,,rk). Scoped terms have the same constructors but every identifier is a scoped identifier and every reference resolves by definition 116.1. A syntax object is a scoped term together with its phase number. Raw syntax has no scoped-binding claim, but it is well formed only when its binder tokens are pairwise distinct and each lexical target β on a reference names the unique enclosing binder carrying β. Scoped syntax records enough data to check the corresponding expanded binding claim. Every source-typing and expansion judgment below ranges only over well-formed raw syntax.

Definition 116.3 — Provenance on scoped syntax

A transformer package P partitions its nodes into copies substituted from named input nodes and newly constructed nodes. For an introduction stamp i, marki(P)=h copies every substituted node byte-for-byte and adds i to every constructed identifier. Every constructed binder aS[o;new(o)] has its own origin. Every free constructed reference is marked new(ot), where ot is either such a constructed origin or an origin exported by the transformer-definition environment D. The latter records an origin environment ΞD for its exports.

The judgment D;Ξh prov is defined by structural recursion on scoped syntax. At a copied source reference aS[o;src(ω)], it requires resolveΞ(aS)=o. At a constructed reference aS[o;new(ot)], it requires resolveΞΞD(aS)=o=ot and requires ot to be a constructed origin in scope or an origin exported by D. At a transformer-created binder λ(aS[o;new(o)]:A).h0, it checks h0 under Ξ,o(a,S). A binder marked src(β) extends Ξ by the same clause. Applications check both subterms under Ξ, and quotation and splicing check their subterm under Ξ. Thus the origin environment is extended under every binder that a transformer creates; checking only free references is not the provenance judgment. Finally, the judgment requires every copied node to be unchanged. It fails on an unresolved, ambiguous, or mismatched reference, and it rejects duplicate binder origins.

Definition 116.4 — Hygienic expansion with provenance

The scoped expansion environment E¯ is a finite stack of records β(a,Sb,o). Lookup is by binder token, so shadowing an atom does not overwrite an older record. Its origin projection is πo(E¯)(β)=o, and its origin environment is Ξ(E¯)(o)=(a,Sb). These two projections are not source typing environments. Let L be the lexical scopes on the node being expanded and let U be the finite set of stamps already used. Write Scopes(D,E¯,r) for the finite set of stamps occurring in D, E¯, and r. An expansion judgment is well formed only when Scopes(D,E¯,r)U; the vector form uses the union of the component scope sets. The judgment D;E¯;n;L;UrhU is the least relation generated by the following rules. Argument vectors are expanded left to right by the two rules

 
D;E¯;n;L;U[][]U
Exp-Args-Nil
D;E¯;n;L;UrhU1D;E¯;n;L;U1rhU2
D;E¯;n;L;U(r,r)(h,h)U2
Exp-Args-Cons

Single terms are generated by the following six rules.

E¯(β)=(a,Sb,o)resolveΞ(E¯)(aSb)=o
D;E¯;n;L;UaβωaSb[o;src(ω)]U
Exp-Var
βdomE¯sUodomΞ(E¯)Orig(D)L=L{s}D;E¯,β(a,L,o);n;L;U{s}rhU
D;E¯;n;L;Uλ(aβ:A).rλ(aL[o;src(β)]:A).hU
Exp-Lam
D;E¯;n;L;Ur1h1U1D;E¯;n;L;U1r2h2U2
D;E¯;n;L;Ur1r2h1h2U2
Exp-App
D;E¯;n+1;L;UrhU
D;E¯;n;L;Uquote(r)quote(h)U
Exp-Quote
D;E¯;n;L;UrhU
D;E¯;n+1;L;Usplice(r)splice(h)U
Exp-Splice
D;E¯;n;L;UrhUkD(m)(h)=PiUkC(P)FP=C(P) pairwise distinctFP=domΞ(E¯)Orig(D)Orig(h)marki(P)=hD;Ξ(E¯)h prov
D;E¯;n;L;Um(r)h(Uk{i})
Exp-Macro

Here C(P) is the finite set of origins on constructed binders. The two fresh choices therefore name the finite sets they avoid: i avoids the threaded stamp set Uk, and every member of C(P) avoids FP. The transformer table stores an arity and an executable partial meta-function, not a trusted typing theorem. If it diverges or produces a package that fails prov, then Exp-Macro has no derivation.

Expand twice(x) under the caller binder x{c}. If the transformer chooses introduction stamp i, its output has the form let x{i}=x{c} in x{i}+x{i}. The caller occurrence resolves to the binder carrying c; the two introduced occurrences resolve to the binder carrying i. Equal printed atoms do no work in this calculation.

Theorem 116.5 — No accidental capture

Suppose D;E¯;n;L;UrhU. Every reference in h originating from an input subterm resolves to the image of the same input binding, and every reference introduced by a transformer resolves either to a binding introduced by that transformer or to a binding named explicitly in the transformer’s definition environment.

Proof of Theorem 116.5 — No accidental capture

Proof. Use mutual induction on term expansion and argument-vector expansion. The strengthened claims carry πo(E¯), which maps raw binder tokens to expanded origins, and Ξ(E¯), which maps those origins to atoms and scope sets. Exp-Args-Nil has no references. Exp-Args-Cons applies the term claim to its head and the vector claim to its tail under the threaded stamp set. In Exp-Var, lookup uses the stable token β, and resolveΞ(E¯)(aSb)=πo(E¯)(β) is the second premise. In particular, a reference whose token targets an outer binder keeps that binder’s scope set rather than acquiring the scope of an intervening same-atom binder.

In Exp-Lam, let o and s be the origin and stamp in the rule. The exact side conditions and the well-formedness invariant give sU,odomΞ(E¯)Orig(D). The body induction hypothesis is taken under E¯,β(a,L{s},o). Equivalently, provenance resolution traverses the output binder under Ξ(E¯),o(a,L{s}), exactly the binder clause of definition 116.3. Every older candidate lacks s, whereas the new binder has it. Hence the new binder is the unique maximal candidate for its source occurrences, and no older reference acquires the new stamp. The two induction hypotheses of Exp-App preserve both maps in the threaded stamp sets.

In Exp-Macro, argument syntax retains its scope sets and origin tokens by the copying clause of mark. The introduction stamp iUk is absent from every copied identifier, because all stamps on the expanded arguments belong to Uk. Hence a constructed binder cannot capture an argument reference. Traverse the marked package by D;Ξ(E¯)h prov. At each constructed binder the provenance judgment extends the origin environment before checking its body. Consequently a nested constructed reference resolves against all and only the constructed binders in lexical scope together with the caller and exported origins. The reference clause then gives its declared constructed origin or its declared origin in D. The disjointness C(P)FP= prevents a constructed origin from identifying an older origin. Exp-Quote and Exp-Splice change only the phase component and apply their term induction hypothesis. These cases cover every term and vector rule. ◻

Deleting the distinction between constructed and substituted syntax breaks the proof. If the stamp i is also attached to the argument x{c}, then it becomes x{c,i} and the introduced binder is a candidate; a maximality comparison may capture it.

Exercise 116.1

★★☆ Define a macro that expands swap(e1,e2) to two nested lets whose printed binders are both x. Assign concrete scope sets and show the unique resolver result for every occurrence when e1 itself contains a free printed x.

Typed stages

Hygiene prevents capture, but it does not prevent executing a run-time value during expansion.

Definition 116.6 — Staged typing

A staged context contains declarations x:A@n. The judgment Γne:A means that e is available at phase n. In addition to the ordinary typed lambda-calculus rules at a fixed phase, the calculus has

Γn+1e:A
Γnquote(e):Code(A)
Q-Quote
Γne:Code(A)
Γn+1splice(e):A
Q-Splice
(x:A@n)Γ
Γnx:A
Q-Var

There is no rule that uses x:A@n directly at phase n+1.

Definition 116.7 — Source typing

A source context Γ maps source origins to declarations A@n, and the source environment E0 maps binder tokens to source origins. In particular, E0 is not the scoped stack E¯ and is not the expanded-origin projection πo(E¯). A macro signature M records only an intended arity and source type M(m)=(A1,,Ak;A;n); it is not evidence that the executable transformer respects that type. Write E0;Γnsrcr:A. The ordinary variable, lambda, application, quotation, splice, and macro rules are

E0(β)=osΓ(os)=A@n
E0;Γnsrcaβω:A
Src-Var
βdomE0osdomΓranE0E0,βos;Γ,os:A@nnsrcr:B
E0;Γnsrcλ(aβ:A).r:AB
Src-Lam
E0;Γnsrcr1:ABE0;Γnsrcr2:A
E0;Γnsrcr1r2:B
Src-App
E0;Γn+1srcr:A
E0;Γnsrcquote(r):Code(A)
Src-Quote
E0;Γnsrcr:Code(A)
E0;Γn+1srcsplice(r):A
Src-Splice
M(m)=(A1,,Ak;A;n)E0;Γnsrcri:Ai(1ik)
E0;Γnsrcm(r1,,rk):A
Src-Macro

The first premise of Src-Macro checks a source call against its declared interface. It deliberately says nothing about the transformer output; that output is checked independently after expansion.

If x:N@0, the raw quotation quote(x+1) is rejected: its body needs x at phase one. If c:Code(N)@0, then quote(splice(c)+1) is typed by one application of Q-Splice followed by Q-Quote. The splice marks the only permitted cross-phase dependency.

Lemma 116.8 — Phase-indexed substitution

If Γ,x:A@n,Δke:B and Γnu:A, then Γ,Δ[u/x]ke[u/x]:B[u/x], provided every declaration in Δ is well formed at its recorded phase.

Proof of Lemma 116.8 — Phase-indexed substitution

Proof. By induction on the typing derivation of e. The matching variable case has k=n by Q-Var and is exactly the second premise. A different variable is preserved by context substitution. The lambda and application cases apply the induction hypotheses under the unchanged phase. In Q-Quote, apply the induction hypothesis to the premise at k+1 and rebuild quotation. In Q-Splice, write k=j+1, apply the induction hypothesis to the premise at phase j, and rebuild splice at phase k. The phase annotations rule out a variable occurrence at any unhandled phase. ◻

Definition 116.9 — Source–scoped compatibility

Let Γ^ map expanded origins to declarations. The judgment Compatρ(E0;Γ;E¯;Γ^) holds when ρ:domΓdomΓ^ is a bijection satisfying both of the following clauses:

  1. domE0=domE¯, domΓ=ranE0, and domΓ^=domΞ(E¯). If E0(β)=os and E¯(β)=(a,S,oh), then ρ(os)=oh=πo(E¯)(β);

  2. for every osdomΓ, Γ^(ρ(os))=Γ(os).

Thus ρ records an origin renaming, not an equality between source and expanded origins. For vectors, the judgments E0;Γnsrcr:A and Γ^nh:A mean that the corresponding judgment holds componentwise.

Theorem 116.10 — Mutual typing and provenance of macro-free expansion

Suppose Compatρ(E0;Γ;E¯;Γ^). The following two claims hold mutually.

  1. If r contains no macro call, E0;Γnsrcr:A, and D;E¯;n;L;UrhU, then Γ^nh:A and D;Ξ(E¯)h prov.

  2. If every component of r contains no macro call, E0;Γnsrcr:A, and D;E¯;n;L;UrhU, then Γ^nh:A, and every component of h satisfies provenance under D;Ξ(E¯).

Proof of Theorem 116.10 — Mutual typing and provenance of macro-free expansion

Proof. Prove the term and vector claims by mutual induction on their expansion derivations, inverting the source-typing derivation in each term case. The origin bijection is the invariant that relates a source variable to the scoped reference produced from it.

Variable case. Let E0(β)=os. By compatibility, E¯(β)=(a,Sb,ρ(os)) and Γ^(ρ(os))=Γ(os)=A@n. The resolver premise of Exp-Var gives resolveΞ(E¯)(aSb)=ρ(os). Hence Q-Var derives the target typing judgment, and the reference clause of provenance derives the second conclusion.

Binder case. Invert Src-Lam and Exp-Lam. Write os for the source origin and oh for the expanded origin chosen by the two rules. Their token and origin side conditions are βdomE0,βdomE¯,osdomΓranE0,ohdomΞ(E¯)Orig(D). The first two conditions ensure that extending either token map adds one new key rather than overwriting a same-token binder. The latter two ensure that extending either origin context adds one new origin. Therefore ρ=ρ{osoh} is a bijection between the extended context domains. It satisfies Compatρ(E0,βos;Γ,os:A@n;E¯,β(a,L,oh);Γ^,oh:A@n). Apply the term induction hypothesis to the body with ρ. It gives the body typing judgment under Γ^,oh:A@n and provenance under Ξ(E¯),oh(a,L). Lambda introduction gives the target type, and the binder clause of definition 116.3 gives provenance for the whole lambda.

Application and stage cases. In Src-App/Exp-App, the two term induction hypotheses give the function and argument typings and their provenance judgments; application elimination and the application provenance clause give the conclusions. In Src-Quote/Exp-Quote, apply the term induction hypothesis at phase n+1, then apply Q-Quote and the quotation provenance clause. In Src-Splice/Exp-Splice, apply it at phase n, then apply Q-Splice and the splicing provenance clause. The macro-free hypothesis excludes Src-Macro/Exp-Macro.

Vector cases. Exp-Args-Nil gives the empty target vector and an empty family of provenance derivations. In Exp-Args-Cons, the term induction hypothesis gives both conclusions for the head. The vector induction hypothesis gives them for the tail under the threaded stamp set U1. Combining the componentwise judgments proves the vector claim. These cases exhaust both inductive definitions. ◻

Definition 116.11 — Checked macro expansion

A call m(r) with M(m)=(A;A;n) is accepted relative to E0,Γ,E¯,Γ^ only when there is a bijection ρ such that Compatρ(E0;Γ;E¯;Γ^),E0;Γnsrcr:A, and expansion derives D;E¯;n;L;Um(r)hU. The ordinary staged checker then independently derives Γ^nh:A, and the accepted output must satisfy D;Ξ(E¯)h prov. The target judgment is deliberately under Γ^, not the source-origin context Γ. Neither the target derivation nor provenance is stored in D, and neither is inferred from the macro signature.

If a transformer loops, no Exp-Macro derivation exists. If it returns true where N was declared, expansion may succeed but definition 116.11 rejects the call. Thus the local theorem proves exactly the macro-free inductive system; typed macro output crosses a separate checker boundary rather than an assumed principal transformer case.

Generated declarations and the trust boundary

Definition 116.12 — Checked command expansion

A generated declaration contains a fresh global name c, a closed type A, and a term t. The command expander may propose the declaration only after hygienic expansion and staged checking. The environment is extended by c:A only when the ordinary kernel independently checks t:A. Name freshness is checked against the finite global signature.

A derived command derivePairMap(A,B) can therefore generate the declaration pairMap:(AA)(BB)A×BA×B with the term that maps the two projections. Expansion convenience does not alter the kernel rule for products.

Corollary 116.13 — Generated declarations preserve kernel trust

Every declaration admitted by definition 116.12 has a kernel derivation in the signature preceding its admission.

Proof of Corollary 116.13 — Generated declarations preserve kernel trust

Proof. Admission is defined to require exactly that derivation. Freshness ensures that checking the term cannot use the declaration being installed. ◻

The maximal-subset rule is executable because binding resolution enumerates a finite table and deletes dominated candidates. The binding table and the compile-time environment remain separate objects. The no-capture theorem is proved above for the smaller expander fixed in this chapter.

Generative and analytical macros. The expander above constructs syntax and never looks inside it. A macro that inspects its argument needs more: it must be told, at typing time, what a successful match makes available. The calculus that fixes this is separate from the expander above, and is frozen here.

Convention 116.14 — Lambda-triangle source card

Write λ for the comparison calculus. It is exactly the union of Figures 1–4 on printed pp. 5, 6, 8, and 11 of the companion technical report; no host-language feature is implicit. Its core signature is T::=CTTT,t::=cxλx:T.tttfix tttts match tp then tt else tebind(x:T;x:T),p::=def x=t in peval t,Γ::=Γ,x:iT,Σ::=Σ,x:T,Ω::=Ω,x=t. The notation bind(x:T;x:T) is this book’s unambiguous print name for the report’s bind-pattern glyph. It binds the matched subterm of result type T to x, permitting exactly the locally bound variables x:T. It is a pattern form, not an ordinary term constructor. All variables are globally unique, terms are identified up to renaming, and iN. The exact judgment signature is ΓΣit:T,Σp:T,tit,pΩppΩ,ΓpΣitp:TΓt,tstp/ttΦtt. Figure 1 supplies precisely T-Const, T-Var, T-Abs, T-App, T-Fix, T-Quote, and T-Splice; E-App-1, E-Abs, E-Beta, E-App-2, E-Fix, E-Fix-Red, E-Quote, E-Splice, and E-Splice-Red; and the eight value rules V-Const, V-Abs-0, V-Quote, V-Var, V-Fix, V-Abs, V-App, and V-Splice. Figure 2 adds exactly T-Link, T-Eval, T-Def, V-Eval, E-Link, E-Eval, E-Macro, and E-Compile. Figure 3 adds exactly T-Match, the six pattern-typing rules T-Pat-Const through T-Pat-Bind, the five match congruence/selection rules, the six pattern-reduction rules E-Pat-Const through E-Pat-Bind, and V-Match. Figure 4 adds only the library-pattern rules T-Pat-Link and E-Pat-Link, while threading Σ and Ω through the preceding judgments. These inventories and the four cited figures fix every premise, side condition, and conclusion used below.

In particular, quotation raises the staging level, splicing lowers it, and T-Splice requires i1. A binding x:iT is usable only at level i, so local cross-stage persistence is unavailable; library entries are stage-polymorphic through T-Link. Pattern typing returns the environment Γt available only in the then branch. Patterns contain constants, variables, abstraction, application, fix, and bind patterns, but no quotation, splice, or match form.

For the preservation theorem below, Definition 3.24 on printed p. 10 fixes the predicate WFSub(Γp,Γδ,Φ), printed in the source with the glyph : Φ is a bijection from dom(Γp) to dom(Γδ), the two domains are disjoint, and whenever xp:1TΓp, one has Φ(xp):1TΓδ.

Two macros make the phase structure necessary. A generative macro powCode(x,n) with x:N and n:N returns 1 when n is zero and x×powCode(x,n1) otherwise: it consumes an ordinary natural number and produces syntax. An analytical macro unliftBool(x) with x:2 matches x against tt and ff and returns the corresponding element of 2: it consumes syntax and produces an ordinary value. Their combination forces three distinct times. The library definition of powCode is compiled and stored in Ω before any call site is examined; that is generation time. A call site powCode(x,2) is a top-level splice, so it is reduced one stage earlier than the program that contains it, and it is exactly there that unliftBool may inspect a quoted argument; that is inspection time. The residual program is reduced last, at run time, by eval. Collapsing inspection time into run time would let a match observe a value that generation has not yet produced; collapsing it into generation time would let a macro inspect a call site that does not exist yet.

Theorem 116.15 — Soundness of λ ^

For the exact syntax and rules fixed in convention 116.14, the source proves the following package.

  1. Progress and preservation for terms at every staging level (Theorems 3.5 and 3.6). Progress is the closed statement: if it:T, then t is a value at level i or steps at level i. Preservation is the open statement: if Γit:T and tit, then Γit:T.

  2. Progress and preservation for programs over a well-typed library (Theorems 3.14 and 3.15): a well-typed program is a value or steps together with its library, and stepping preserves the program’s type under an extended signature.

  3. Preservation of pattern reduction (Theorem 3.25, printed p. 10) has exactly the five premises (1)Γ;Γδ1ts:T1,(2)Γp1tp:T1Γt,(3)Γ;Γt0t:T,(4)tstp/tΦt,(5)WFSub(Γp,Γδ,Φ). Its conclusion is Γ0t:T. Premise (5) has exactly the bijection, disjoint-domain, and level-one type clauses printed in convention 116.14. Preservation for a whole match is Corollary 3.26.

Proof of Theorem 116.15 — Soundness of λ ^

Proof. We reconstruct the progress and preservation inductions for the rule inventory of convention 116.14.

First prove substitution: if ΓΣju:A and Γ,x:jAΣit:B, then ΓΣit[u/x]:B. Induct on the second typing derivation. The variable case returns u when the variable is x and uses lookup otherwise. Under abstraction, quotation, splice, and pattern binders, alpha-rename the binder and apply the induction hypothesis in the extended context. Application and match use the induction hypotheses for all term premises; constants, links, and pattern constants are unchanged. Fixpoint uses the induction hypothesis on its function premise. These cases cover all term forms in the card. Repeating the lemma for a finite list of distinct variables gives simultaneous substitution.

For term progress strengthen the claim to a context containing only bindings at levels at least 1. Induct on term typing. A constant is a value. A variable at level 0 cannot be found in the restricted context; at a positive level it is a value. At level 0, an abstraction is a value; at a positive level its body is a value or steps by induction, yielding V-Abs or E-Abs. For application at level 0, step the left operand, then the right; if both are values, canonical forms makes the left operand an abstraction and E-Beta steps by substitution. At a positive level, either component steps by the corresponding congruence rule or both are values and V-App applies. A level-zero fixpoint either steps inside or its value argument is an abstraction and E-Fix-Red unfolds it; at a positive level it is a value exactly when its argument is. Quotation applies the induction hypothesis one level higher and is otherwise a value. A splice cannot be typed at level 0. At level 1, its operand steps at level 0, or canonical forms makes it a quotation and E-Splice-Red applies; above level 1, it steps inside or is a staged value. For a match, first step the scrutinee; when it is a quotation, the six pattern-reduction rules either produce a successful substituted then-branch or select the else branch, and a positive-stage match is otherwise a value. A library link at level 0 steps by E-Link because well-typedness of the library supplies its definition; at positive level it is a value. These are the Figure 1–4 typing families. Taking the restricted context empty proves term progress.

For term preservation, induct on typing and invert the step. Congruence steps for abstraction, application, fixpoint, quotation, splice, and match rebuild the same typing rule from the induction hypothesis. Beta and fixpoint unfolding use substitution. Splice–quotation reduction inverts T-Quote under T-Splice. A link step uses the definition typing in the well-typed library. The only remaining reduction is successful pattern reduction; it is handled below. Thus every term step preserves its type.

For program progress, induct on Σp:T. In evalt, term progress either steps t by E-Eval or makes the program a value. In def x=t in p, term progress at level 1 either steps the definition body by E-Macro, or makes it a value and E-Compile extends Ω with x=t. Program preservation uses term preservation in the first alternatives. In the compile alternative extend Σ with x:Tx; weakening types the residual program under the extension, and the typing of t makes the extended library well typed. This proves both program theorems.

It remains to prove pattern preservation. Induct on the pattern-typing derivation, retaining all five premises printed in item 3. A pattern constant inverts the scrutinee constant and returns the already typed branch. A pattern variable adds no then-branch binding and leaves the branch unchanged. A library pattern is disjoint from the local pattern domain by WFSub, so only E-Pat-Link applies and again leaves the branch unchanged. For abstraction, application, and fix patterns, inversion of the scrutinee typing exposes exactly the component typings required by the recursive pattern premise; apply the induction hypotheses in the order of the pattern-reduction derivation and rebuild the enclosing typing. For a bind pattern, the bijection clause of WFSub pairs every level-one pattern variable with a distinct level-one scrutinee variable of the same type. The branch premise is therefore well typed after the simultaneous substitution supplied by the first paragraph. Disjointness prevents capture, and the domain equality shows that every branch binding is replaced exactly once. Hence the reduced branch has type T at level 0.

Those are the six pattern forms, so pattern reduction is preserving. Inverting T-Match and applying this result proves preservation for a whole match, completing term preservation and item 3. The proof used only the frozen calculus. It states no hygiene result, no result for definition 116.6, and no product-macro theorem. ◻

Two boundaries hold. The theorem concerns the printed calculus and proves no compiler-correctness statement. Analytical inspection is not unrestricted reflection: the pattern-typing judgment matches only the simply typed fragment with fix, and what the then branch may use is exactly the output environment Γt, so a match cannot decompose a quotation, a splice, or a term whose bindings it did not itself introduce.

Sources

The maximal-subset resolver and the separation between binding tables and compile-time environments follow Flatt’s executable model [Fla16]. The λ card follows Stucki, Brachthäuser, and Odersky [SBO21]; its rules are Figures 1–4 on printed pp. 5, 6, 8, and 11 of the companion technical report.

Suggested first pass.

None of these problems is a prerequisite for a later chapter. Begin with exercise 116.2, then complete exercise 116.4.

Exercise 116.2

★★☆ Let Γ0=(c:Code(N)@0). Draw the complete inference tree, including the ordinary addition premises, for Γ00quote(splice(c)+1):Code(N). Every node must display its phase and type. Next use the context Γ1=(d:Code(N)@1). Draw the complete nested tree for Γ10quote(quote(splice(d))):Code(Code(N)). The innermost variable premise is at phase one, the splice conclusion is at phase two, and the two quotation conclusions are at phases one and zero. Finally, for Γx=(x:N@0), draw the attempted tree for quote(x+1) and identify its missing judgment Γx1x:N. A list of rule names is not a complete tree.

Exercise 116.3

★★★ In λ as fixed in convention 116.14, write the analytical macro unliftBool with both branches. Give the pattern-typing judgment of each pattern together with its output environment, state the result type of the then and else branches, and exhibit one pattern that the pattern-typing judgment rejects because it is outside the simply typed fragment the source permits.

Exercise 116.4

★★★ Practical project.scoped-hygienic-macro-expander Implement in Agda or Kappa scoped identifiers, maximal-subset resolution, the twice transformer, and a finite executable observation model for the quotation/splicing fragment. The observation model has naturals, Booleans, functions, quotation, splicing, a dedicated generative-double form, and a dedicated analytical-Boolean form. Those last two forms are test probes, not constructors of the exact λ card in convention 116.14; the project therefore does not implement the card’s libraries, fix, general bind patterns, or pattern typing. Keep E0:βos distinct from E¯:β(a,S,oh), and check a finite bijection ρ such that ρ(E0(β))=πo(E¯)(β). Provenance traversal must extend its origin environment beneath each constructed binder. Introduced stamps avoid all used and input-identifier stamps; constructed origins avoid caller and exported origins; variables occur only at their recorded phase.

Print the following input–output pairs:

caller-x caller-reference-preserved
nested-x introduced-reference-local
ambiguous rejected: ambiguous-binding
origin-renaming origin-renaming-preserved
nested-transformer-binders constructed-binder-provenance
nested-quotation nested-quotation-typed
generated-double generated-normal-form
analyse-true analytical-true-branch
ill-staged rejected: ill-staged
beta-collision beta-capture-avoided

Also run the absent-target counteroracle subst 2 (var 1) (λ1:N.var 1). It must print absent-target-alpha-preserved; the fresh-name bound must include the substitution target as well as the replacement and body. The nested quotation must be the exact tree from exercise 116.2. Mutating copied syntax with the introduction stamp, omitting the provenance extension, ignoring a variable’s phase, or reversing the true analytical branch must make the corresponding oracle fail. The program checks this finite observation model; it is not a mechanization of the calculus-wide progress and preservation proof. Its two probe constructors do not implement general lambda-triangle patterns.

Search the book

Type to search the local edition.