Lectures onType Theory
Chapter 138
Chapter 138Optional

Dependency-Preserving A-Normal Form

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

A compiler that wants a machine-like intermediate language names every intermediate computation. The expression f(snd e) becomes a sequence of primitive steps, let y=e in let z=snd y in fz, and in a simply typed language that is the end of the matter. With dependent types it is not. Let e:Σx:A.B, so that snd e:B[fst e/x], and let f:B[fst e/x]C. In the sequence above, snd y has type B[fst y/x], because the typing rule for the second projection copies its own scrutinee into the type. The scrutinee is now the variable y, not e. So f is applied to an argument of the wrong type, and the ANF term does not type check — although the original does, and although the two compute the same thing.

The same failure appears for positive types. If f(if e then e1 else e2) is well typed with f:B[e/x]C and e1:B[true/x], then pushing f into the branches applies it to an argument of type B[true/x], and the type system cannot know that e equals true in that branch.

Both failures have one cause. By the time the machine runs the body of the let, it has performed the step y=e; by the time it runs a branch, it has evaluated e to a boolean. The type system has no way to record either fact. This chapter follows Koronkevich, Rakow, Ahmed and Bowman (2022): give the target two ways to record a performed machine step, and the ANF translation preserves dependent types — up to an equality that the target must be able to reflect.

The languages

Definition 138.1 — Source

The source is the extended calculus of constructions: dependent functions Πx:A.B, dependent pairs Σx:A.B with projections, booleans with dependent if, a predicative universe hierarchy, and definitional equality containing β, η and ζ. The two dependent elimination rules that matter are

Γe:Σx:A.B
Γsnd e:B[fst e/x]
Snd
Γe:BoolΓ,x:BoolB:TypeiΓe1:B[true/x]Γe2:B[false/x]
Γif e then e1 else e2:B[e/x]
If

Definition 138.2 — A-normal form

Target expressions are stratified into values, computations and configurations: V::=xtruefalseλx:A.MV,VΠx:A.BΣx:A.BBoolTypei,N::=VV1V2fst Vsnd V,M::=Nlet x=N in Mif V then M1 else M2. A configuration is a sequence of let-bound primitive computations ending in a computation or a branch; no let is nested inside another’s bound computation. The machine reduces the leftmost binding, whose operands are already values.

Control flow has become data flow: each let is one machine step, and the machine’s state is the remaining configuration.

Recording a step: definitions

Definition 138.3 — Definitions in the context

Contexts admit entries x=δe:A, and the typing rule for let records one:

ΓN:AΓ,x=δN:AM:B
Γlet x=N in M:B[N/x]
Let

Equivalence is extended by ζ: under x=δN:A, the variable x is equivalent to N.

The highlighted change is the context entry. In the standard rule the definition appears only in the substitution performed on the output type; here it is available while checking the body, which is exactly where the opening’s failure occurred.

Example 138.4 — The projection, repaired

With definition 138.3 the sequence let y=e in let z=snd y in fz type-checks. Checking the inner body proceeds under y=δe:Σx:A.B, so B[fst y/x]B[fst e/x] by ζ, and f may be applied to z. The definitional equality that Let makes available is precisely the machine step the type system had forgotten.

Exercise 138.1

★☆☆ Delete the context entry from Let, keeping the substitution in the conclusion, and show that example 138.4 no longer type-checks. Name the equivalence step that becomes underivable.

Recording a step: propositional equality and reflection

A definition does not help at a branch: there is no variable to define, and the two branches need different facts.

Definition 138.5 — Dependent if with recorded equalities

Γe:BoolΓ,x:BoolB:TypeiΓ,p:(eproptrue)e1:B[true/x]Γ,p:(epropfalse)e2:B[false/x]
Γif e then e1 else e2:B[e/x]
If^δ

together with the equivalence rule

Γp:e1prope2
Γe1e2
-Reflect

Example 138.6 — The branch, repaired

Pushing f:B[e/x]C into the two branches now type-checks. In the true branch the context contains p:(eproptrue), so -Reflect gives etrue and hence B[e/x]B[true/x]; the argument e1 has the latter type and f expects the former. The false branch is the same with false.

Remark 138.7 — What the second repair costs

Definition 138.3 is a conservative extension: definitions are admissible in any pure type system, and proof assistants already have them. Definition 138.5 is not. -Reflect is equality reflection, and a type theory with equality reflection has undecidable type checking: deciding Γe1e2 requires deciding whether some p inhabits e1prope2, which is a proof search. The target is therefore an extensional calculus, suitable as a specification of what the translation preserves and not as a checker. Recovering decidability means restricting where reflection may be used — for instance to the equalities the translation itself introduces — and that restriction is not developed here.

Exercise 138.2

★★☆ Show that -Reflect makes Γe1e2 as hard as inhabitation of e1prope2. Then propose a syntactic restriction on the rule that suffices for example 138.6 and say which of the chapter’s later lemmas would have to be rechecked under it.

Continuations as typed contexts

The translation is indexed by a continuation — a configuration with a hole — and the type of its output is known only once the hole is filled. The metatheory therefore needs a typing judgment for continuations.

Definition 138.8 — Continuation typing

K::=[]let x=[] in M, and

Γ[]:(N:A)A
K-Empty
Γ,x=δN:AM:Bxfv(B)
Γlet x=[] in M:(N:A)B
K-Let

The judgment records the term N the hole is expected to receive, because the body’s type may depend on it; the side condition xfv(B) makes the result type independent of the hole, so that continuations compose.

Lemma 138.9 — Continuation cut

If ΓK:(N:A)B and ΓN:A then ΓKN:B.

Proof of Lemma 138.9 — Continuation cut

Proof. By cases on the continuation typing derivation. For K-Empty the plugged term is N and B=A. For K-Let the plugged term is let x=N in M; the premise gives Γ,x=δN:AM:B, so Let types it at B[N/x], which is B because xfv(B). ◻

Lemma 138.10 — Continuation cut modulo equivalence

If ΓK:(N:A)B, ΓN:A, ΓN:A and ΓNN, then ΓKN:B.

Proof of Lemma 138.10 — Continuation cut modulo equivalence

Proof. By cases on K. K-Empty is immediate. For K=let x=[] in M it suffices to show that Γ,x=δN:AM:B implies Γ,x=δN:AM:B. The only use a derivation can make of the entry x=δN:A is a ζ step CC[N/x]. Replace each such step by CC[N/x]C[N/x], the second equivalence following from NN because substitution respects equivalence. Every other step of the derivation is unchanged. ◻

Remark 138.11 — Why this is a lemma and not a rule

Building equivalence into definition 138.8 — typing K against any NN — would make continuation typing correct but would spoil its induction principle, because the inversion of K-Let would no longer determine the recorded term. Lemma 138.9, Lemma 138.10 together say that continuation typing is admissible rather than an extension: plugging a well-typed computation into a well-typed continuation always yields a well-typed configuration of the expected type.

The translation and type preservation

Definition 138.12 — ANF translation

Write [[e]]K for the translation of e with continuation K. A value translates by filling the hole; a computation translates by naming its subterms. The two clauses that matter are [[snd e]]K=[[e]](let x=[] in Ksnd x),[[e1e2]]K=[[e1]](let x1=[] in [[e2]](let x2=[] in Kx1x2)), and the whole-program translation is [[e]]=[[e]][].

Theorem 138.13 — The output is in A-normal form

For every e and every K, [[e]]K is a configuration of definition 138.2.

Proof of Theorem 138.13 — The output is in A-normal form

Proof. Induction on e. Every clause either fills the hole of K with a value or a primitive computation, or calls the translation recursively with a continuation of the form let x=[] in M; neither places a let inside the bound computation of another. ◻

The type-preservation statement is not provable as it stands, and the reason is worth seeing before the repair.

Remark 138.14 — Why the obvious induction fails

Consider [[snd e]]K. The induction hypothesis for “Γe:A implies [[Γ]][[e]]:[[A]]” tells us about [[e]][], not about [[e]] applied to the newly built continuation let x=[] in Ksnd x. The hypothesis must carry typing information for whatever continuation the clause constructs.

Lemma 138.15 — Type preservation, strengthened

If Γe:A and Γ,ΓK:([[e]]:[[A]])B, then Γ,Γ[[e]]K:B.

Proof of Lemma 138.15 — Type preservation, strengthened

Proof. Induction on the source typing derivation.

Snd. Let Γe:Σx:A.B, so that snd e:B[fst e/x], and let K be typed at ([[snd e]]:[[B[fst e/x]]])C. Apply the induction hypothesis at e with the continuation K=let y=[] in Ksnd y. Typing K by K-Let requires Γ,Γ,y=δ[[e]]:[[Σx:A.B]]Ksnd y:C. In that context y[[e]] by ζ, hence [[B[fst y/x]]][[B[fst e/x]]], so snd y has the type the hole of K expects up to equivalence, and lemma 138.10 gives the required judgment. The induction hypothesis then types [[e]]K=[[snd e]]K at C.

Application. The same argument twice, with the inner continuation typed under the definition of x1 and the outer one under the definitions of both.

Ifδ. The continuation is duplicated into the two branches. In the true branch the context acquires p:([[e]]proptrue), and -Reflect makes [[B[e/x]]] equivalent to [[B[true/x]]], which is what the duplicated continuation needs; the false branch is symmetric. Without definition 138.5 this case is where the proof stops.

The value and remaining computation cases fill the hole directly and appeal to lemma 138.9. ◻

Theorem 138.16 — Type preservation

If Γe:A then [[Γ]][[e]]:[[A]].

Proof of Theorem 138.16 — Type preservation

Proof. Take K=[] in lemma 138.15; K-Empty types it at ([[e]]:[[A]])[[A]]. ◻

Lemma 138.17 — Compositionality and substitution

K[[e]]K=[[e]](K;K), where K;K fills the hole of K with K; consequently K[[e]]=[[e]]K. Moreover [[e[e/x]]]K([[e]]K)[[[e]]/x].

The first half says the compiler is compositional: translating a term and then placing it in a context is translating it in the composed context. The second half is what makes the source’s substitution match the target’s, and it is the step that turns theorem 138.16 into a statement about linking.

The target is consistent

Adding definition 138.5 and -Reflect to a type theory is adding axioms. The target must be shown to prove nothing new.

Construction 138.18 — A model in the source

Define [[]]M from the target back to the source by erasing the recorded equality: an if with its proof binder becomes the source if, a use of -Reflect becomes the corresponding source equivalence, and a definition becomes a let.

Lemma 138.19

[[]]M; if e1e2 in the target then [[e1]]M[[e2]]M; and if Γe:A in the target then [[Γ]]M[[e]]M:[[A]]M.

Theorem 138.20 — Consistency and subject reduction

There is no e with e: in the target. If Γe:A and Γee then Γe:A.

Proof of Theorem 138.20 — Consistency and subject reduction

Proof. Consistency: a closed target proof of would be sent by lemma 138.19 to a closed source proof of , and the source has none. Subject reduction reduces to the single-step case. That case needs context replacement, which says that a derivation is unchanged when a context entry is replaced by an equivalent one, and it needs the two cut lemmas, for variables and for definitions. Each is proved by induction on the derivation, replacing every use of the affected entry as in lemma 138.10. ◻

Theorem 138.21 — Evaluation soundness

For every well-typed configuration M, Γeval(M)M, where eval runs the machine of definition 138.2.

Proof of Theorem 138.21 — Evaluation soundness

Proof. Each machine step is a β, ζ or projection step, hence an equivalence; iterate, using naturality of continuation plugging to move the step under the surrounding configuration. ◻

Separate compilation

Theorem 138.22 — Correctness of separate compilation

Let Γe:A with A a base type, let γ be a closing substitution for Γ, and let γ be a target closing substitution with [[γ]]γ. Then eval(γ(e)) and eval(γ([[e]])) are the same base value.

Proof of Theorem 138.22 — Correctness of separate compilation

Proof. The square eval(γ(e))γ(e)eval(γ([[e]]))γ([[e]]) commutes: the horizontal edges are theorem 138.21 in the source and in the target, the right edge is lemma 138.17 together with the hypothesis on γ, and the left edge follows. At a base type, equivalence of two values is equality of those values. ◻

The restriction to base types is not decoration. At a function type, equivalence of the two results is not the same as their being the same value, and the theorem would need a relation between source and target values chosen independently of the compiler.

Exercise 138.3

★★☆ Exhibit a source term of function type for which the conclusion of theorem 138.22, read as “the same value”, is false while the equivalence still holds. Then say which line of the proof used the base-type hypothesis.

Join points

Proposition 138.23 — The translation duplicates code

Definition 138.12 copies the continuation into both branches of an if. A source term with n nested conditionals in the argument position of a continuation therefore has a translation of size exponential in n.

Proof of Proposition 138.23 — The translation duplicates code

Proof. Let Cn be the size of the translation of n nested conditionals with a continuation of size k. The clause for if places a copy of the continuation in each branch, so Cn2Cn1 and C0k; hence Cn2nk. ◻

Construction 138.24 — Join-point translation

Bind the continuation once, as a function taking the branch result and the recorded equality, and call it from both branches: Write W for [[if e then e1 else e2]] and Ki=let xi=[] in fxi(refl xi). Then [[if e then e1 else e2]]K=let f=λy:[[B[e/x]]].λp:(ypropW).Kyin [[e]](let x=[] inif x then [[e1]]K1 else [[e2]]K2).

Proof of Lemma 138.25 — The join-point translation is type preserving

Proof. Only the if case changes. The join point f must be applied to arguments of the right types. For the first argument, in the true branch the context contains x1=δ[[e1]], and [[B[e/x]]][[B[true/x]]] by -Reflect on the recorded equality, which is the equivalence already used in lemma 138.15. For the second, it suffices to show (x1propx1)(x1prop[[if e then e1 else e2]]), which by congruence reduces to x1[[if e then e1 else e2]], and that is the same equivalence again under the definition of x1. The false branch is symmetric. ◻

Remark 138.26

Construction 138.24 needs the type B of the branch, which the source if does not carry. Either define the translation by induction on typing derivations, or annotate the source if with B. The second is preferable and is what dependent case analysis in a proof assistant already does.

Exercise 138.4

★★☆ Compute the size of construction 138.24 applied to n nested conditionals and check that it is linear in n. Then say why proposition 138.23 is nevertheless not a defect of definition 138.12 as a specification.

Limits and seminar

The frozen source is Koronkevich, Rakow, Ahmed and Bowman (2022). Definition 138.3, Definition 138.5, Definition 138.8, Definition 138.12 and construction 138.24 are its definitions; lemma 138.9, lemma 138.10, theorem 138.13, lemma 138.15 and theorem 138.16, theorem 138.20, theorem 138.21, theorem 138.22, lemma 138.25 are its lemmas and theorems at their exact statements. Proposition 138.23 is local.

Five boundaries. The target’s type checking is undecidable, by remark 138.7: it is a specification of what ANF preserves, and a compiler that used it as an intermediate representation would need a restricted form of reflection that is not developed here. Theorem 138.22 holds at base types only. The source is the extended calculus of constructions with booleans and natural numbers; there are no general inductive types, no pattern matching and no fixed points, so nothing here covers a realistic dependent source language. The translation is one pass: there is no continuation-passing pass before it, no closure conversion after it, and no claim that the passes compose. Finally, the equality of theorem 138.16 is extensional — the target’s equivalence includes reflected propositional equalities — so “preserves dependent types” means preserves them up to that equality, and not up to the source’s definitional equality.

[4]

Suggested first pass.

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

Exercise 138.5

★★☆ Translate f(snd(fst e)) by definition 138.12, writing every continuation in full, and then type the result, naming the ζ step used at each let.

Exercise 138.6

★★★ Give a source term whose translation needs both definition 138.3 and definition 138.5, and show that deleting either one makes the translation ill typed. Identify the smallest such term you can.

Exercise 138.7

★★★ Delete the side condition xfv(B) from K-Let. Show that lemma 138.9 becomes false, and that continuations no longer compose; exhibit the two continuations whose composition has no type.

Exercise 138.8 — Practical: ANF translator and checker

★★★ Practical project.anf-dependency-checker Complete project anf-dependency-checker. Implement, for a source fragment with dependent pairs, projections, booleans and dependent if, (i) a type checker for the source, (ii) a type checker for the A-normal target including the definition-recording Let of definition 138.3 and the equality-recording Ifδ of definition 138.5 with reflection restricted to the recorded equalities, and (iii) the two translations of a nest of conditionals, the duplicating one of definition 138.12 and the join-point one of construction 138.24, with a size measure on the results. The invariant the implementation must maintain is that the target checker accepts an A-normal term exactly when the rule that records the corresponding machine step is enabled; if the full translation is also implemented, strengthen the invariant to theorem 138.16 on every accepted input. The named cases print

snd-let: accepted
snd-let-without-definitions: rejected
if-push: accepted
if-push-without-equalities: rejected
join-point-size: linear

The checker is independent evidence on the named inputs. It does not prove theorem 138.16 or lemma 138.15, implements no model and therefore says nothing about theorem 138.20, and its reflection rule is the restricted one, not the unrestricted -Reflect; a checker using the unrestricted rule would not terminate on every input, by remark 138.7.

Search the book

Type to search the local edition.