Partial Evaluation, Binding-Time Analysis, and the Futamura Projections
Prerequisites. Direct starred prerequisites: Chapter 17. No later core chapter depends on this route.
An interpreter for a fixed source program performs the same dispatch whenever its dynamic input changes. Evaluation cannot perform that dispatch before the input is known. A specializer must evaluate the source-determined work, emit the input-dependent work, and preserve divergence as well as returned values.
A residual term is the missing result
The shared source language is Jones–Gomard–Sestoft’s exact first-order Scheme0 card.
𝑃::=(𝑑1,…,𝑑𝑚),𝑑::=𝖽𝖾𝖿𝗂𝗇𝖾(𝑓⃗𝑥)𝑒,𝑒::=𝑐∣𝑥∣𝗂𝖿𝑒0𝑒1𝑒2∣𝖼𝖺𝗅𝗅𝑓⃗𝑒∣𝗈𝗉(⃗𝑒),𝑐::=𝑛∣𝗊𝗎𝗈𝗍𝖾(𝑣),𝗈𝗉::=𝖼𝖺𝗋∣𝖼𝖽𝗋∣𝖼𝗈𝗇𝗌∣=∣+∣⋯. The first definition is the goal. Programs are statically scoped, call-by-value, first order, and purely applicative; function values, partial application, lambda, assignment, and side-effecting primitives are absent. With 𝑃(𝑓)=(⃗𝑥,𝑒𝑓), environment 𝜌, and partial primitive interpretation 𝛿, write 𝑃;𝜌⊢𝑒⇓𝑣. The list judgment 𝑃;𝜌⊢⃗𝑒⇓⃗𝑣 evaluates left to right. The complete big-step schemas are
𝑃;𝜌⊢𝑐⇓[[𝑐]]
S0-Const
𝜌(𝑥)=𝑣
𝑃;𝜌⊢𝑥⇓𝑣
S0-Var
𝑃;𝜌⊢⃗𝑒⇓⃗𝑣𝛿(𝗈𝗉,⃗𝑣)=𝑣
𝑃;𝜌⊢𝗈𝗉(⃗𝑒)⇓𝑣
S0-Op
𝑃;𝜌⊢𝑒0⇓𝖿𝖺𝗅𝗌𝖾𝑃;𝜌⊢𝑒2⇓𝑣
𝑃;𝜌⊢𝗂𝖿𝑒0𝑒1𝑒2⇓𝑣
S0-If-F
𝑃;𝜌⊢𝑒0⇓𝑣0𝑣0≠𝖿𝖺𝗅𝗌𝖾𝑃;𝜌⊢𝑒1⇓𝑣
𝑃;𝜌⊢𝗂𝖿𝑒0𝑒1𝑒2⇓𝑣
S0-If-T
𝑃;𝜌⊢⃗𝑒⇓⃗𝑣𝑃(𝑓)=(⃗𝑥,𝑒𝑓)𝑃;[⃗𝑥↦⃗𝑣]⊢𝑒𝑓⇓𝑣
𝑃;𝜌⊢𝖼𝖺𝗅𝗅𝑓⃗𝑒⇓𝑣
S0-Call
Here [[𝑐]] is the value represented by the literal or quotation 𝑐. The rules are partial: an undefined primitive and a divergent call have no derivation. We write 𝗋𝗎𝗇0(𝑃,⃗𝑣)=𝑤 only for a finite derivation. This card is the language of the historical 𝗆𝗂𝗑 below [JGS93].
Fix natural numbers with total addition and multiplication. For a natural exponent 𝑘, define 𝗉𝗈𝗐𝖾𝗋(𝑘,𝑥)={1,𝑘=0,𝑥⋅𝗉𝗈𝗐𝖾𝗋(𝑘−1,𝑥),𝑘>0. At 𝑘=3, the tests and decrements depend only on 3. The three multiplications depend on 𝑥. Performing the former and retaining the latter gives 𝑥⋅(𝑥⋅(𝑥⋅1)).
Let variables range over a countable set. Expressions and residual expressions share the grammar 𝑒,𝑟::=𝑛∣𝑥∣𝑒1+𝑒2∣𝑒1⋅𝑒2∣𝗂𝖿𝟢𝑒0𝗍𝗁𝖾𝗇𝑒1𝖾𝗅𝗌𝖾𝑒2. An environment 𝜌:𝑥↦𝑛 is finite. The partial function 𝖾𝗏𝖺𝗅(𝑒,𝜌) evaluates operators left to right and selects one conditional branch after evaluating its test. We write 𝖾𝗏𝖺𝗅(𝑒,𝜌)=𝑛 only when this computation returns 𝑛. A static result is 𝗄𝗇𝗈𝗐𝗇(𝑛). A dynamic result is 𝗋𝖾𝗌(𝑟). Define 𝗅𝗂𝖿𝗍(𝗄𝗇𝗈𝗐𝗇(𝑛))=𝑛 and 𝗅𝗂𝖿𝗍(𝗋𝖾𝗌(𝑟))=𝑟.
There is a direct translation into Scheme0: translate 𝑛 to the numeral constant, 𝑒1+𝑒2 and 𝑒1⋅𝑒2 to primitive applications, and 𝗂𝖿𝟢𝑒0𝗍𝗁𝖾𝗇𝑒1𝖾𝗅𝗌𝖾𝑒2 to 𝗂𝖿(=𝑒00)𝑒1𝑒2. Thus this is a numeric projection of Scheme0, not a second presentation of its grammar. It omits quoted lists and calls until section 127.2; the historical self-applicability theorem is never inferred from this smaller definition.
Let 𝜌𝑠 contain the variables known during specialization. The judgment 𝜌𝑠⊢𝗉𝖾𝑒⇓𝑞 is generated by these rules.
Proof. Induct on the specialization derivation. Rules PE-Num and PE-Static leave no variable. Rule PE-Dynamic retains its one variable, whose side condition puts it outside the static domain. Each operator conclusion takes the union of the free variables in its premises. The two known-test rules retain only the selected branch. Rule PE-If-D takes the union of the test and both branches. These are all rules. ◻
Let the domains of 𝜌𝑠 and 𝜌𝑑 be disjoint, and suppose their union assigns every free variable of 𝑒. If 𝜌𝑠⊢𝗉𝖾𝑒⇓𝑞, then for every 𝑛, 𝖾𝗏𝖺𝗅(𝑒,𝜌𝑠∪𝜌𝑑)=𝑛⟺𝖾𝗏𝖺𝗅(𝗅𝗂𝖿𝗍(𝑞),𝜌𝑑)=𝑛.
Proof of Theorem 127.4 — Online specialization equation
Proof. Induct on the specialization derivation. The numeral and variable cases follow by lookup. In PE-Op-S, both operands evaluate to the recorded numerals. In PE-Op-D, apply the two induction hypotheses in left-to-right order and rebuild the operator evaluation.
For PE-If-Z, the test induction hypothesis gives zero in the combined environment. Both evaluations enter the first branch, where the branch induction hypothesis gives the equivalence. Rule PE-If-N uses the second branch and the recorded nonzero value. In PE-If-D, the residual test and source test have the same value by the induction hypothesis. They select the same branch, whose induction hypothesis gives the result. ◻
★☆☆ Specialize 𝗂𝖿𝟢𝑥𝗍𝗁𝖾𝗇(2+3)𝖾𝗅𝗌𝖾(𝑦⋅1) under the empty static environment. Give the complete derivation and check theorem 127.4 at (𝑥,𝑦)=(0,7) and (1,7).
For the recursion argument, extend definition 127.2 with calls. A numeric call program is a finite list of equations 𝑓(⃗𝑥𝑠;⃗𝑥𝑑)=𝑒𝑓, separating static and dynamic parameters. Its source evaluation extends 𝖾𝗏𝖺𝗅 with
The numeral, variable, operation, and conditional rules are the big-step counterparts of 𝖾𝗏𝖺𝗅 in definition 127.2; vector evaluation is left to right. This card translates into Scheme0 by merging the two formal-parameter lists and applying the translation above. A static call pattern is a pair (𝑓,⃗𝑛). A memo table maps such a pattern to one residual function name and, after the recursive call returns, its equation. Revisiting a pattern emits a call to that name; encountering a new pattern first allocates a fresh name and then specializes its body. Different static tuples are distinct patterns, so this policy is polyvariant.
The call 𝑔(𝑛;𝑥)=𝑔(𝑛+1;𝑥) generates the infinite pattern sequence (𝑔,0),(𝑔,1),…. Memoization alone does not terminate.
The judgment 𝑏;𝜇;𝜌𝑠⊢𝗉𝖾𝑒⇓𝑞;𝜇′ extends the preceding rules with a natural budget 𝑏 and memo table 𝜇. A table entry has the form (𝑓,⃗𝑛)↦ℎ while its equation is being constructed and (𝑓,⃗𝑛)↦(ℎ,ℎ(⃗𝑥𝑑)=𝑟ℎ) afterward. The sequential vector judgments are the left-to-right lift of this scalar judgment; in particular, 𝗄𝗇𝗈𝗐𝗇(⃗𝑛) means that every component is known. A syntax rule passes 𝑏 unchanged. Calls use exactly these rules, where 𝑃(𝑓)=(⃗𝑥𝑠;⃗𝑥𝑑;𝑒𝑓), 𝜋=(𝑓,⃗𝑛), and ̂⃗𝑞 maps 𝗅𝗂𝖿𝗍 componentwise.
Let 𝐹0 contain each source function whose call was retained by PE-Fuel. Define 𝐹𝑖+1 by adjoining every source function called in the body of a function in 𝐹𝑖, and put 𝐹∞=⋃𝑖𝐹𝑖. Because 𝑃 is finite, this union stabilizes. The residual program 𝑃𝜇′ consists of the completed equations in 𝜇′ together with the source equations in 𝑃 whose names lie in 𝐹∞. Thus retained source code is closed under residual calls. A pending entry is callable only by a recursive PE-Call-Hit; its equation is the equation being constructed by the unique enclosing PE-Call-New.
The two entry states are not merely an implementation detail. A proof that uses a recursive hit must relate the hit to the equation that will exist after the allocating call finishes.
Call 𝜇 a table prefix of 𝜇∗ when 𝜇∗ preserves every name allocated by 𝜇, completes every pending entry of 𝜇, and never changes a completed equation. For 𝑘∈ℕ, write I𝑘(𝜇,𝜇∗) when 𝜇 is a table prefix of 𝜇∗ and the following conditions hold.
Each pending entry 𝜋↦ℎ in 𝜇 has one allocating PE-Call-New ancestor, and 𝜇∗ contains the equation produced by that ancestor.
For every completed entry (𝑓,⃗𝑛)↦(ℎ,ℎ(⃗𝑥𝑑)=𝑟ℎ) in 𝜇∗, every dynamic environment 𝜌𝑑, every numeral 𝑚, and every source or residual evaluation derivation of height below 𝑘, 𝑃;[⃗𝑥𝑠↦⃗𝑛,⃗𝑥𝑑↦𝜌𝑑(⃗𝑥𝑑)]⊢𝑒𝑓⇓𝑚⟺𝑃𝜇∗;𝜌𝑑⊢𝑟ℎ⇓𝑚.
Every source function retained at fuel zero is accompanied by the complete call-reachable source-equation closure 𝐹∞.
The height bound applies to the derivation on the side from which an implication starts. It makes the invariant inductive even when a pending back edge calls its own eventual equation.
Suppose proof search starts with 𝜇, returns 𝑏;𝜇;𝜌𝑠⊢𝗉𝖾𝑒⇓𝑞;𝜇∗, and every entry of 𝜇∗ is completed. If pending entries of 𝜇 have their unique allocating ancestors in that derivation, then I𝑘(𝜇,𝜇∗) holds for every 𝑘. Under the same hypotheses, 𝑒 and 𝗅𝗂𝖿𝗍(𝑞) have equal finite-return observations for derivations of height below 𝑘.
Proof of Lemma 127.7 — Memo completion and finite simulation
Proof. Use strong induction on 𝑘, simultaneously for every table entry and for the expression at every specialization subderivation. Inside the height induction, induct on that fixed specialization derivation. Numerals, variables, primitives, and conditionals use the corresponding argument or selected-branch induction hypotheses.
For PE-Call-Hit, memo-table lookup fixes the name in the conclusion. If its entry is pending, condition 1 identifies the unique enclosing allocator and the equation that the final table supplies; if it is completed, that equation belongs to the completed entry and condition 2 applies. In either case, inversion of a finite N-Call derivation exposes an evaluation of the equation body whose height is strictly below the height of the call. Apply the outer induction hypothesis to that body and rebuild the call on the other side. This argument works in both directions and is the step that justifies a recursive back edge.
For PE-Call-New, allocation preserves all older names, the body premise supplies the new equation, and completion changes only the state of that entry. Apply the inner induction hypotheses to the argument derivations and the outer induction hypothesis to each strictly shorter finite body evaluation. Table extensions are harmless by the same evaluation-derivation argument later recorded as residual-program monotonicity: allocation uses fresh names, completion preserves old equations, and retained closure only grows. For PE-Fuel, both sides invoke the same retained source equation; condition 3 supplies every later callee. These are the three call rules. Freshness gives unique names, 𝖼𝗈𝗆𝗉𝗅𝖾𝗍𝖾 gives one final equation per allocation, and the construction of 𝐹∞ gives condition 3. Hence the simultaneous claims hold at 𝑘+1, and induction establishes them for every 𝑘. ◻
Suppose 𝜇1 is extended to 𝜇2 only by allocating fresh residual names, completing pending equations, or enlarging the retained-function set. If 𝑃𝜇1;𝜌⊢𝑟⇓𝑛, then 𝑃𝜇2;𝜌⊢𝑟⇓𝑛.
Proof of Lemma 127.8 — Residual-program monotonicity
Proof. Induct on the finite evaluation derivation. Numeral, variable, operator, and conditional rules do not inspect the program. In the call case, the equation used by the premise is either a completed residual equation or a retained source equation. Extension preserves completed entries, uses globally fresh names for new entries, and only enlarges the call-reachable retained set. Therefore 𝑃𝜇2 retains the equation used by the premise; apply the induction hypotheses to the argument and body derivations and rebuild N-Call. ◻
For every finite program, expression, environment, and budget, deterministic proof search from the empty memo table has no infinite search path: it returns either a unique specialization derivation or a static failure. A static failure occurs, for example, when a declared static argument does not specialize to 𝗄𝗇𝗈𝗐𝗇(𝑛), or when an attempted static primitive is undefined. If 𝑏;∅;𝜌𝑠⊢𝗉𝖾𝑒⇓𝑞;𝜇′, then source and residual evaluation have the same finite-return observations: for every dynamic environment 𝜌𝑑 and numeral 𝑛, 𝑃;𝜌𝑠∪𝜌𝑑⊢𝑒⇓𝑛⟺𝑃𝜇′;𝜌𝑑⊢𝑟⇓𝑛, provided every encountered primitive is defined and 𝑟=𝗅𝗂𝖿𝗍(𝑞). The statement makes no claim about coinductive divergence or executions stuck at an undefined primitive.
Proof of Theorem 127.9 — Safe-fuel search termination and preservation
Proof. Implement proof search by the ordered, pairwise-disjoint rule alternatives. Order its recursive calls lexicographically by budget and expression size. A syntax-directed rule searches strict subexpressions. Unfolding a new call pattern decreases the budget. A repeated pattern and PE-Fuel emit syntax and make no recursive call on a function body. The order is well founded, so search returns either the unique derivation selected by the rules or a finite static-failure result. This first conclusion does not assert that every input has a derivation.
For preservation, the successful search from the empty table has a final completed table: every allocation is completed while its enclosing PE-Call-New returns. Apply lemma 127.7 at a height strictly larger than the finite evaluation derivation in the implication under consideration. The lemma’s expression clause gives that implication, and applying it from the other side gives the converse. Quantifying over all finite heights proves the displayed equivalence. This argument does not turn absence of a finite derivation into a divergence theorem. ◻
Fuel controls transformation time, not residual size. A dynamic conditional can duplicate a large continuation. For size control, extend residual syntax with 𝗅𝖾𝗍𝑧=𝑟𝗂𝗇𝑟′ and the single source rule
If 𝑃;𝜌⊢𝑟⇓𝑣, then for every expression 𝑟′ and numeral 𝑛, 𝑃;𝜌⊢𝑟′[𝑟/𝑧]⇓𝑛⟺𝑃;𝜌[𝑧↦𝑣]⊢𝑟′⇓𝑛. Substitution is capture avoiding; the binding of 𝑧 has no scope in 𝑟.
Proof of Lemma 127.10 — Evaluation decomposition for substitution
Proof. Induct structurally on 𝑟′, with a subsidiary induction on the displayed finite evaluation derivation for each direction. At the variable 𝑧, the left side is the assumed derivation of 𝑟, while the right side is lookup of 𝑣. A different variable uses the same lookup on both sides. Primitive and conditional cases apply the induction hypotheses in evaluation order; determinism of 𝑃;𝜌⊢𝑟⇓𝑣 ensures that multiple substituted occurrences yield the same 𝑣. In a call, apply the induction hypotheses to every actual argument, then use the identical source equation and environment for the body. Alpha-renaming handles a binder in an extended residual language. These are every expression constructor, so both implications follow. This reverse implication is an anti-substitution lemma; it is not obtained by reading the ordinary substitution lemma backward. ◻
Proof of Proposition 127.11 — Let-insertion preservation
Proof. Invert or introduce N-Let, then apply lemma 127.10 in the required direction. The termination hypothesis is necessary under call by value: when 𝑧∉fv(𝑟′), insertion evaluates 𝑟 even though substitution does not. ◻
This preservation theorem does not bound the number of distinct static call patterns.
★★☆ Specialize 𝑔(𝑛;𝑥)=𝑔(𝑛+1;𝑥) from static input 0 with budgets zero, one, and two. Write the residual call at exhaustion and prove that replacing it by zero would violate theorem 127.9.
The second card is the two-level Scheme0 syntax of Jones–Gomard–Sestoft. For 𝑏∈{𝑆,𝐷}, a division 𝜏 maps variables to binding times. The annotated constructs are 𝑒𝑏::=𝑐∣𝑥∣𝗈𝗉𝑠(⃗𝑒)∣𝗈𝗉𝑑(⃗𝑒)∣𝗂𝖿𝑠(𝑒0,𝑒1,𝑒2)∣𝗂𝖿𝑑(𝑒0,𝑒1,𝑒2)∣𝖼𝖺𝗅𝗅𝑠𝑓(⃗𝑒𝑠)(⃗𝑒𝑑)∣𝖼𝖺𝗅𝗅𝑑𝑓(⃗𝑒𝑠)(⃗𝑒𝑑)∣𝗅𝗂𝖿𝗍(𝑒). The two argument lists at a call are the callee’s static and dynamic parameters. The subscript on a call instead selects unfolding (𝑠) or residualization (𝑑); these are different decisions. The exact checking rules are:
𝜏⊢𝑐:𝑆
BT-Const
𝜏(𝑥)=𝑏
𝜏⊢𝑥:𝑏
BT-Var
𝜏⊢𝑒𝑖:𝑆(1≤𝑖≤𝑎)
𝜏⊢𝗈𝗉𝑠(𝑒1,…,𝑒𝑎):𝑆
BT-Op-S
𝜏⊢𝑒𝑖:𝐷(1≤𝑖≤𝑎)
𝜏⊢𝗈𝗉𝑑(𝑒1,…,𝑒𝑎):𝐷
BT-Op-D
𝜏⊢𝑒0:𝑆𝜏⊢𝑒1:𝑏𝜏⊢𝑒2:𝑏
𝜏⊢𝗂𝖿𝑠(𝑒0,𝑒1,𝑒2):𝑏
BT-If-S
𝜏⊢𝑒0:𝐷𝜏⊢𝑒1:𝐷𝜏⊢𝑒2:𝐷
𝜏⊢𝗂𝖿𝑑(𝑒0,𝑒1,𝑒2):𝐷
BT-If-D
𝜏⊢𝑒𝑖:𝑆(1≤𝑖≤𝑎)
𝜏⊢𝖼𝖺𝗅𝗅𝑠𝑓(𝑒1,…,𝑒𝑎)():𝑆
BT-Call-S0
When 𝑚<𝑎, both call forms with dynamic arguments have binding time 𝐷:
𝜏⊢𝑒𝑖:𝑆(1≤𝑖≤𝑚)𝜏⊢𝑒𝑖:𝐷(𝑚<𝑖≤𝑎)
𝜏⊢𝖼𝖺𝗅𝗅𝑠𝑓(𝑒1,…,𝑒𝑚)(𝑒𝑚+1,…,𝑒𝑎):𝐷
BT-Call-S
𝜏⊢𝑒𝑖:𝑆(1≤𝑖≤𝑚)𝜏⊢𝑒𝑖:𝐷(𝑚<𝑖≤𝑎)
𝜏⊢𝖼𝖺𝗅𝗅𝑑𝑓(𝑒1,…,𝑒𝑚)(𝑒𝑚+1,…,𝑒𝑎):𝐷
BT-Call-D
𝜏⊢𝑒:𝑆
𝜏⊢𝗅𝗂𝖿𝗍(𝑒):𝐷
BT-Lift
For 𝖽𝖾𝖿𝗂𝗇𝖾(𝑓(𝑥1,…,𝑥𝑚)(𝑥𝑚+1,…,𝑥𝑎))𝑒, checking uses 𝜏𝑓(𝑥𝑖)=𝑆 for 𝑖≤𝑚 and 𝐷 otherwise; its body must have binding time 𝑆 when 𝑚=𝑎, and 𝐷 when 𝑚<𝑎. These are exactly the rule families of the source card [JGS93].
The check becomes operational only after its two representations are fixed. Write 𝑞::=𝗏𝖺𝗅(𝑣)∣𝖼𝗈𝖽𝖾(𝑟), with ⌊𝗏𝖺𝗅(𝑣)⌋=𝑣 and ⌊𝖼𝗈𝖽𝖾(𝑟)⌋=𝑟. A specialization environment 𝜉=(𝜌𝑠;𝜂𝑑) maps static variables to source values and dynamic variables to residual expressions. The judgment 𝑃𝑏;𝜉⊢𝗈𝖿𝖿𝑒𝑏⇓𝑞 is the least relation generated by the following rules. The vector forms apply the scalar rules left to right.
Rule Off-Call-D names the residual state reached at the displayed static tuple. The rules determine residual expressions; the following graph card determines the residual program used by the soundness theorem.
A completed offline specialization graph for 𝑃𝑏 and a main static input is a finite triple (𝜈,𝐸,𝑟0) satisfying four conditions.
The finite domain of 𝜈 consists of reached states (𝑓,⃗𝑣), and 𝜈(𝑓,⃗𝑣)=𝑓⃗𝑣 is injective.
For every (𝑓,⃗𝑣)∈dom(𝜈), if 𝑃𝑏(𝑓)=(⃗𝑥𝑠;⃗𝑥𝑑;𝑒𝑏𝑓), then 𝑃𝑏;([⃗𝑥𝑠↦⃗𝑣];[⃗𝑥𝑑↦⃗𝑥𝑑])⊢𝗈𝖿𝖿𝑒𝑏𝑓⇓𝖼𝗈𝖽𝖾(𝑟𝑓,⃗𝑣), and 𝐸 contains exactly the equation 𝑓⃗𝑣(⃗𝑥𝑑)=𝑟𝑓,⃗𝑣.
Every residual call 𝑔⃗𝑤(⃗𝑟) occurring in 𝑟0 or an equation body in 𝐸 has (𝑔,⃗𝑤)∈dom(𝜈).
Specializing the main expression under its static input and identity residual environment yields 𝖼𝗈𝖽𝖾(𝑟0).
Construction is deterministic: allocate 𝑓⃗𝑣 before specializing the state’s body; a repeated state reuses that name. It returns the residual program 𝑃𝑠=(𝐸,𝑟0) only when this reachability expansion terminates. A revisit is a back edge and does not expand the graph. These are the memoization and reachability-closure conditions of the source specializer [JGS93].
Constraint generation erases the subscripts, assigns an unknown in {𝑆,𝐷} to every occurrence and formal parameter, and reads the premises of the displayed rules as equations. For example, a static operator generates 𝑏𝑖=𝑆; a dynamic conditional generates 𝑏0=𝑏1=𝑏2=𝐷; and a call equates each argument with the corresponding formal division. A boundary 𝑆<𝐷 is repaired only by inserting 𝗅𝗂𝖿𝗍. Starting with every unknown at 𝑆, repeatedly changing a violated unknown to 𝐷 computes the least solution because every generated constraint is monotone on the two-point lattice.
Suppose 𝜏⊢𝑒:𝑏, 𝜉 maps every 𝑆-variable to a source value and every 𝐷-variable to residual syntax, and 𝑃𝑏;𝜉⊢𝗈𝖿𝖿𝑒⇓𝑞. Then 𝑞=𝗏𝖺𝗅(𝑣) for some 𝑣 when 𝑏=𝑆, and 𝑞=𝖼𝗈𝖽𝖾(𝑟) for some 𝑟 when 𝑏=𝐷. In particular, no static operator, test, or parameter receives residual syntax.
Proof. Induct on the finite offline-specialization derivation, and invert the matching final binding-time rule. This choice is essential in Off-Call-S: its body specialization is a strict subderivation, whereas the callee’s binding-time derivation lives under the different division 𝜏𝑓. The program check supplies that matching derivation under 𝜏𝑓.
Rules Off-Const and Off-Var-S/D match BT-Const and BT-Var. The induction hypotheses give source values to Off-Op-S and Off-If-S, and residual syntax to Off-Op-D and Off-If-D. Each call rule’s matching binding-time rule checks its argument lists against the callee division. In Off-Call-S, apply the induction hypothesis to the body subderivation using the program check for 𝑒𝑏𝑓. Rule Off-Call-D constructs residual syntax. Rule Off-Lift changes the source value supplied by its induction hypothesis into quoted residual syntax. These are all offline rule families. ◻
Let (𝜈,𝐸,𝑟0) be a completed offline specialization graph for a checked program 𝑃𝑏, and let 𝑃=|𝑃𝑏|. For every node (𝑓,⃗𝑣)∈dom(𝜈), write 𝐸(𝜈(𝑓,⃗𝑣))=(⃗𝑥𝑑,𝑟𝑓,⃗𝑣). For every dynamic tuple ⃗𝑑 and value 𝑤, 𝑃;[⃗𝑥𝑠↦⃗𝑣,⃗𝑥𝑑↦⃗𝑑]⊢|𝑒𝑏𝑓|⇓𝑤⟺𝐸;[⃗𝑥𝑑↦⃗𝑑]⊢𝑟𝑓,⃗𝑣⇓𝑤. The same equivalence holds simultaneously for the graph’s main source expression and 𝑟0.
Proof of Lemma 127.14 — Completed-graph node simulation
Proof. For 𝑘∈ℕ, prove both implications simultaneously for every graph node and the main expression when the given finite evaluation derivation has height below 𝑘. Use strong induction on 𝑘, followed by induction on the fixed offline-specialization derivation supplied by definition 127.12. Constants, variables, operators, lifts, and both conditional forms use strict argument or selected-branch subderivations.
For an unfolded static call, inversion of source evaluation exposes the callee-body derivation strictly below the enclosing call; apply the outer height induction and rebuild the residual evaluation. For a residual dynamic call, graph closure supplies the unique target node and its equation. In the source-to-residual direction, inversion of the source call again exposes a strictly shorter body derivation, to which the outer induction applies. In the residual-to-source direction, inversion of the residual named call exposes the equation-body derivation strictly below that call; apply the same outer induction in the opposite direction and rebuild the source call. Crucially, a back edge is handled by smaller evaluation height, not by an induction hypothesis on the graph node or on its specialization body. These are both call forms and every offline rule family. Induction on 𝑘 removes the bound. ◻
Let 𝑃𝑏 pass the program check above, let 𝑃 be its annotation erasure, and let offline specialization on static input 𝑠 return a completed graph (𝜈,𝐸,𝑟0) in the sense of definition 127.12, with residual program 𝑃𝑠=(𝐸,𝑟0). For every dynamic input 𝑑 and returned value 𝑣, 𝗋𝗎𝗇0(𝑃𝑠,𝑑)=𝑣⟺𝗋𝗎𝗇0(𝑃,(𝑠,𝑑))=𝑣. No conclusion is asserted when specialization does not return.
Proof of Theorem 127.15 — Well-annotated-program soundness
Proof. Apply lemma 127.14 to the main-expression clause of the completed graph. The definition of 𝗋𝗎𝗇0 supplies the displayed static and dynamic environments on the source side and the dynamic environment on the residual side. The two implications of the lemma give the equivalence for each returned value. The proof therefore depends on the completed, reachability-closed graph, rather than treating a back-edge body as a structural subderivation. ◻
★☆☆ Let 𝑥 be dynamic. Explain why 𝗂𝖿𝑠(𝑥,0,1) has no binding-time derivation, display the failed premise, and give the least well-annotated repair, including every required lift. Derive the offline residual result of the repaired term under 𝜂𝑑(𝑥)=𝑥.
Monovariance can force a useless division. Fix the actual Scheme0 definition 𝖽𝖾𝖿𝗂𝗇𝖾(𝗉𝗈𝗐𝖾𝗋𝑘𝑥)𝗂𝖿(=𝑘0)1(∗𝑥(𝖼𝖺𝗅𝗅𝗉𝗈𝗐𝖾𝗋(−𝑘1)𝑥)). The primitive (−𝑘1) is defined on positive naturals; the conditional prevents its use at zero. Suppose one call has dynamic exponent and another has known exponent four. A single monovariant division marks 𝑘 dynamic at both sites.
Here is the finite copying transformation used to repair that division. Let 𝑃 be a finite Scheme0 program containing (127.1) and no definitions named 𝗉𝗈𝗐𝖾𝗋𝑑 or 𝗉𝗈𝗐𝖾𝗋𝑠. A power-site map𝜒 assigns 𝑑 or 𝑠 to every nonrecursive syntactic call site of 𝗉𝗈𝗐𝖾𝗋 in 𝑃. The program 𝖲𝗉𝗅𝗂𝗍𝜒(𝑃) deletes the original definition, inserts two alpha-distinct copies 𝗉𝗈𝗐𝖾𝗋𝑑 and 𝗉𝗈𝗐𝖾𝗋𝑠, redirects the recursive call in each copy to that copy, and redirects every other call site ℓ to 𝗉𝗈𝗐𝖾𝗋𝜒(ℓ). Every other syntax node and definition is copied homomorphically. The collapse map ⌊−⌋𝗌𝗉𝗅𝗂𝗍 renames either copy back to 𝗉𝗈𝗐𝖾𝗋 and identifies their duplicate definitions. Therefore ⌊𝖲𝗉𝗅𝗂𝗍𝜒(𝑃)⌋𝗌𝗉𝗅𝗂𝗍=𝑃, including bodies and call sites.
For offline specialization, the two copies receive different annotated definitions. Writing the static arguments before the semicolon, their bodies are 𝗉𝗈𝗐𝖾𝗋𝑑(;𝑘,𝑥)=𝗂𝖿𝑑(𝗈𝗉𝑑(=)(𝑘,𝗅𝗂𝖿𝗍(0)),𝗅𝗂𝖿𝗍(1),𝗈𝗉𝑑(∗)(𝑥,𝖼𝖺𝗅𝗅𝑑𝗉𝗈𝗐𝖾𝗋𝑑()(𝗈𝗉𝑑(−)(𝑘,𝗅𝗂𝖿𝗍(1)),𝑥))),𝗉𝗈𝗐𝖾𝗋𝑠(𝑘;𝑥)=𝗂𝖿𝑠(𝗈𝗉𝑠(=)(𝑘,0),𝗅𝗂𝖿𝗍(1),𝗈𝗉𝑑(∗)(𝑥,𝖼𝖺𝗅𝗅𝑠𝗉𝗈𝗐𝖾𝗋𝑠(𝗈𝗉𝑠(−)(𝑘,1))(𝑥))). The displayed lifts make every dynamic primitive operand dynamic. Both annotated definitions erase to their Scheme0 copies. A site may be assigned 𝑠 only after its exponent premise checks at binding time 𝑆; all remaining sites use 𝑑, inserting a lift when a known actual flows to a dynamic formal. Thus the transformation and its admissible call-site annotations are separate, explicit data.
For every power-site map 𝜒, Scheme0 environment 𝜌, expression 𝑒 in 𝑃, and value 𝑣, 𝑃;𝜌⊢𝑒⇓𝑣⟺𝖲𝗉𝗅𝗂𝗍𝜒(𝑃);𝜌⊢𝗌𝗉𝗅𝗂𝗍𝜒(𝑒)⇓𝑣. In particular, for all naturals 𝑘,𝑥,𝑣, a call to either copied function returns 𝑣 exactly when the call to 𝗉𝗈𝗐𝖾𝗋(𝑘,𝑥) in (127.1) returns 𝑣. Under the displayed checked offline annotation, specialization of 𝗉𝗈𝗐𝖾𝗋𝑠(4;𝑥) returns 𝑥⋅(𝑥⋅(𝑥⋅(𝑥⋅1))).
Proof of Proposition 127.16 — Binding-time split preservation
Proof. Prove the two implications simultaneously by induction on the finite evaluation derivation from the side supplying the implication. Constants, variables, primitives, and conditionals rebuild the same rule because 𝗌𝗉𝗅𝗂𝗍𝜒 is homomorphic there. A call to any function other than 𝗉𝗈𝗐𝖾𝗋 uses the induction hypotheses for its argument and body derivations. At an external power call, the selected copy has the same formals and collapsed body as (127.1); apply the induction hypotheses to the arguments and then to the strictly smaller body derivation. At a recursive power call, the copy calls itself, while collapse calls 𝗉𝗈𝗐𝖾𝗋; the recursive body derivation is again strictly smaller. These are all call cases. The reverse induction collapses either copied name to 𝗉𝗈𝗐𝖾𝗋, so it has the same cases and establishes the converse.
For specialization, Off-If-S evaluates the equality tests at 4,3,2,1,0. At each positive exponent, Off-Op-S computes the decrement and Off-Call-S unfolds the next state. At zero, Off-Lift emits 1. The four enclosing Off-Op-D instances retain multiplication by the residual variable 𝑥, giving the displayed term. ◻
The same pair separates online state from an offline monovariant division. For 𝑓(𝑘;𝑥)=𝗂𝖿𝟢𝑘𝗍𝗁𝖾𝗇𝑥𝖾𝗅𝗌𝖾(𝑥+1), consider one call 𝑓(𝑘;𝑥) with unknown 𝑘 and one call 𝑓(0;𝑥). Monovariance joins the first call into the formal division and marks 𝑘 dynamic, so the offline specializer residualizes the conditional at both call sites. The online specializer sees the second actual value 0 and returns only 𝑥. Function splitting recovers that result offline by assigning the second call its own all-static copy. Thus online is more precise on this fixed program and division; the calculation is not a system-independent ordering.
For the first projection, fix the represented arithmetic language 𝑞::=𝖫𝗂𝗍(𝑛)∣𝖨𝗇𝗉𝗎𝗍∣𝖠𝖽𝖽(𝑞,𝑞) and the Scheme0 equations 𝗂𝗇𝗍𝖤𝗑𝗉(𝖫𝗂𝗍(𝑛),𝑑)=𝑛,𝗂𝗇𝗍𝖤𝗑𝗉(𝖨𝗇𝗉𝗎𝗍,𝑑)=𝑑,𝗂𝗇𝗍𝖤𝗑𝗉(𝖠𝖽𝖽(𝑞1,𝑞2),𝑑)=𝗂𝗇𝗍𝖤𝗑𝗉(𝑞1,𝑑)+𝗂𝗇𝗍𝖤𝗑𝗉(𝑞2,𝑑). These equations use the card’s constructor tests, selectors, calls, and base addition. Specializing with respect to 𝖠𝖽𝖽(𝖫𝗂𝗍(2),𝖨𝗇𝗉𝗎𝗍) unfolds the first and third equations and yields 𝑑↦2+𝑑. Hence the first-projection instance is 𝖾𝗏𝖺𝗅(𝗆𝗂𝗑(𝗂𝗇𝗍𝖤𝗑𝗉𝑎,⌜𝖠𝖽𝖽(𝖫𝗂𝗍(2),𝖨𝗇𝗉𝗎𝗍)⌝),𝑑)=2+𝑑. The general compiler equation below is the same calculation with 𝑞 arbitrary.
Let ⌜𝑝⌝ be the Scheme0 representation of 𝑝, and let 𝑝𝑎=𝖺𝗇𝗇(𝑝,𝛿𝑝) be its two-level annotation under checked division 𝛿𝑝. Thus 𝑝𝑎 contains the division information rather than hiding it as an implicit argument. Fix the historical self-applicable offline program 𝗆𝗂𝗑 with full Scheme0 interface 𝗆𝗂𝗑:𝖠𝗇𝗇𝖯𝗋𝗈𝗀×𝖲𝗍𝖺𝗍𝗂𝖼𝖳𝗎𝗉𝗅𝖾⇀𝖯𝗋𝗈𝗀 and equation 𝗆𝗂𝗑(𝑝𝑎,𝑠)=𝑟⟹𝖾𝗏𝖺𝗅(𝑟,𝑑)=𝑣⟺𝖾𝗏𝖺𝗅(|𝑝𝑎|,(𝑠,𝑑))=𝑣. The annotation 𝑝𝑎, suppressed in informal projection slogans, is part of the executable interface. The equation is not asserted for definition 127.5.
For 𝗆𝗂𝗑𝑎=𝖺𝗇𝗇(𝗆𝗂𝗑,𝛿𝗆𝗂𝗑), and the distribution’s checked annotation 𝗉𝗈𝗐𝖾𝗋𝑎 of its Scheme0 power program, the following two applications are well-formed specializer inputs: 𝗆𝗂𝗑(𝗆𝗂𝗑𝑎,𝗉𝗈𝗐𝖾𝗋𝑎),𝗆𝗂𝗑(𝗆𝗂𝗑𝑎,𝗆𝗂𝗑𝑎). If either application returns, its result lies in 𝖯𝗋𝗈𝗀. The proposition asserts neither termination nor a structural fixed-point equation.
Proof of Proposition 127.17 — Scheme0 self-application typing boundary
Proof. The checked annotations satisfy 𝗆𝗂𝗑𝑎,𝗉𝗈𝗐𝖾𝗋𝑎∈𝖠𝗇𝗇𝖯𝗋𝗈𝗀, and the Scheme0 representation satisfies 𝖠𝗇𝗇𝖯𝗋𝗈𝗀⊆𝖲𝗍𝖺𝗍𝗂𝖼𝖳𝗎𝗉𝗅𝖾. Therefore each displayed pair lies in 𝖠𝗇𝗇𝖯𝗋𝗈𝗀×𝖲𝗍𝖺𝗍𝗂𝖼𝖳𝗎𝗉𝗅𝖾, the domain sort of 𝗆𝗂𝗑. Its codomain is 𝖯𝗋𝗈𝗀, so any returned value has that sort. Partiality of the signature prevents a termination conclusion. The cited construction supplies the annotation shapes but no general termination theorem for self-application . ◻
The following meta-sort ledger types every self-application. Let 𝖯𝗋𝗈𝗀⊆𝖲𝗍𝖺𝗍𝗂𝖼𝖳𝗎𝗉𝗅𝖾⊆𝖣𝖺𝗍𝖺 be the represented Scheme0 programs, 𝖠𝗇𝗇𝖯𝗋𝗈𝗀⊆𝖲𝗍𝖺𝗍𝗂𝖼𝖳𝗎𝗉𝗅𝖾⊆𝖣𝖺𝗍𝖺 their checked annotations, 𝗋𝗎𝗇:𝖯𝗋𝗈𝗀×𝖣𝖺𝗍𝖺⇀𝖣𝖺𝗍𝖺, and let 𝗂𝗇𝗍,𝗆𝗂𝗑∈𝖯𝗋𝗈𝗀. The interpreter accepts [⌜𝑞⌝,𝑑]:𝖣𝖺𝗍𝖺, and the annotation encodings 𝗂𝗇𝗍𝑎 and 𝗆𝗂𝗑𝑎 are members of 𝖲𝗍𝖺𝗍𝗂𝖼𝖳𝗎𝗉𝗅𝖾. The specializer accepts [𝑝𝑎,𝑠]:𝖣𝖺𝗍𝖺 with 𝑝𝑎:𝖠𝗇𝗇𝖯𝗋𝗈𝗀 and returns 𝑟:𝖯𝗋𝗈𝗀. Hence, if the following three applications return, their outputs have the displayed meta-sorts; the equations introduce names for those returned programs rather than asserting termination: 𝗋𝗎𝗇(𝗆𝗂𝗑,[𝗂𝗇𝗍𝑎,⌜𝑞⌝])=𝖼𝗈𝗆𝗉𝗂𝗅𝖾(𝑞):𝖯𝗋𝗈𝗀,𝗋𝗎𝗇(𝗆𝗂𝗑,[𝗆𝗂𝗑𝑎,𝗂𝗇𝗍𝑎])=𝖼𝗈𝗆𝗉𝗂𝗅𝖾𝗋:𝖯𝗋𝗈𝗀,𝗋𝗎𝗇(𝗆𝗂𝗑,[𝗆𝗂𝗑𝑎,𝗆𝗂𝗑𝑎])=𝖼𝗈𝗀𝖾𝗇:𝖯𝗋𝗈𝗀. The displayed inclusions into 𝖲𝗍𝖺𝗍𝗂𝖼𝖳𝗎𝗉𝗅𝖾 and 𝖣𝖺𝗍𝖺, realized by Scheme0’s quoted program and annotation representations, license the two self-applications when they terminate. The typing proposition above discharges none of the three termination antecedents. An arbitrary typed specializer need not have such a reflexive representation.
Let 𝗂𝗇𝗍 satisfy 𝖾𝗏𝖺𝗅(𝗂𝗇𝗍,(⌜𝑞⌝,𝑑))=𝖾𝗏𝖺𝗅(𝑞,𝑑) whenever either side returns. For the represented program 𝑞 under consideration, assume that all three displayed specializations below terminate and return the named programs. Define 𝖼𝗈𝗆𝗉𝗂𝗅𝖾(𝑞)=𝗆𝗂𝗑(𝗂𝗇𝗍𝑎,⌜𝑞⌝),𝖼𝗈𝗆𝗉𝗂𝗅𝖾𝗋=𝗆𝗂𝗑(𝗆𝗂𝗑𝑎,𝗂𝗇𝗍𝑎),𝖼𝗈𝗀𝖾𝗇=𝗆𝗂𝗑(𝗆𝗂𝗑𝑎,𝗆𝗂𝗑𝑎). Then 𝖾𝗏𝖺𝗅(𝖼𝗈𝗆𝗉𝗂𝗅𝖾(𝑞),𝑑)=𝖾𝗏𝖺𝗅(𝑞,𝑑),𝖾𝗏𝖺𝗅(𝖼𝗈𝗆𝗉𝗂𝗅𝖾𝗋,⌜𝑞⌝)=𝖼𝗈𝗆𝗉𝗂𝗅𝖾(𝑞),𝖾𝗏𝖺𝗅(𝖼𝗈𝗀𝖾𝗇,𝗂𝗇𝗍𝑎)=𝖼𝗈𝗆𝗉𝗂𝗅𝖾𝗋. Each equality asserts a common returned program or value; it says nothing when the corresponding specialization diverges.
Proof of Theorem 127.18 — The three Futamura equations
Proof. Instantiate (127.2) with 𝑝𝑎=𝗂𝗇𝗍𝑎 and 𝑠=⌜𝑞⌝. The interpreter equation proves the first line. For the second, use 𝑝𝑎=𝗆𝗂𝗑𝑎, 𝑠=𝗂𝗇𝗍𝑎, and dynamic input ⌜𝑞⌝. The right side is 𝖼𝗈𝗆𝗉𝗂𝗅𝖾(𝑞). For the third, use 𝑝𝑎=𝗆𝗂𝗑𝑎, 𝑠=𝗆𝗂𝗑𝑎, and dynamic input 𝗂𝗇𝗍𝑎. The right side is 𝖼𝗈𝗆𝗉𝗂𝗅𝖾𝗋. The stated termination hypotheses license precisely these applications. ◻
A typed self-interpreter does not discharge those hypotheses. In the 𝐹𝜔 card of chapter 17, an evaluator at object type 𝐴 has shape 𝖾𝗏𝖺𝗅𝐴:𝖱𝖾𝗉(𝐴)→𝐴. Its own syntax has type 𝖱𝖾𝗉(𝖱𝖾𝗉(𝐴)→𝐴), not 𝖱𝖾𝗉(𝐴); the attempted application 𝖾𝗏𝖺𝗅𝐴⌜𝖾𝗏𝖺𝗅𝐴⌝ therefore fails at the argument type. Scheme0’s untyped program-as-data inclusion avoids that particular mismatch, but self-applying 𝗆𝗂𝗑 still requires the binding-time and termination facts in (127.2).
The binding-time analysis has a local abstract-interpretation map. Take the two-point lattice 𝑆≤𝐷; map a concrete partial environment to 𝑆 at exactly its known variables and to 𝐷 elsewhere. Constants transfer to 𝑆, variables by lookup, base applications by join, a dynamic test forces the result and both branches to 𝐷, and calls join actual annotations into the callee’s formal division.
For a finite Scheme0 program, the displayed transfer is monotone on the finite lattice {𝑆,𝐷}𝖵𝖺𝗋𝗌. Kleene iteration from the all-static map terminates at its least post-fixed division, and every accepted static occurrence depends only on variables mapped to 𝑆.
Proof of Lemma 127.19 — Finite binding-time analysis
Proof. Each transfer clause is a projection, constant, or finite join in 𝑆≤𝐷, hence monotone. The product lattice has finite height at most the number of program variables; every strict iteration changes at least one coordinate from 𝑆 to 𝐷, so iteration terminates. At the post-fixed point, induct on expression syntax. The variable case is lookup; an operation can remain static only when every operand does; the conditional and call cases use their displayed propagation constraints. ◻
This is an explicit instance of the vocabulary in chapter 25. It does not import that chapter’s flow-sensitive domains, widening theorem, or verified analyzer: the lattice, transfer, and termination argument above are the entire connection.
Sources. The Scheme0 encodings, binding-time analysis, mix equation, and projection derivations follow Jones, Gomard, and Sestoft, especially Chapters 4–6 [JGS93]. The online specializer is the smaller calculus proved locally above. Other computational-metalanguage and pure-lambda binding-time theorems remain separate systems.
Suggested first pass.
None of these problems is a prerequisite for a later chapter. Begin with exercise 127.5, then complete exercise 127.6.
★★☆ Give the useless monovariant annotation of power in which both arguments are dynamic. Perform the function split above, solve both divisions, and calculate the residual program at exponent four. Prove equality of the erased calls by unfolding the two copied equations.
★★★ Let 𝗂𝗇𝗍+ interpret numerals, variables, and addition. Supply its encoding and use (127.2) to derive the first projection for (𝑥+2)+3. State the termination assumption where it is used.
★★★Practical project.scheme0-fuelled-specializer Implement in Kappa the expression core, dynamic conditional, recursive call patterns, memo hits, and safe fuel exhaustion. Maintain lemma 127.3. Print 𝗉𝗈𝗐𝖾𝗋-𝟥:𝑥⋅(𝑥⋅(𝑥⋅1)),𝖽𝗒𝗇𝖺𝗆𝗂𝖼-𝗂𝖿:𝗂𝖿𝟢𝑥𝗍𝗁𝖾𝗇5𝖾𝗅𝗌𝖾𝑦,𝖿𝗎𝖾𝗅-𝗓𝖾𝗋𝗈:frontiercallplusretainedequation,𝖿𝗎𝖾𝗅-𝗍𝗐𝗈:twoequationsplusretainedclosure,𝗆𝖾𝗆𝗈-𝗁𝗂𝗍:areachedself-recursiveequation,𝖼𝗅𝗈𝗌𝗎𝗋𝖾-𝖼𝗁𝖾𝖼𝗄:everyfrontiertargetispresent. A mutation that returns the left operand of static addition must fail the dynamic-conditional test. The Kappa program does not prove self-applicability or a Futamura equation.
★★☆ For 𝐸0=𝑥 and 𝐸𝑛+1=𝗂𝖿𝟢𝑦𝑛𝗍𝗁𝖾𝗇𝐸𝑛𝖾𝗅𝗌𝖾𝐸𝑛, calculate the tree size of the residual term produced without let insertion. Then insert one fresh let at each level and prove the resulting dag has linear size using proposition 127.11 under its termination hypothesis.
★★☆ Compare the polyvariant key (𝑓,⃗𝑛) with the following monovariant policy on calls 𝗉𝗈𝗐𝖾𝗋(2;𝑥) and 𝗉𝗈𝗐𝖾𝗋(3;𝑦). The monovariant table has key 𝑓; when distinct static tuples collide, it promotes every differing static component to a dynamic formal, emits one copy of the erased source equation with that promoted formal, and redirects every colliding call to the copy. Write both residual programs and state which static distinction is lost. Prove preservation of the polyvariant program by the corresponding call cases of theorem 127.9. Prove preservation of the monovariant program directly by induction on the promoted exponent; it is not an instance of that theorem’s (𝑓,⃗𝑛)-keyed algorithm.
★★★ Using (127.2), derive the second projection for an arbitrary ⌜𝑞⌝ and the third at 𝗂𝗇𝗍𝑎. Type each meta-level application with the ledger above and state its termination premise.