ADR-0019 · Typing context split — Finsupp grade-vector + ambient type context
-
Status: Accepted
-
Summary: Typing context split into a Finsupp grade-vector + ambient type context; enables the resource-enforcing rules.
-
Resolves: Q3, Q10
-
Depends-on: 0001, 0016
-
Status: Accepted
-
Date: 2026-06-21
-
Layer: K (kernel — resolves OPEN_QUESTIONS Q3, enables Q10)
-
Related: 0001 (rows-as-Finset; same "let Mathlib supply the algebra" move, applied to grades), 0016 (two-hop; the graded source semantics this shapes), Torczon et al. OOPSLA 2024 (
resource/CBPV/typing.v— the representation ported)
Context
HasVTy / HasCTy (Bang/Core/Typing.lean) judge over Ctx := List (Var × Mult × VTy) —
the multiplicity glued to each binding. The Phase-A rules carry but never
enforce the grade: vvar is (∃ ρ, (x,ρ,A) ∈ Γ) (ρ discarded); ret/app
never scale or add grades. So HasCTy is grade-insensitive — and the graded
metatheory (a real subst_value, zero_usage_erasable, effect_sound — the
QTT payoff and the substance of ◊2) is unprovable. This was surfaced 2026-06-21
when the placeholder subst_value was found to be vacuous (conclusion =
hypothesis). Closing it requires resource-enforcing rules (Q10), and those force
the long-deferred Q3: how is the context represented?
The resource discipline needs the rules to express, at a variable, "grade ρ at
x, zero everywhere else" and, at elimination forms, "split the grades:
γ₁ + ρ·γ₂". Over List (Var × Mult × VTy) with Ctx.add = zipWith:
zipWithis partial — it silently truncates unless both lists share shape and order, so "add two contexts" is only meaningful under an unstated precondition;- "ρ at
x, 0 elsewhere" has no clean closed form (you'd scale-by-0 then patch).
The reference dev (Torczon, resource/CBPV/typing.v) avoids this by keeping
two things, not one:
context n := fin n → ValTy— the types, fixed and shared across a whole derivation;γ : gradeVec n := fin n → Q— the grades, which split, scale, and add (γ = γ₁ Q+ (q Q* γ₂)).
The insight that decides the representation: types are ambient; grades are resources. A resource is consumed and split between subderivations; a typing assumption is not. Gluing them into one list forces them to split together, which is exactly wrong — you cannot scale the grades without scaling the types.
Decision
Split the typing context into two independent components, mirroring Torczon:
HasVTy (γ : GradeVec) (Γ : TyCtx) (v : Val) (A : VTy) : Prop
HasCTy (γ : GradeVec) (Γ : TyCtx) (c : Comp) (e : Eff) (B : CTy) : Prop
GradeVec := Var →₀ Mult -- Mathlib Finsupp; default ⊥/0; the resource vector
TyCtx := List (Var × VTy) -- ambient typing assumptions; shared, no arithmetic
Grade arithmetic comes from Mathlib's Finsupp for free (Var →₀ Mult is a
Module over the Semiring Mult):
Finsupp.single x ρ— "grade ρ atx, 0 elsewhere" (the var rule);γ₁ + γ₂— pointwise, total (no shape precondition);ρ • γ— scale every grade byρ(elimination forms).
The rules then thread grades Torczon-style (the Q10 upgrade):
vvar demands γ = Finsupp.single x 1 with Γ supplying the type; ret/app/
letC/lam scale-and-add. Ctx.scale / Ctx.add (the old List ops) are
retired in favour of the Finsupp • / +.
Companion type-syntax change — the function arrow carries the argument
multiplicity. Torczon's function type is CAbs q' A B; ours is
CTy.arr A B, dropping q'. Without it, lam cannot record how much it uses
its argument and app has nothing to scale the argument's grades by
(T_App: γ = γ₁ Q+ q Q* γ₂) — the resource-enforcing application rule is
unimplementable. So CTy.arr becomes arr : Mult → VTy → CTy → CTy
(A →^q B). This is done now because it is currently cheap: arr is
matched in exactly two places (the lam/app rules); the compiler and LR do
not pattern-match it yet. Deferring until they do would multiply the cost.
Rationale
- Total arithmetic, no hidden precondition.
Finsupp +is defined on all pairs; thezipWithshape-matching wart disappears (and with it a class of latent off-by-one context bugs). - The discipline becomes expressible.
single x 1is the variable rule's premise; there is nothing to encode by hand. - Mathlib supplies the algebra — the same win ADR-0001 took for rows
(
Finsetlattice) we now take for grades (Finsuppmodule). Laws inherited, not proven. - Ports Torczon near-line-by-line.
gradeVec/context↦GradeVec/TyCtx;Q+/Q*↦+/•. The substitution and preservation proofs port with it. - Single source of truth per concern — grades live only in
γ, types only inΓ; they can't disagree.
Rejected alternatives
| option | why not |
|---|---|
Keep List (Var × Mult × VTy), patch the rules | zipWith add stays partial; "0 elsewhere" stays unexpressible; the proofs fight the representation forever. Premature-pragmatism: smaller diff now, permanent wart. |
One Finsupp (Var) (Mult × VTy) | (Mult × VTy) has no meaningful pointwise + (grades add; types must match, not add). Conflates the two concerns the split exists to separate. |
Var → Mult (plain total function) for grades | No finite support ⇒ no canonical 0-default, no decidable equality, no Finset of used vars; loses the Mathlib Finsupp lemma library. |
de Bruijn fin n (Torczon verbatim) | Faithful but abandons our named-variable substitution (Comp.subst x v) and the existing syntax/eval; a larger rewrite than the win justifies pre-◊3. |
Consequences
- (+) Q10's rule upgrade becomes a mechanical port rather than a fight with the
representation;
subst_value'sΓ + ρ·Δisγ_Γ + ρ • γ_ΔoverFinsupp. - (+) Q3 is resolved (was "defer until proofs demand" — the proofs now demand it).
- (−) Signature change to every
HasVTy/HasCTysite:Bang/Core/Typing.lean(definitions),Bang/Spec.lean+Bang/Meta/BinaryLR.lean(statements). Bounded — 3 files, ~40 occurrences. - (−)
subst_valueand the grade-soundness statements get restated against the two-context signature (alreadysorry; no proof lost). - The effect row
e : Effis unchanged and orthogonal — effects annotate the computation, grades annotate variable usage; the split touches only the latter.
Revisit if
- A proof needs the type context to also vary within a derivation (it shouldn't — that would signal a deeper modelling error, not a rep problem).
TyCtx'sListlookup becomes a bottleneck → switch the type side to aFinsupp/FinMaptoo (independent of the grade-vector decision settled here).- The named-variable encoding itself proves too costly at ◊3 and a de-Bruijn
port becomes warranted — at which point
GradeVec/TyCtxmap straight onto Torczon'sgradeVec/context.