Lectures onType Theory
Chapter 121
Chapter 121Core route

Compiling Dependent Pattern Matching

Let A:Ui, m,n:N, a:A, xs:Vec(A,m), and ys:Vec(A,n). The two equations append(vnil,ys)=ys,append(vcons(m,a,xs),ys)=vcons(n+m,a,append(xs,ys)) state the desired computations, but they are not a term of dependent type theory. The second left side determines that the length of the first vector is suc(m); the right side then has type Vec(A,suc(n+m)), which is judgmentally the required type Vec(A,n+suc(m)). A compiler has to discover this refinement, reject impossible or uncovered branches, and construct the motive of the vector eliminator.

The hand translation in construction 78.7 performs these operations for append. It does not determine what to do with an arbitrary ordered matrix of clauses. The missing operation is a total compiler for one fixed clause language whose output realizes exactly the computations asserted by its reachable clauses.

The clause layer over the kernel

The compiler elaborates notation; it adds no rule to the kernel. Its source must therefore be fixed separately from the target theory.

Definition 121.1 — The Timpl-clauses system card

The target Timpl is exactly the kernel frozen in convention 112.1; this chapter adds no target former or computation rule. The source restriction below permits splitting only the finite constructor families named by that card and permits recursion only through a displayed structural eliminator.

Timpl-clauses adds an untrusted, first-order clause layer with the following exact boundary.

  1. A declaration has one Timpl argument telescope Δ=(x1:A1,,xr:Ar) and a result type B in that telescope. The only inductive families that may be split are the finite constructor families declared in Timpl.

  2. Patterns have the grammar p::=xc(p1,,pk).t! Pattern variables are linear, and constructor patterns are first order. The term t in .t is a Timpl term in the full pattern-variable context of its row; it binds no variable. Pattern elaboration rejects a cyclic dependency among inaccessible terms.

  3. Clause rows are ordered. At a compiler node, the first surviving row is the earliest row not deleted by an earlier constructor-branch filter. A blocking pattern in that row is an unresolved constructor or absurd pattern in a runtime column; variables and inaccessible terms do not block. The compiler scans the first surviving row from left to right and chooses the column of its first blocking pattern. An unresolved constructor or absurd pattern in an erased column instead produces a relevance error. When two rows overlap, the earlier row has priority.

  4. Every binder retains its inherited runtime or erased annotation. Index constraints and inaccessible terms may mention erased variables, but the compiler never branches on their run-time values. A right side may use an erased pattern variable only where the Timpl relevance rules permit it.

  5. Splitting x:D(u) with c:(y:Δc)D(vc) generates the homogeneous index constraint (u;x)Ξ;D(vc;c(y)), where Ξ is the index telescope of D. The compiler uses exactly the restricted unifier of definition 78.9. A positive result carries a dependency-preserving most general substitution, a negative result carries a conflict or cycle certificate, and a stuck result rejects the declaration. Such a substitution σ:ΘΘ is dependency preserving when its solution and injectivity transitions are those of definition 78.9, up to the stable adjacent exchange of independent declarations needed to expose a solution. Every such exchange preserves the relative order of all declarations not exchanged, and every dependency of a declaration remains earlier than that declaration in Θ.

  6. When a row becomes the first surviving candidate, each inaccessible term in that row is checked, never inspected at run time. It is accepted only when the accumulated substitution makes the pattern position judgmentally equal to that term. The compiler rejects a merely plausible but unforced inaccessible term. An absurd pattern in that row is accepted only when every constructor gives a negative unification certificate. Assertions in a shadowed later row are discarded by ordered priority; they are neither validated nor accepted as equations or impossibility evidence.

  7. A right side is a Timpl term at the result type instantiated by its written row map, in a context containing the declared identifier at its complete iterated function type. A nonrecursive declaration requires that identifier not to occur in a right side. A single structurally recursive definition may contain it only as a saturated call. Its designated argument must be a direct recursive child made available by the split, and the branch path must carry the corresponding child certificate. Mutual recursion, nested recursion, guards, views, higher-order patterns, nonlinear patterns, and unification outside the restricted constructor fragment are not in Timpl-clauses.

  8. The intermediate target is an ordered case tree. The kernel target is the Timpl term obtained from that tree using the declared eliminators. The observable operation is evaluation of the target term at closed argument tuples satisfying the relevance-indexed readiness judgment of definition 121.5.

The layer adds no conversion rule, universe rule, identity principle, coinductive object, or erasure rule. In particular, its compiler may not delete a reflexive equality constraint and does not assume K.

The delta has a sharp consequence. A covered clause declaration can now be accepted and translated, but two overlapping source rows no longer denote two unconditional equations. First-match selection determines which residual part of each row remains; generated equations are attached to those residual parts. This repairs the failure of reading an ordered clause list as an unordered equational theory.

Remark 121.2 — Status of inherited results

Timpl-clauses has indexed families and single-argument structural recursion only as specified in definition 121.1. Coinduction is not in the signature. Normalization, canonicity, and decidable conversion apply to the full named Timpl kernel by theorem 111.76, theorem 49.18, corollary 49.20; clause compilation proves none of them again. Universe consistency and erasure remain separate properties. Every statement in this chapter that mentions target evaluation is relative to its explicit evaluation interface. Identity remains intensional, and no computed-univalence rule is present.

Rows match in order

Dependency changes the types of the columns, but not the elementary reason that specialization preserves first-match order. We isolate that reason before putting telescopes on the matrix.

Definition 121.3 — Nondependent clause matrices

Fix finite constructor signatures, and let p::=_c(p1,,pk) be nondependent patterns. A simple clause matrix Pa is a finite ordered list of equal-width pattern rows, each carrying an action ai. Matching a constructor-value tuple returns the action on its least matching row; _ matches every value.

With the selected column first, write Sc(Pa) for constructor specialization. It processes rows in order:

  1. replace a c-cell by its child patterns;

  2. a cell headed by dc deletes the row; and

  3. a wildcard is replaced by k wildcards.

The other cells and the action are unchanged. The default matrix D(Pa) retains, in order, just the rows whose selected cell is a wildcard and removes that cell. It is the branch action for a constructor whose head does not occur in the selected column. Selecting another column first means permuting that column to the front, applying these operations, and restoring the residual order.

Lemma 121.4 — Matching decomposition preserves the least row

Let the first component of a constructor-value tuple be c(w1,,wk). The least row of Pa matching that tuple has action ai if and only if the least row of Sc(Pa) matching (w1,,wk,v2,,vn) has action ai. If c does not occur as a head in the selected column, the latter matrix may equivalently be replaced by D(Pa) and the constructor children omitted. In both statements the residual row has the same original row number i.

Proof of Lemma 121.4 — Matching decomposition preserves the least row

Proof. Inspect rows in their original order. A row headed by c matches the first component exactly when its children match w; a row headed by a different constructor matches neither side; and a wildcard row matches both sides. These are exactly the three clauses defining Sc, and none permutes retained rows. Hence the first successful row is the same. When c is absent from the column heads, only wildcard rows survive, which is exactly D. ◻

For the two Boolean rows tt_0__1 specialization at tt gives the one-column rows (_0),(_1), whose least action is 0. The set of written heads in the first column is {tt}, so the ff branch uses the default matrix (_1) and returns 1. Thus the wildcard action is copied into the true specialization but remains shadowed there; it becomes least only in the default branch.

This is the exact nondependent boundary taken from Maranget’s matrix decomposition: specialization, a default matrix, and preservation of ordered matching. His decision-tree heuristics may choose any useful column and analyze sharing, exhaustiveness, and redundancy in an ML pattern language. They establish no dependent substitution, index refinement, or Timpl typing claim. Timpl-clauses keeps only the decomposition lemma and fixes the first blocker of the first surviving row. Its dependent matrix state replaces the rectangular fringe by a typed frontier telescope.

Definition 121.5 — Readiness by relevance

Every Timpl binder has relevance ϱ{runtime,erased}. The typed readiness judgment for a supplied argument is generated by v:Av is a closed runtime valuevreadyruntime:An:An is in closed normal formnreadyerased:A. Runtime values are lambdas, pairs of runtime values, and declared constructors whose runtime children are runtime values. The erased children stored under such a constructor need only be closed and well typed: they are not themselves tested by the readiness judgment, and no value grammar or evaluation-context position descends into them. By contrast, an erased argument supplied to a function binder satisfies the displayed erased rule and hence is a closed normal form before beta reduction. A tuple is ready when every component satisfies the rule indexed by its declared relevance. Only a runtime-relevant family component may be split, and then its weak-head form must be a declared constructor. Parameters such as A:Ui need not themselves have constructor form. For the vector constructor used below, the index argument of vcons is erased, while its element and tail arguments are runtime.

Fix a universe level j, a Timpl telescope Δ, and a result-type derivation ΔB:Uj. For Δ=(x1:A1)ϵ1,ϱ1,,(xk:Ak)ϵk,ϱk, define its iterated product by ΠB:=B,Π(x:A)ϵ,ϱ,ΔB:=x:Aϵ,ϱΠΔB. This notation retains every explicitness and relevance annotation. Put FΔ:=ΠΔB. For a substitution σ:ΩΘ, its identity extension across the recursive identifier is σf:(f:FΔ,Ω)(f:FΔ,Θ),σf(f):=f,σf(x):=σ(x)(xΘ).

A dependent pattern matrix is useful only after its binding and matching actions have been made exact. Let v:Δ be a closed well-typed ready tuple.

Definition 121.6 — Pattern matching and the source step

First define the binding judgment binds(p,a,θ;O). It collects the bindings and the finite set O of inaccessible obligations made by a pattern at a closed ready argument. Such an argument may be an erased type, lambda, or index. Constructor form is required only when the pattern at that exact position is a constructor pattern. Write εsub for the empty pattern-variable substitution.

  1. binds(x,a,[a/x];) holds for any closed, well-typed argument a.

  2. The judgment binds(c(p1,,pk),c(v1,,vk),θ;O) holds if the component binding judgments hold, their substitutions have disjoint domains whose union is θ, and their obligation sets have union O. Runtime children satisfy the runtime readiness rule; an erased constructor child is merely a closed well-typed payload and is not traversed by evaluation. Distinct constructor heads do not match.

  3. binds(.t,a,εsub;{(a,t)}) holds for any closed well-typed argument a; an inaccessible pattern contributes no binding and records the value at its exact nested position.

  4. binds(!,a,θ;O) never holds.

The relation matches(p,a,θ) holds if the component binding substitutions have disjoint union θ and every recorded (a,t)O satisfies at[θ]. Thus all pattern variables are collected before inaccessible terms are checked. Given ordered rows fpi=ei, the source root step at a selects the least i for which matches(pi,a,θ) holds, where θ:Θi, and returns ei[θf], leaving the explicitly typed recursive identifier f:FΔ unchanged. In the source dynamics, that remaining variable is interpreted by the declared head f. The tuple a is ready in the sense of definition 121.5. Consequently a runtime split-family position has constructor form when a constructor pattern inspects it, while an unrelated erased type or lambda in the same tuple need not have constructor form.

The inaccessible clause is a compile-time assertion. A successful compiler proves its equality while specializing the matrix and erases the dot before run time. The source matching relation specifies the accepted equations; it is not an additional equality-deciding operation in the target.

Example 121.7 — Overlap determines residual equations

Consider the ordered rows choose(tt,n)=0,choose(b,n)=suc(0). The first column is blocking, so the case tree splits it. Its tt branch uses the first row. Its ff branch uses the second row. Hence choose(tt,n)0,choose(ff,n)suc(0). The unqualified assertion choose(b,n)suc(0) is false at b=tt and is not a generated equation.

Exercise 121.1

★☆☆ Reverse the two rows of example 121.7. Construct the resulting case tree and list all of its judgmental equations. Explain why the constructor row is unreachable. (Five lines.)

Definition 121.8 — Raw rows and their written maps

Let f:FΔ. A nonabsurd raw row consists of a pattern-variable context Θi, a written pattern tuple pi, and its written row map ρ^i:ΘiΔ. This map reads a variable as that variable, a constructor pattern as the corresponding constructor term, and an inaccessible cell .t as the term t the programmer actually wrote. It does not assert that t is forced. The row also carries a derivation in the explicit recursive-identifier context f:FΔ,Θiei:B[ρ^i]. Every occurrence of f in ei is therefore typed at FΔ. The source card further requires each such occurrence to be a saturated call whose designated argument is a certified direct child. Thus ei[σf] specializes the pattern variables and preserves the recursive identifier with its type. Formation of ρ^i checks each written dot term at its exact pattern position in Θi, after the stable reordering of independent declarations permitted by its acyclic dependency graph. It checks no forced-term equality. An absurd source row has the same well-scoped pattern data but no total written map at the absurd cell and no right side. It asserts that the cell has no reachable constructor. Every pattern variable occurs once.

During compilation, a surviving row carries its original number i, its residual patterns, a partial assignment of its pattern variables, and a finite ordered list O of typed residual obligations. New obligations are appended in left-to-right pattern order. An obligation is either an equality ut:A, or a suspended forced match pforcedByu:A. A selected dot creates an equality from the forced branch term u and the written term t. A constructor pattern against a neutral forced image creates the second form instead of assuming that the pattern was accepted. Later matrix specialization substitutes through both forms. At a frontier map ρ:ΘΔ, the row-map invariant says that every well-typed match of all residual patterns by a substitution υ:ΩΘ, with completed pattern-variable assignment τ:ΩΘi, satisfies (O[υ] holds)ρ^iτρυ. Here O[υ] holds when every equality is judgmental and every forced match succeeds structurally at the instantiated constructor head. The initial row state has no residual obligations. The implication follows componentwise from the definition of matching: constructor cells recurse, variables supply the assignment, and written dots supply exactly the equality premise that was not assumed when the raw row was formed.

A clause matrix at a frontier Θ is a finite ordered list of such residual row states. Every state has one pattern for each declaration of Θ, in frontier order, followed by either its original right side checked under f:FΔ, or an absurd marker.

Timpl-clauses uses a deliberately mixed checking time. Scope, linearity, the written row map, and the body typing derivation are checked for every source row before matrix compilation. Forced-dot equalities, suspended forced matches, and absurd assertions are checked only when their row becomes the least surviving candidate; a shadowed row needs only the former scope and body checks. One general dependent-clause compiler permits the right side itself to be checked after splitting, whereas Agda 2.5.4 retains a separate pass that checks each clause individually before building the combined case tree. The Timpl-clauses policy is neither one: bodies are checked early against written maps, but reachability-sensitive dot and absurd obligations are checked late. In particular, it does not claim to describe the interaction-hole behavior of Agda.

For append, the elaborated rows carry more information than the two equations at the chapter opening. Extend the iterated-product notation by the following exact recursive abstractions and applications: λe:=e,λ(x:A)ϵ,ϱ,Δe:=λϵ,ϱ(x:A).λΔe,f@():=f,f@(x:A)ϵ,ϱ,Δ(a,b):=(fϵ,ϱa)@Δ(b). Thus no metadata is hidden. Put Δapp:=(A:Ui)imp,erased,(m:N)imp,erased,(n:N)imp,erased,(xs:Vec(A,m))exp,runtime,(ys:Vec(A,n))exp,runtime. The complete elaborated type is Fapp:=ΠΔappVec(A,n+m),append:Fapp. With arguments in that order, the two written row maps are represented by append(A,.0,n,vnil,ys)=ys,append(A,.suc(k),n,vcons(k,a,xs),ys)=vcons(n+k,a,append(A,k,n,xs,ys)). The dots record the equations m=0 and m=suc(k) forced by the constructor result indices. They do not request a split on m. In this declaration A,m,n, and the constructor index k are erased; the vector values and their elements are runtime. The compiler splits the runtime vector, not the inaccessible length.

In particular, put Θs:=(A:Ui)imp,erased,(k:N)imp,erased,(n:N)imp,erased,(a:A)exp,runtime,(xs:Vec(A,k))exp,runtime,(ys:Vec(A,n))exp,runtime. Put Γs:=append:Fapp,Θs,us:=append@Δapp(A,k,n,xs,ys),esraw:=vcons(n+k,a,us). The successor right side has the explicit derivations Γsus:Vec(A,n+k),Γsesraw:Vec(A,n+suc(k)). The constructor has result type Vec(A,suc(n+k)), which converts to the displayed result by construction 28.23.

Exercise 121.2

★★☆ Replace .suc(k) in the second append row by .k. Run the index constraint generated by the vector constructor and identify the judgmental equality that the inaccessible-pattern check would require. State the diagnostic with the forced and written terms. (A quarter page.)

From a matrix to a case tree

The compiler carries a frontier telescope, the typed argument context at one matrix node, rather than a list of untyped columns. When a split introduces constructor arguments, their declarations replace the selected declaration in that telescope. Later column types are simultaneously specialized by the unifier substitution. This dependency invariant is the part lost by a compiler that treats the matrix as a rectangular array of names.

For append, the naive rectangular step would replace the vector pattern by its three subpatterns but retain the old columns: (A,m,xs,n,ys)(A,m,k,a,zs,n,ys). The boxes mark the selected column and the entries incorrectly put in its place; they are not an additional pattern form. The right tuple no longer has the frontier’s arity, k,a,zs have no declarations there, and the result type still mentions unspecialized m. The typed step instead applies [suc(k)/m], replaces the vector declaration by k:N,a:A,zs:Vec(A,k), and substitutes through the result type. This is the exact point at which an untyped rectangle fails.

Definition 121.9 — Typed case trees

A typed case tree for a frontier Θ, a frontier map ρ:ΘΔ, and result B[ρ] is generated by the following forms.

  1. leaf(i,e) records the least surviving source row i and a derivation f:FΔ,Θe:B[ρ]. In a nonrecursive declaration, f is not free in e.

  2. split(x,{c(σc,Cc)}) requires x:D(u) in Θ. For every constructor c:(y:Δc)D(vc), restricted unification of (u;x)Ξ;D(vc;c(y)) is either positive or negative. A positive result supplies the dependency-preserving substitution σc and the subtree Cc at the specialized frontier. A negative result supplies a conflict or cycle certificate and has no subtree.

  3. absurd(x,{νc}) records a negative certificate νc for every constructor of the family of x.

There is no node for a stuck unification problem. Reading the branches in constructor declaration order fixes a printed representation, while source row order fixes leaf priority.

Definition 121.10 — Valid Timpl-clauses tree

A typed case tree is valid when:

  1. every split result is positive with its typed substitution, or negative with a conflict or cycle certificate; no stuck result occurs;

  2. every specialized leaf has the checked result type at its frontier;

  3. positive and negative alternatives together account for every declared constructor of the selected family, in declaration order; and

  4. every occurrence of f in a recursive leaf is a saturated call, and its designated argument uses the direct-child component supplied by the corresponding structural eliminator together with the branch-path child certificate.

These are the four premises of the indexed valid-tree interface used by theorem 78.19, instantiated here by ordered Timpl-clauses rows.

Definition 121.11 — Restricted-unifier schedule

This chapter gives the transition system of definition 78.9 one fixed schedule. Maintain an ordered equation list, always inspect its leftmost equation, and use deterministic weak-head forms. Tag a variable old when it was in the frontier before the selected constructor split and fresh when it belongs to that constructor’s telescope. For an equation between two distinct variables, list the two possible orientations in this order: eliminate a fresh variable before an old one, and otherwise eliminate the variable later in the ordered frontier telescope before the earlier one. An orientation is admissible only when its occurs check succeeds and the following stable move is legal. For a candidate x:=t, move x rightward to immediately after the rightmost declaration free in t, if any such declaration is later than x, crossing declarations one at a time and preserving the order of every other declaration. The move is legal exactly when every crossed declaration is independent of x; it is then unique. Choose the first admissible orientation. Thus an injectivity equation r=q, with old r and fresh q, has the canonical solution q:=r. For any other equation whose left side is not a variable and whose right side is, orient that right-hand variable to the left; then try the solution transition, the indexed self-unifiability check followed by injectivity, conflict, and cycle, in that order. If none applies, return the remaining ordered list as stuck. Equations exposed by an injectivity step replace the selected equation in constructor-telescope order. A solution in any equation uses the same stable move and is applied immediately to the telescope and the whole remaining list. An equation x=x is not a distinct-variable equation and remains stuck unless a constructor rule changes its form; the schedule adds no reflexive deletion. The recursive index self-check uses this same schedule.

This policy makes every finite transition trace and every returned answer unique. It does not add reflexive deletion, and it is not a local proof that all weak-head computations or recursively requested index self-checks terminate. The totality result below therefore states that missing premise explicitly.

The split node is both an execution plan and a proof plan. At run time a constructor value selects one subtree. In the kernel translation the same node becomes one application of the family eliminator; its motive contains the frontier substitution and the homogeneous equality constraints.

Definition 121.12 — The leftmost compiler

Let M be an ordered matrix at a typed frontier. The partial function compile(M) has exactly the following result forms: R::=ok(C)uncovered(Θ,π)stuckEq(E)stuckForce(i,z,c,u)badDot(i,u,t,A)badAbsurd(i,c,π)badRelevance(i,z). Here π is the constructor path, E is the ordered unsolved unification problem, and stuckForce(i,z,c,u) says that row i demands head c at the eliminated declaration z, but its typed forced image u is neutral. The remaining three errors carry, respectively, the forced and written terms at their type, a reachable constructor and path, or the erased blocking declaration. There are no implicit abort outcomes. The function is defined as follows.

  1. If M is empty, return uncovered(Θ,π) for frontier Θ and constructor path π.

  2. Apply ordered-row priority before inspecting later assertions. If the first surviving row carries a row-local reachable-absurd marker, take its leftmost marker and return badAbsurd(i,c,π) using the constructor recorded by the marker. If it carries a suspended forced match, retry the leftmost such task’s forced-image matcher after the accumulated substitution. A matching constructor consumes that task structurally; a different head deletes the row and restarts priority; a still-neutral image returns stuckForce(i,z,c,u).

    If the first row has no suspended forced match and no unresolved constructor or absurd pattern, turn each remaining dot cell into the equality between its image under the accumulated frontier substitution and its written term. Check only that row’s equality obligations, in list order, against the accumulated substitution. If they hold, complete its partial pattern-variable assignment to τ, return ok(leaf(i,ei[τf])), and discard every later row without validating any dot or absurd assertion in those rows. The factorization needed to type this leaf is lemma 121.15. If an obligation of the first row is not forced, return badDot(i,u,t,A).

    The returned tree carries no redundancy warning: Timpl-clauses accepts an unreachable later row silently because ordered priority never selects it.

    Otherwise scan the patterns of that first surviving row from left to right and select its leftmost blocking pattern, in the sense of definition 121.1. Encountering an unresolved constructor or absurd pattern in an erased column returns badRelevance(i,z).

  3. If the leftmost blocking column contains x:D(u), enumerate the constructors of D. For each constructor c, run restricted unification with the schedule of definition 121.11 on (u;x)Ξ;D(vc;c(y)). A stuck result returns stuckEq(E) with the unsolved equation list. A negative result is recorded in the node. For a positive result, specialize the selected pattern cell exactly once, in original row order, against c(y), by the following four exhaustive clauses.

    1. A selected c(p1,,pk) contributes its k=|Δc| subpatterns; a selected d(p) with dc deletes the row.

    2. A selected variable row is retained, records the binding x:=c(y), and contributes the fresh variable-pattern tuple y of length |Δc|.

    3. A selected inaccessible row .t is retained with the residual obligation c(y)t, after applying the positive substitution to both sides, and contributes the fresh variable-pattern tuple y. Later specialization continues to substitute through the obligation. It is checked only if that row becomes the first nonblocking candidate; judgmental equality then discharges it, while failure reports the forced and written terms.

    4. A selected absurd row is retained with a row-local reachable-absurd marker in every positive constructor branch and contributes the fresh variable-pattern tuple y. The recursive compiler invocation raises the marker only if that row becomes the least surviving candidate. A prior successful row instead discards it as redundant. The same priority convention applies to an invalid dot row.

    After row specialization, apply σc to the frontier and row annotations. This frontier contains both surviving old declarations and the newly introduced constructor-telescope declarations. For every declaration z eliminated by σc after the selected cell has been expanded, run the recursive forced-image matcher forceσc(pz,σc(z)). A variable records its forced image; a dot records their residual judgmental equality; an absurd pattern records a reachable-absurd marker; and a constructor pattern requires the forced image to expose the same head and recursively matches its subpatterns against the forced constructor arguments. Different heads delete the row. A neutral forced image against a constructor pattern stores the typed suspended task c(p)forcedByu; it is retried, and can return stuckForce, only if that row becomes the first surviving candidate. This matcher consumes the one pattern cell for z and creates no frontier cell. In particular, the append successor branch uses m:=suc(k) to validate and remove .suc(k); it does not expose a second k-column.

    Thus a retained row replaces the selected pattern by exactly |Δc| patterns and removes one pattern for every other declaration eliminated by σc. The specialized frontier performs the same replacement and removals, so row and frontier arities agree. Recursively compile the resulting specialized matrix. Negative constructor branches have no matrix and therefore perform none of these row actions.

  4. If every constructor is negative, return ok(absurd(x,{νc})). Otherwise return ok of the split node after every positive branch has compiled. A positive branch with an empty specialized matrix returns uncovered with that constructor appended to the path. Any other diagnostic returned by a recursive branch propagates unchanged.

The algorithm never guesses a later split in order to escape a stuck leftmost one. The declaration order of rows, columns, and constructors together with definition 121.11 therefore gives at most one result.

Lemma 121.13 — Termination of matrix compilation, relative form

For a finite well-scoped Timpl-clauses matrix, suppose every restricted-unifier invocation made by compile, including its recursive index self-checks and required weak-head computations, terminates. Then compilation terminates in exactly one of the seven result forms displayed in definition 121.12.

Proof of Lemma 121.13 — Termination of matrix compilation, relative form

Proof. The forced-image matcher terminates structurally on the consumed pattern, returning a resolved state, deletion, or one suspended task. A suspended task is retried only after its row becomes first; that retry either consumes the task or immediately returns stuckForce. For a matrix M, let μ(M) be the total number of constructor and absurd nodes in all its patterns. A recursive call is made only after a split. In a fixed positive constructor branch, a row headed by that constructor loses its outer constructor node and retains only the nodes already counted in its subpatterns. A row headed by another constructor disappears. A variable row adds no constructor node, and an inaccessible row adds none. At least one row made the selected column blocking; in the chosen branch that row either loses its outer node or disappears. Thus every recursive call has strictly smaller μ. All other operations traverse finite telescopes, rows, constructor lists, terms, or one of the terminating restricted-unifier runs assumed in the statement. Induction on μ(M) proves termination. ◻

Exercise 121.3

★★☆ A variable row is copied into every positive constructor branch. Explain why counting rows does not prove lemma 121.13, and verify that μ decreases in each branch of a matrix containing the two patterns vcons(k,a,xs) and a variable. (A quarter page.)

The append trace

The first two append columns contain A and the inaccessible index m, so neither blocks. The vector column is the leftmost blocking column. Splitting xs:Vec(A,m) produces the two constraint calculations m=0has solution[0/m],m=suc(k)has solution[suc(k)/m]. Both are solution transitions of the restricted unifier. In the first branch the successor row disappears and the dot .0 is forced. In the second branch the vnil row disappears and the dot .suc(k) is forced. In each retained row the forced-image matcher consumes the old m-cell. The successor frontier therefore contains the constructor index k once, not once from the vector split and again from matching the dot. Each specialized matrix has one unblocked row, so the compiler returns ok(Cappend). Put σ0:=[0/m,vnil/xs],σs:=[suc(k)/m,vcons(k,a,zs)/xs],C0:=leaf(1,ys),es(k,a,zs):=vcons(n+k,a,append@Δapp(A,k,n,zs,ys)),Cs(k,a,zs):=leaf(2,es(k,a,zs)). Then Cappend:=split(xs,{vnil(σ0,C0),vcons(σs,Cs)}). The branch key is the constructor name. Its declared telescope binds k,a,zs in σs and Cs; those variables are not part of the key.

To translate the tree, put Δn,ys:=(n:N)exp,erased,(ys:Vec(A,n))exp,runtime. Then P(m,xs):=ΠΔn,ysVec(A,n+m). Write DA(q):=Vec(A,q). At a successor constructor, the defining equation of BelowDA is BelowDA(P,suc(k),vcons(k,a,zs))BelowDA(P,k,zs)×P(k,zs). Let Hs:BelowDA(P,suc(k),vcons(k,a,zs)). The recursive-result component of Hs is rs:=pr2(Hs):P(k,zs),P(k,zs)ΠΔn,ysVec(A,n+k). For fixed A, let p0 and ps be the following fully annotated methods: p0:=λΔn,ysys,ps(k,a,zs,r):=λΔn,ysvcons(n+k,a,r@Δn,ys(n,ys)). Here k,a,zs,r are the four declarations added to the successor method’s checking context by Vec-elim; k is erased, while a,zs,r are runtime. The argument r is exactly the component rs selected from Hs, rather than an untyped recursive call. Define appendA(m,n,xs,ys):=vind(P;p0;ps;m,xs)@Δn,ys(n,ys). The closed target obtained by theorem 121.18 is the fully annotated core term f:=λΔappappendA(m,n,xs,ys). Let msapp be the proof-dependent recursive step induced by p0,ps in the general lowering construction. Put Hs0:=belowDA(P,msapp,suc(k),vcons(k,a,zs)),rs0:=pr2(Hs0). Write (PA) for the instance of (78.16) at the direct child zs. Then rs0@Δn,ys(n,ys)(PA)f@Δapp(A,k,n,zs,ys)BetaappendA(k,n,zs,ys). Consequently the transformed successor leaf is es(k,a,zs)[f/append](PA)vcons(n+k,a,rs0@Δn,ys(n,ys)), which is the body of ps at its canonical recursive-result component. In the successor method, the displayed erased/runtime applications of r have type Vec(A,n+k). The constructor result has length suc(n+k), judgmentally equal to n+suc(k) by construction 28.23, which recurs on its second argument. The same definition gives n+0n, typing the base method. Thus the motive, rather than an inserted cast, checks either branch.

Let xs2:=vcons(suc(0),a,vcons(0,b,vnil)),ys1:=vcons(0,c,vnil),r2:=appendA(suc(suc(0)),suc(0),xs2,ys1),r1:=appendA(suc(0),suc(0),vcons(0,b,vnil),ys1),r0:=appendA(0,suc(0),vnil,ys1). Using the two vector constructor equations Vec-comp of chapter 31 and the addition equations of construction 28.23, the three root computations are r2Veccomp; construction28.23vcons(suc(suc(0)),a,r1),r1Veccomp; construction28.23vcons(suc(0),b,r0),r0Veccomp; construction28.23ys1. Compatible closure of the latter two computations gives the closed endpoint r2vcons(suc(suc(0)),a,vcons(suc(0),b,ys1)). Every step is a target eliminator computation on closed constructor data.

What successful compilation proves

Four facts must be separated. Specialization preserves the residual-row invariant. A selected row then factors through its raw written map. Clause compilation produces a typed case tree with first-match behavior. Only a second translation lowers that typed tree to eliminators.

Lemma 121.14 — Specialization preserves the row-map invariant

Suppose the residual state of raw row i satisfies the row-map invariant at frontier ρ:ΘΔ. If a positive constructor split returns the dependency-preserving substitution σ:ΘΘ and specialization retains this row, then the resulting state satisfies the row-map invariant at ρσ:ΘΔ. Its old obligations are transported by σ, and a selected or forced-image dot contributes the typed equality between its forced image and its written term. If the retained body has derivation f:FΔ,Θe:B[ρ], substitution by the identity extension σf gives f:FΔ,Θe[σf]:B[ρσ].

If either the selected-pattern comparison or a forced-image comparison exposes distinct constructor heads, specialization instead deletes the row. A deleted row contributes no leaf or assertion to the branch. If a forced constructor comparison has a neutral image, specialization retains the row with its typed suspended forced-match obligation; it assumes no constructor equality.

Proof of Lemma 121.14 — Specialization preserves the row-map invariant

Proof. The restricted unifier returns a well-typed substitution for the entire frontier telescope. For a constructor pattern, its literal factorization replaces the selected variable by the constructor applied to its fresh arguments, so matching the residual subpatterns is equivalent to matching the old constructor cell. A variable pattern records that constructor image and puts one fresh variable pattern at every declaration of the constructor telescope. A selected dot .t records the residual equality between the constructor image and t[σ]; using that equality restores exactly the dot component of ρ^i. An absurd pattern retains a reachable-absurd marker in a positive branch. Hence the selected cell is consumed once and replaced by exactly |Δc| cells, matching the constructor telescope. For another declaration eliminated by σ, structural induction on its pattern proves the forced-image matcher sound: matching heads recurse without adding frontier cells, variables record their images, dots retain obligations, and absurd patterns retain markers. A neutral image against a constructor stores exactly the forced-match premise needed by the row-map invariant. That cell and its eliminated declaration are both removed. Constructor disjointness justifies deletion at a different head in either matcher. The recursive compiler call applies ordered priority before either validating an obligation or raising a marker; neither becomes a run-time test. Composing a hypothetical residual match with σ, and then applying the old row-map invariant, gives ρ^iτρσυ, the required new invariant. The substitution lemma applied to σf preserves the declaration f:FΔ and gives the displayed specialized-body judgment; FΔ is closed with respect to the frontier because the declarations of Δ are bound by its iterated products. These cases exhaust the pattern grammar. ◻

Lemma 121.15 — Selected-row factorization

Suppose compilation reaches a leaf frontier Θ with path map ρ:ΘΔ, and the least nonblocking state is raw row i. If all its residual equality and forced-match obligations are discharged, then compilation constructs a typed map τ:ΘΘisuch thatρ^iτρ. Consequently substitution and conversion give f:FΔ,Θei[τf]:B[ρ]. No suspended forced match, dot, or absurd assertion in a later shadowed row is needed for either conclusion.

Proof of Lemma 121.15 — Selected-row factorization

Proof. The residual row has no constructor, forced-match, or absurd blocker. Assign each remaining linear variable pattern to its frontier variable and combine these assignments with the partial assignments accumulated along the path. This constructs τ. Every remaining dot and forced-match obligation has been discharged, so the row-map invariant preserved by lemma 121.14, with υ the identity substitution, gives ρ^iτρ. Applying substitution to the raw body derivation gives ei[τf]:B[ρ^iτ] under f:FΔ,Θ; conversion gives the displayed type. The identity extension is necessary: substituting only τ would not have the recursive identifier in its source or target context. Ordered priority stops before consulting any later row. ◻

Lemma 121.16 — Recursive-leaf replacement through Below

Let xj:D(v) be the designated structural argument in Δ, and let P be the motive used to lower a valid recursive case tree. Suppose a leaf at ρ:ΘΔ has a body derivation f:FΔ,Θe:B[ρ], and every occurrence f@Δ(r) in e is saturated. Suppose also that its designated argument rj:D(w) is a direct child certified by the branch path. For H:BelowD(P,(v;xj)[ρ]), replace such an occurrence by the corresponding component πH(r,q):B[r],q:(w;rj)Ξ;D(v;xj)[r]. Here q is the reflexivity certificate obtained after substituting the recursive call tuple r; its designated component is rj. The resulting leaf e(H) has derivation Θ,H:BelowD(P,(v;xj)[ρ])e(H):B[ρ]. Let m be the case-tree method and ms its proof-dependent recursive step in the lowering construction. Put H0:=belowD(P,ms,v[ρ],xj[ρ]). Let f:FΔ be the lowered function. Then e(H)[H0/H]e[f/f].

Proof of Lemma 121.16 — Recursive-leaf replacement through Below

Proof. Use rule induction on the displayed derivation, strengthened to an arbitrary well-typed subterm after a substitution for its non-f variables. Treat a maximal saturated spine headed by f as one derived application-spine case before descending into ordinary application premises. The source-card check rules out every other occurrence of f. In the derived case, the direct-child certificate selects from H a component with exactly the displayed type B[r], so replacing the occurrence preserves its type. On the canonical package, write (R) for the defining equation of recD and (J) for the reflexivity equation of homogeneous identity elimination. Write (P) for (78.16), instantiated with result family B. The branch-path certificate computes to homogeneous reflexivity, and the selected component calculates as πH0(r,refl)(R)ms((w;rj),belowD(P,ms,w,rj),r,refl)(J)mrbelowD(P,ms,w,rj)(P)f@Δ(r).

For a variable other than f, a constructor, or a constant, the translation is the identity. Universe and type-former cases translate their typed operands by the induction hypotheses. In an application whose head is not the saturated recursive identifier, the two induction hypotheses translate the function and argument; the explicitness and relevance annotations are unchanged. Product, pair, projection, and eliminator cases apply the induction hypotheses to every typed premise. In a binder case, choose a representative whose binder avoids f, H, and the free variables of ρ, and apply the strengthened induction hypothesis in the extended context. For a substituted subterm, identity extension across f commutes with the non-f substitution, while the replacement substitution maps f to the closed f. The conversion case follows from congruence and the unchanged result type B[ρ]. These are all Timpl term and typing-rule families. Congruence combines the displayed component calculation at every recursive occurrence and proves the final judgmental equality. ◻

Theorem 121.17 — Compilation typing

Let f:FΔ be a Timpl-clauses declaration. If every source row is well scoped and its right side checks as in definition 121.8, and if compile(M)=ok(C), then C is a typed valid case tree for that declaration. At a leaf for row i, the stored body is ei[τf] under f:FΔ, at the path type B[ρ], where τ is the map of lemma 121.15. For a recursive declaration, every leaf occurrence satisfies the designated direct-child condition of definition 121.10.

Proof of Theorem 121.17 — Compilation typing

Proof. A returned result is a finite derivation of compile(M)=ok(C) with a completed call tree. Induct on that derivation; this does not use the relative totality lemma. At a positive split, lemma 121.14 preserves every retained row state, and the induction hypothesis types every subtree at its specialized frontier. At a leaf, lemma 121.15 supplies both the map to the raw pattern context and the checked body under f:FΔ at the path type. Every specialization uses the identity extension across f supplied by lemma 121.14; hence no recursive-identifier hypothesis is dropped between the raw row and the leaf. Every negative branch has the conflict or cycle certificate required by definition 121.9, and an absurd node has such a certificate for every constructor. An uncovered branch, stuckEq, stuckForce, badDot, badAbsurd, or badRelevance would have returned its named diagnostic rather than ok(C). Thus unifier outcomes justify every branch, specialization types every leaf, and constructor enumeration is exhaustive. These are the first three clauses of definition 121.10. The source card checks every occurrence of f as a saturated direct-child call, and the branch path retains its child certificate. This establishes the recursive-decrease clause. ◻

Theorem 121.18 — Case-tree-to-eliminator lowering

Let C be any typed valid Timpl-clauses case tree for f:FΔ, independently of how that tree was constructed. Then its lowering through the declared Timpl eliminators is a term f:FΔ,f:=lower(C). For a leaf at path map ρ:ΘΔ storing f:FΔ,Θe:B[ρ], lowering has the judgmental computation f(x[ρ])e[f/f]. The substitution [f/f] is well typed because the source and target of the substituted identifier are both FΔ. On a closed ready tuple following that path, the corresponding family and identity eliminator computations form a finite target reduction to the closed instance of e[f/f]. In the recursive case, direct-child calls are replaced by the corresponding BelowD components recalled in theorem 78.19.

Proof of Theorem 121.18 — Case-tree-to-eliminator lowering

Proof. Each validity clause is a hypothesis of theorem 78.19. Its node construction supplies the typed proof-dependent motive and the positive-branch specializers. At a leaf, lemma 121.16 types the replacement of every saturated recursive call by its BelowD component and proves that the canonical below-package makes that replacement judgmentally equal to e[f/f]. Hence the inherited elimination theorem supplies the displayed type and leaf computation without dropping f:FΔ. On a closed ready tuple, each split scrutinee has constructor form. The family eliminator selects the recorded method, and its equality transports compute on reflexivity. Induction on the finite path gives the finite reduction. No raw clause matrix or first-match premise is used in this lowering stage. ◻

The substantive hypotheses have distinct roles. Raw body typing and selected-row factorization are needed for leaf typing; dropping it admits a branch with an arbitrary result. Positive unifier factorization is needed to specialize dependent later columns; an occurs check alone does not produce their types. Restricted unification is needed to avoid K. Finiteness and first-order clauses make matrix recursion well founded; totality still has the explicit unifier-termination premise of lemma 121.13. Neither statement concerns larger pattern languages.

Write follows(C,a;i,ξ) when the symbolic case-tree dynamics follows the constructor heads of the closed ready tuple a to a leaf for row i, whose residual frontier Θ is closed by ξ:Θ.

Theorem 121.19 — First-match simulation and failure witnesses

Suppose compile(M)=ok(C). Then every closed well-typed tuple a:Δ satisfying definition 121.5, and whose runtime split-family components have canonical constructor form, follows exactly one path. More precisely, row i is the least row with matches(pi,a,θ) if and only if there is a leaf closure ξ for which follows(C,a;i,ξ)andθτξ, where τ:ΘΘi is the selected-row factorization stored at that leaf. Thus clause-to-tree compilation preserves first-match semantics before any eliminator lowering is chosen.

If compilation returns uncovered(Θ,π), the reported sequence of constructor choices is a well-typed symbolic pattern compatible with the frontier constraints and with no source row. Results stuckEq, stuckForce, badDot, badAbsurd, and badRelevance are diagnostics, not coverage witnesses.

Proof of Theorem 121.19 — First-match simulation and failure witnesses

Proof. Induct on the compiler call tree. In the leaf case, the first surviving row has no constructor, suspended forced-match, or absurd blocker, and successful compilation has validated all its residual obligations. By lemma 121.15, it therefore matches every closing input at that frontier with assignment τξ and is the least matching row. Ordered priority discards all later rows before examining their obligations, so a shadowed forced match, invalid dot, or absurd marker cannot change this case.

At a split on a closed runtime value, canonical constructor form selects one declared constructor c. The value’s indices instantiate the constraint (u;x)Ξ;D(vc;c(y)), so the c case cannot have a negative certificate. Successful compilation also rules out both stuckEq and stuckForce. Hence there is one positive c subtree. Specialization deletes a row when its selected cell has a different constructor head or when a constructor pattern in another substitution-eliminated column has a different head from that column’s forced image. Either deletion is sound: the row cannot match the present input. For every retained row, structural induction on the selected pattern and on each forced-image task gives the converse correspondence: the original row matches precisely when the specialized row matches the branch tuple and all its residual obligations hold. Variables record their forced images, matching constructor heads recurse, and inaccessible terms add no run-time choice. Both matchers preserve the order of retained rows. This is the dependent instance of the least-row decomposition mechanism in lemma 121.4. The induction hypothesis gives one leaf containing the least matching specialized row, which is the least original row matching the input. The row-map invariant of lemma 121.14 and lemma 121.15 identify its closed assignment as θτξ.

At an absurd node, a hypothetical closed tuple would give the selected position canonical form c(w). Its indices and element instantiate the same constructor constraint, contradicting the recorded negative certificate νc. Thus no closed tuple reaches an absurd node, and the successful coverage assertion is vacuous in that case.

For uncovered(Θ,π), read the constructor choices on the recursive-call stack from root to failure. Every positive unifier supplies a typed specialization, so their composite types the reported symbolic pattern. The failure occurs precisely when its specialized matrix is empty. Thus no row is compatible with that pattern. A stuckEq result has no positive or negative certificate; stuckForce has no constructor head to compare; and the three bad results reject a row against the Timpl-clauses card rather than exhibit an uncovered input. ◻

The theorem does not assert that every symbolic witness has a closed inhabitant; a pattern may retain a variable of an empty type. The successful direction needs no such inhabitance hypothesis.

Corollary 121.20 — Selected-clause composition

Suppose compile(M)=ok(C), and put f:=lower(C). Let a be a closed ready tuple. If raw row i is least with matches(pi,a,θ), then faei[θf][f/f].

Proof of Corollary 121.20 — Selected-clause composition

Proof. Theorem 121.19 gives the unique leaf, its closure ξ, and θτξ. The leaf body is ei[τf] by lemma 121.15, theorem 121.17. The closed-path computation of theorem 121.18 reduces to that body closed by the identity extension ξf, and then replaces f by f. Identity extensions compose: τfξf(τξ)fθf. The closed leaf is therefore the displayed ei[θf][f/f]. ◻

Exercise 121.4

★★☆ For onlyNil:A:Uim:NVec(A,m)1, compile the single row onlyNil(A,.0,vnil)=. Give the uncovered symbolic pattern and state why, under the separate assumption that Timpl has no closed term of 0, it yields no closed counterexample when A:=Id2(tt,ff). Name the internal no-confusion map used in that argument. Then put A:=1 and give a closed uncovered input. (A half page.)

Generated equations

A leaf path records constructor choices and positive substitutions. Let x be the original frontier variables and z the variables of the residual frontier Θ. Compose the path substitutions to obtain ρ:ΘΔ; each xj[ρ] is therefore a term in z. Suppose the least row at the leaf is raw row i, whose written map and checked body are ρ^i:ΘiΔ and f:FΔ,Θiei:B[ρ^i]. Lemma 121.15 supplies τ:ΘΘi, with ρ^iτρ. Compilation stores ei[τf] in the leaf, and theorem 121.18 gives the leaf equation f(x[ρ])ei[τf][f/f]. This is a judgmental equation because the eliminators and the generated identity transports compute on the constructor and reflexivity data of that path. The identity term reflf(x[ρ]):IdB[ρ](f(x[ρ]),ei[τf][f/f]) is the corresponding propositional equation theorem. It records the same fact but does not add a rule to judgmental equality.

An original row may produce several leaf equations, or none. The compiler emits only those judgmental leaf equations; it has no propositional-equation output. Reflexivity reifies any leaf equation as an identity theorem. For an overlapping later row, a theorem conditional on the proposition that no earlier row matches may instead be derived on demand by case analysis on the finite constructor frontier. That conditional theorem need not compute on a neutral input and is not promoted to a judgmental equation. The global equation asserted by a shadowed row is not generated at all.

Proposition 121.21 — Equation generation is exact

For a successfully compiled Timpl-clauses matrix, the generated judgmental equations are exactly the leaf equations of its case tree. Each leaf equation belongs to the least source row matching its residual pattern. The compiler generates no propositional equations.

Proof of Proposition 121.21 — Equation generation is exact

Proof. By the symbolic substitution dynamics of definition 78.18, a well-typed substitution for Θ follows its recorded constructor path to that leaf. Ordered specialization identifies its least surviving row. Lemma 121.15 constructs the typed map τ, proves ρ^iτρ, and types the stored leaf body. The leaf computation of theorem 121.18 gives the displayed judgmental equation. No other equation is emitted by the construction of definition 121.9. Reflexivity inhabits the corresponding identity type by conversion; a conditional no-earlier-match theorem is a separate derived construction and is not a compiler output. ◻

Exercise 121.5

★★☆ For the matrix g(tt,tt)=0,g(x,ff)=suc(0),g(x,y)=suc(suc(0)), construct the leftmost case tree. List its judgmental leaf equations. Give one valid propositional equation with an explicit no-earlier-match premise, and identify one unconditional source-row equation that is false. (A half page.)

Closed-data simulation

For this theorem, fix the typed readiness judgment of definition 121.5 and the following closed, left-to-right dynamics. For a finite set H of declared heads, evaluation contexts are EH::=[]EHtvEH(EH,t)(v,EH)pr1(EH)pr2(EH)c(qc,EH,t)elimD(P;m;EH)hqEHt(hH). The function-side context EHt is available at every application node, including erased applications, so it can expose the leftmost head beta redex. Only the argument-side context vEH is restricted to a supplied runtime argument; erased arguments are already ready and have no runtime child to evaluate. In the declared-head frame, q is the ready prefix and EH occupies the selected supplied runtime argument; t is the remaining supplied suffix and may be empty. Thus the frame includes a partially applied call while one of its supplied arguments evaluates and a saturated call before all of its runtime arguments are values. Erased arguments are closed normal forms and are skipped. The declared-head frame stops at the declared arity of h; applications to a result of function type use the ordinary EHt frame. In the constructor frame, qc is an admissible constructor prefix: every earlier runtime child is a value, while every earlier erased child is merely a closed well-typed term. The hole occupies the selected runtime child; there is no constructor frame whose hole is at an erased child. Thus vcons(n+k,a,EH) is a valid frame after closed substitution even when the erased index n+k is not normal. The eliminator form ranges exactly over the Boolean, natural-number, identity, and vector eliminators of the Timpl card, with the scrutinee in the displayed position; motives, types, and methods are not evaluated before a branch is selected. Writing ϵ,ϱ for the matching inherited Timpl application metadata, its beta root is (λϵ,ϱx.e)ϵ,ϱa0Te[a/x]. This root is available exactly when areadyϱ:A: a closed runtime value for ϱ=runtime, and a closed normal form with no runtime child for ϱ=erased. The Timpl root relation otherwise consists of the two pair projections, the two Boolean computations, the zero and successor natural-number computations, identity elimination at reflexivity, and the vnil and vcons computations of vector elimination. These are precisely the computation rules already listed for the frozen kernel; there is no clause-tree root rule in the target.

The source root relation adds one rule. Let a be a closed ready tuple. If row i is the least row with matches(pi,a,θ), then fa0Mei[θf], with recursive occurrences of f unchanged. Define sMt when s=E{f}r, t=E{f}r, and either r0Mr is this source root rule or r0Tr is one of the listed Timpl roots. Define sTt with E and only Timpl roots. Write st for the reflexive–transitive closure of this T relation. The declared-head frame is therefore a source frame; it adds no target reduction rule. Write src(M,a):=fa in the source language, and write src(M,a)w for a finite M-sequence to w, and faw for a finite T-sequence. Both judgments concern closed data; they do not compare open neutral terms.

For a closed source expression s, let trM(s) be the capture-avoiding homomorphic translation that replaces every occurrence of the declared symbol f by its compiled target f. It fixes constructor symbols, maps source values to target values, and commutes with substitution. In particular, for every closed matching substitution θ, trM(ei[θf])ei[θf][f/f]. The special declared-head frame does not translate literally to a target context: the lambda prefix of f may contract before a source argument can take another step.

Define the administrative translation atrM(s) by starting at trM(s) and repeatedly taking the unique deterministic target step only while that step is a beta contraction introduced by replacing a declared head f with f. Stop when the first nonadministrative step would be an argument evaluation or case-tree eliminator computation. The left-to-right context grammar makes this prefix unique. It is finite because each step consumes one of the finitely many argument binders on an active translated declared-head spine. Moreover, trM(s)atrM(s). For a declared-head source frame, these contractions consume exactly the ready prefix q. The residual target is a lambda value applied to the translation of the argument in the hole, so an ordinary target application context can perform that argument’s steps.

Lemma 121.22 — One-step closed simulation after administrative beta reduction

Let M be a successfully compiled Timpl-clauses declaration with target f. Let s and s be closed source expressions. If sMs, then atrM(s)atrM(s). If this step is the clause-root step selected by the least matching row i, with matching substitution θ, then it specializes to faei[θf][f/f]atrM(ei[θf]).

Proof of Lemma 121.22 — One-step closed simulation after administrative beta reduction

Proof. First consider a root step. The definition of atrM stops before a Timpl root. Homomorphic translation preserves that root, so one target step followed by the administrative prefix for s reaches atrM(s). For the added clause root, by theorem 121.19, the closed input follows the case-tree path to the same least row i. The factorization of lemma 121.15 identifies its closed leaf assignment with θ. The composed root result corollary 121.20, using the independent lowering of theorem 121.18, therefore reaches exactly ei[θf][f/f]. Its administrative beta prefix reaches atrM(ei[θf]). The sequence to the row body can be empty only for a nullary declaration whose compiled target is already its sole row body.

For a source step inside an ordinary shared evaluation frame, structural induction on the frame lifts the finite root reduction through the corresponding target frame. In the source-only frame fqE{f}t, administrative beta contraction consumes q and exposes a target lambda value immediately to the left of the translated hole. The ordinary target frame vE then lifts the induction hypothesis for the step in that hole; any newly enabled administrative contractions produce atrM(s). These cases exhaust the grammar of E{f}. ◻

Theorem 121.23 — Target evaluation reproduces source evaluation

Let M be a successfully compiled Timpl-clauses declaration with target f. If src(M,a)w for a closed argument tuple of the form just specified and a closed value w, then fatrM(w). If w contains no occurrence of the declared source symbol f, this is the target evaluation faw. Let i be the least source row matching a, with matching substitution θ. The initial source clause-root step is simulated by a finite sequence of compatible target steps to the body of that same least source row: faei[θf][f/f].

Proof of Theorem 121.23 — Target evaluation reproduces source evaluation

Proof. The displayed clause-root sequence is corollary 121.20; it composes theorem 121.19 with theorem 121.18, using lemma 121.15. For the evaluation claim, induct on the length of the given finite M-sequence. At length zero the source term is already the value w. A value has no active evaluation position, so atrM(w)=trM(w). For a first step sMs, lemma 121.22 gives a finite target sequence from atrM(s) to atrM(s). Concatenate it with the finite target sequence obtained from the induction hypothesis for the remaining source steps. Finally, trM(src(M,a))=fa reduces by its administrative prefix to atrM(src(M,a)), so the concatenated sequence has the required endpoints. Recursive calls need no separate premise: each is a later clause-root step in the finite source reduction and is covered by the same one-step lemma. ◻

The closed-data qualification is load bearing. At a neutral vector xs, the vector eliminator in appendA(m,n,xs,ys) is stuck, so neither append leaf equation applies. The theorem also says nothing about Agda’s complete clause language, Equations’ recursive programs, guards, coinduction, or arbitrary higher-order unification.

Impossible branches are certificates

An omitted branch and an absurd branch have different evidence. Omission is accepted only when constructor-index unification is negative. The explicit ! pattern asks the compiler to check the same fact and gives a location for its diagnostic.

Example 121.24 — A rejected absurd assertion

The hand-compiled head example and its forced dot already belong to construction 78.7; the general compiler reproduces that trace without adding a new admissibility argument. By contrast, the declaration with domain b:2, left side h(!), and no right side is rejected. Both tt and ff are positive constructor branches, so neither can supply the negative certificate demanded by an absurd node.

The identity-family boundary is inherited unchanged. Regard identity at a fixed left endpoint as the family D(x):=IdA(a,x). Splitting e:D(a) against reflexivity generates the full constraint (a;e)x:A;D(x)(a;refla). Its first component is the neutral index equation a=a. Restricted unification cannot delete that equation, so it never reaches a solution for the dependent element component and a K-shaped clause is rejected. This is different from a constructor equation such as 0=0:N: the injectivity transition reduces the two equal nullary constructor heads to the empty argument telescope. Calling the identity branch absurd would not help, because reflexivity is compatible with the index even though the restricted run is stuck.

Source boundary. Maranget’s Warnings for Pattern Matching (2007) and Compiling Pattern Matching to Good Decision Trees (2008) own the nondependent specialization/default substrate and decision-tree heuristics summarized by definition 121.3, lemma 121.4 [Mar07, Mar08]; that work proves no typed dependent frontier result. Cockx and Abel, Elaborating Dependent (Co)pattern Matching: No Pattern Left Behind, JFP 30:e2 (2020), Contributions and Section 5.3, own the general core-language elaboration from dependent patterns and copatterns to typed case trees and its first-match correctness theorem [CA20]. The Timpl-clauses stage is strictly smaller: it has only the frozen Timpl families, first-order patterns, no copatterns, a fixed first-surviving-row and leftmost-blocker policy, explicit stuck diagnostics, and observations only at closed ready data. It additionally proves the written-row factorization needed by its early-body/late-obligation policy. Thus the theorem of theorem 121.19 is a new proof for this restricted card, not a transfer of the Cockx–Abel theorem. The exact checking-time delta was stated after definition 121.8.

The homogeneous constraints and restricted no-K unification used by the Timpl stages are the construction of Cockx, Devriese, and Piessens [CDP16], already developed at the indexed-family interface in section 78.4. Lieverse’s 2024 case-tree translation motivates the shape of the separate second stage of theorem 121.18, after a typed valid tree is already given [Lie24]. They are not no-K evidence: Lieverse’s unifier includes reflexive deletion, proves deletion and injectivity correctness using K, and omits the cycle transition retained by definition 78.9. The development also fixes its own universe of datatype descriptions. It therefore supports no claim about the restricted unifier, K-freedom, the Timpl-clauses stage, or every Agda declaration. Cockx–Abel’s historical account identifies the general second-stage lineage with the 2006 elimination construction of Goguen, McBride, and McKinna; this secondary attribution does not prove any theorem of this chapter. Cockx’s 2017 thesis supplies the later detailed account [CA20, Coc17]. Cockx’s dependent-programming course examples guide exercises but donate no compiler theorem. This chapter owns its smaller Timpl-clauses card, deterministic partial leftmost compiler, written-row factorization, and the composition corollary. No theorem about full Agda, Equations, Maranget’s optimizer, or Lieverse’s broader translation is transferred to Timpl-clauses.

Suggested first pass.

None of these problems is a prerequisite for a later chapter. Begin with exercise 121.6, exercise 121.7.

Exercise 121.6

★★☆ For external universe-level parameters i,j,k, let zipWith:{A:Ui}{B:Uj}{C:Uk}(ABC)n:NVec(A,n)Vec(B,n)Vec(C,n). Compile the two ordered clauses zipWith(A,B,C,f,.0,vnil,vnil)=vnil,zipWith(A,B,C,f,.suc(r),vcons(r,a,as),vcons(.r,b,bs))=vcons(r,fab,zipWith(A,B,C,f,r,as,bs)). Each displayed row contains all seven telescope patterns: A,B,C,f,n, and the two vectors. Split the first vector and then the second. Trace both index constraints, identify every negative branch, give the typed case tree and its two leaf equations, and justify the recursive call using the designated first-vector child. (A page.)

Exercise 121.7

★★☆ Let A:Ui, a:A, and K:P:IdA(a,a)UjP(refla)e:IdA(a,a)P(e). Attempt to compile the single clause K(P,p,refla)=p. Write the generated homogeneous telescopic equality, expose its index and element components, and give the complete restricted-unifier run. Then attempt to replace the pattern by ! and identify the compatible constructor that invalidates the absurd assertion. (Half a page.)

Exercise 121.8

★★☆ For each of the following declarations, classify the first compiler result as success, uncovered constructor, stuck unification, stuck forced image, unforced inaccessible term, reachable absurd pattern, or erased-blocker relevance error: a complete Boolean negation; only the tt clause; the identity-family K clause; append with .k in place of .suc(k); h(!) at 2; the single row E(tt)=0 whose Boolean binder is declared erased; and the ordered rows F(u,.u,reflu,tt)=0,F(w,tt,s,z)=suc(0) for F:x:2a:2Id2(a,x)2N. Give the certificate, unsolved equation, or neutral forced image reported in every rejection. (A half page.)

Exercise 121.9

★★★ Practical project.dependent-clause-compiler Implement in Kappa the finite five-declaration decision model below. Give the model structured patterns, rows, and frontiers. Preserve row order, select the leftmost blocker, and execute all four selected-cell cases of definition 121.12. Process that cell once; a separate recursive matcher must consume any other substitution-eliminated cell without widening the frontier. Derive a generic split tree and its assignment lists from the specialization results, check every retained row’s width, and compute wildcard shadowing by ordered priority. Exercise the selected inaccessible-pattern case on an indexed successor with a nontrivial child: whole-term comparison must accept .suc(k) and reject .suc(0). This may be a hidden acceptance guard; the five public records remain the observable interface.

The exact records are: append-123 evaluates [1,2]+[3]=[1,2,3] by interpreting the derived tree; an independent append implementation does not satisfy the record. The overlap-bool record gives true 0, false 1, and a shadowed wildcard. The missing-false record reports path false; bad-dot prints forced suc(k) versus written k. The absurd-bool record names both reachable Boolean constructors. That finite aggregate is a stronger finite observation; the mathematical compiler returns the first badAbsurd(i,c,π) in declaration order. This is a finite acceptance slice, not evidence for arbitrary dependent frontiers, branch-local unification, or the general compiler theorems.

Search the book

Type to search the local edition.