1. Introduction
1.1. What is proved
con-leche is a checker for Lean 4. It reads an export of a Lean environment, the stream: every definition, theorem and inductive type, in the official kernel’s own terms. It accepts or rejects the stream, checking what the official kernel checks (on a few features it declines instead; Section 7). It comes with a proof, written in Lean itself, of what an acceptance means: every environment the checker accepts has a model in set theoryConLeche/MainTheorem.lean:L96-L9996theorem model_exists (V : Type w) [SetTheory V]
97 (pins : List NatOpPinSet) (ds : Array Declaration) (env : Env)
98 (accepted : checkDecls .verified pins ds = .ok env) :
99 Nonempty (Model V env) := by
. Each constant is assigned a setConLeche/Denotes.lean:L269-L289269structure Model (V : Type w) [SetTheory V] (env : Env) where
270 /-- the set a constant denotes, per level assignment -/
271 cval : Name → (LevelParam → Nat) → V
272 /-- every stored constant is a member of what its type denotes, at
273 every level assignment (and every variable environment — the type
274 is closed) -/
275 mem : ∀ c ∈ env.consts, ∀ (φ : LevelParam → Nat) (ρ : BVarIdx → V),
276 ∃ T, Denotes cval env φ ρ c.toConstantVal.type T ∧ cval c.name φ ∈ˢ T
277 /-- whatever the built-in `False` denotes is the empty set -/
278 false_empty : ∀ (φ : LevelParam → Nat) (ρ : BVarIdx → V) (F : V),
279 Denotes cval env φ ρ (.const falseName []) F → F = empty
280 /-- whatever the built-in `Eq` denotes is set equality: at a type `A`
281 of the universe the level `u` names, and two of its members `a` and
282 `b`, `@Eq.{u} A a b` denotes the truth value of `a = b`. The three
283 membership premises are the graph's domains — `Eq`'s denotation is a
284 three-fold graph over `univ (Level.eval φ u)`, then over `A`, then
285 over `A` again, and a graph read off its domain says nothing -/
286 eq_equality : ∀ (u : Level) (φ : LevelParam → Nat) (ρ : BVarIdx → V) (E A a b : V),
287 Denotes cval env φ ρ (.const eqName [u]) E →
288 A ∈ˢ univ (Level.eval φ u) → a ∈ˢ A → b ∈ˢ A →
289 app (app (app E A) a) b = eqv a b
, each type denotes a set, and every stored constant is a member of the set its type denotes. The constant False denotes the empty set, and Eq denotes set equality, so every equation the checker accepts is an equality of sets. Hence no accepted environment holds a proof of False, and every accepted theorem is true in the model.
1.2. The shape of the argument
The theorem is relative to a model of an abstract set theory, a structure with membership, extensionality, the usual set-forming operations and a chain of universes closed under themConLeche/SetTheory/Core.lean:L95-L100 95/-- A model of set theory of exactly the strength the checker needs:
96membership, the six ZF⁻ axioms (extensionality, pairing, union, power
97set, regularity, Lean-level replacement), and an ω-chain of
98Grothendieck universes. Infinity is derivable; choice is inherited
99from the meta-logic (`Classical.choice`); see the module docstring. -/
100class SetTheory (V : Type u) where
. By Gödel’s second incompleteness theorem, Lean cannot prove that such a structure exists. The assumption is standard, though: it follows from the existence of ω many inaccessible cardinals, the hypothesis of the Lean development of Carneiro’s model of Lean (Section 8; §7 of OVERVIEW.md says more).
The usual way to prove such a statement, given a typing judgement for the type theory, has two steps: show that the checker accepts only derivable judgements, and show that derivable judgements are true in the model. The first step needs the metatheory of the type theory: that reduction preserves types (subject reduction), that reduction is confluent, that a function type determines its domain and codomain (injectivity of Π), and that a term has one type up to definitional equality. Our proof needs none of it.
Nor does it have a typing judgement. In its place is a description of what the checker does: three inductively defined relations, one for reductionConLeche/Rules/Rel.lean:L101101inductive Red (env : Env) : Nat → Expr → Expr → Prop where
, one for the verdicts of the definitional-equality testConLeche/Rules/Rel.lean:L358358inductive DefEq (env : Env) : Nat → Expr → Expr → Prop where
and one for type inferenceConLeche/Rules/Rel.lean:L549549inductive Infer (env : Env) : Grade → Nat → Expr → Expr → Prop where
(six in the real proof, where premises about lists get relations of their own; Section 7). Their rules are exactly the moves the checker makes, and each rule’s premises are what the checker verified at that point. On the model side there is a total, term-directed interpretationwhitepaper/Fragment/Interp.lean:L139-L146139noncomputable def interp (M : Name → List Nat → V) (φ : Name → Nat) : (Nat → V) → Expr → V
140 | ρ, .bvar i => ρ i
141 | _, .sort u => typeU (u.eval φ)
142 | _, .const c ls => M c (ls.map (Level.eval φ))
143 | ρ, .app f a => app (interp M φ ρ f) (interp M φ ρ a)
144 | ρ, .lam A b => lamV (interp M φ ρ A) fun x => interp M φ (cons x ρ) b
145 | ρ, .pi A B => piV (interp M φ ρ A) fun x => interp M φ (cons x ρ) B
146 | ρ, .letE _ v b => interp M φ (cons (interp M φ ρ v) ρ) b
: every term denotes a set, well-typed or not, and the interpretation uses nothing beyond what is in the term itself. In particular it uses no typing information, nothing that would have to be inferred. Where a typing judgement would say “this term has that type”, there is a semantic invariantwhitepaper/Fragment/WellDenoted.lean:L58-L7858def WellDenoted : (Nat → V) → Expr → Prop
59 | _, .bvar _ => True
60 | _, .sort _ => True
61 | _, .const _ _ => True
62 | ρ, .app f a =>
63 WellDenoted ρ f ∧ WellDenoted ρ a ∧
64 ∃ (p : Bool) (A : V) (B : V → V),
65 interp M φ ρ f ∈ˢ piR p A B ∧ interp M φ ρ a ∈ˢ A ∧
66 (p = true → ∀ x, x ∈ˢ A → B x ∈ˢ univ 0) ∧ (p = false → IsTy A)
67 | ρ, .lam A b =>
68 WellDenoted ρ A ∧ IsTy (interp M φ ρ A) ∧
69 (∀ x, x ∈ˢ interp M φ ρ A → WellDenoted (cons x ρ) b) ∧
70 ∃ B : V → V,
71 (∀ x, x ∈ˢ interp M φ ρ A → interp M φ (cons x ρ) b ∈ˢ B x) ∧
72 (∀ x, x ∈ˢ interp M φ ρ A → IsTy (B x))
73 | ρ, .pi A B =>
74 WellDenoted ρ A ∧ IsTy (interp M φ ρ A) ∧
75 (∀ x, x ∈ˢ interp M φ ρ A → WellDenoted (cons x ρ) B) ∧
76 (∀ x, x ∈ˢ interp M φ ρ A → IsTy (interp M φ (cons x ρ) B))
77 | ρ, .letE _ v b =>
78 WellDenoted ρ v ∧ WellDenoted (cons (interp M φ ρ v) ρ) b
on the term’s set, imposed at every subterm: every application applies a function to a member of its domain, every binder’s domain is a type, and so on. One induction over the three relationswhitepaper/Fragment/Sound.lean:L2322-L23262322theorem soundness [LevelOracle] (m : EnvModel V env) (φ : Name → Nat) :
2323 (∀ {Γ : List Expr} {e e' : Expr}, Red env Γ e e' → RedSem m φ Γ e e') ∧
2324 (∀ {Γ : List Expr} {a b : Expr}, DefEq env Γ a b → DefEqSem m φ Γ a b) ∧
2325 (∀ {Γ : List Expr} {e T : Expr}, Infer env Γ e T → InferSem m φ Γ e T) :=
2326 ⟨red_sound, defeq_sound, infer_sound⟩
then proves three claims at once. A reduction step preserves the denotation and the semantic invariant; a verdict “definitionally equal” means that the two sides denote the same set; and an inferred type contains the term, that is, the term’s set is a member of the type’s set.
1.3. How to interpret a ∀ or a lambda
At every binder the interpretation has to decide how to interpret a ∀ or a lambda. In Lean, ∀ x : A, B is a type of functions when B is a type and a proposition when B is a proposition, and in the model these are different kinds of sets. A function type is a set of graphs. A proposition is a truth value, a set with at most one element: the model has one distinguished set, the point, which every proof of every proposition denotes, so proof irrelevance is built in. The two cases look the same on the page, and telling them apart by the sort of B would take type inference. Our interpretation instead reads the answer off the valueswhitepaper/Fragment/Lib.lean:L477-L481477/-- **The function space, regime read off the fibres.** -/
478noncomputable def piV (A : V) (B : V → V) : V := piR (propB A B) A B
479
480/-- **The abstraction, regime read off the body values.** -/
481noncomputable def lamV (A : V) (F : V → V) : V := lamR (ptB A F) A F
: a ∀ denotes a truth value exactly when every one of its fibres (the set B denotes at a member of the domain) is a truth value, and a lambda denotes the point exactly when every value of its body is the point. The terms are Lean’s own.
Over a non-empty domain this reading agrees with the sort of the body. Over an empty domain it does not: fun h => h.elim, of type False → Nat, denotes the point although its type has sort Type (Section 2.5). This shapes the soundness claims: a reduction or an equality claims nothing about a term that denotes the point (Section 2.7). It also costs two checks that the official kernel never makes (Section 2.8): the checker compares the types of two bodies at lambda congruence and η, and, in one rare case of ι-reduction, it compares a constructor application with the recursor’s arguments and type-checks its fields.
1.4. This document
This document is a pen-and-paper account of that argument on a simplified fragment of the checker. The fragment has no projections, no number or string literals, no mutual inductive types, nesting only one container deep and not under a binder (Section 5 describes the full check, without proof), no quotients, no axioms, no built-in copies of False, Eq, Nat and their kin (the real checker pins these rather than reading them from the stream), no distinction between a theorem and a definition, and none of the checker’s performance devices (memo tables, fuel, the parallel check phase). Section 7 lists every omission with one sentence on what the real proof does about it. The fragment is verified in Lean in a small development of its own, whitepaper/Fragment/, which imports nothing from the main proof and is parametric in the same kind of abstract set theory. The underlined source links lead into the real proof and into that development.
The rest is in seven parts. §2 presents the fragment without an environment: terms and universe levels, the three relations, the abstract set theory, the interpretation, the semantic invariant, and the three claims with their proof. The routine cases of the proof are listed briefly; the cases where the reading of a binder matters, or where a syntactic proof would fail, are discussed in full. §2 ends with the checks that other checkers skip and with a cheaper mode of inference. The claims (Section 2.7) are a good place to start reading. §3 adds the environment, the constants that refer to it, and its definitions: what is stored, what is checked, how a definition unfolds, and the model that grows with it. §4 adds inductive types as least fixed points in the model, their recursors and reduction rules, large elimination, and the consistency corollary: no accepted environment stores a constant whose type is an inductive proposition with no constructors. §5 changes tack. It describes in full con-leche’s check for nested inductive types, whose constructors mention the type being defined inside another one, as in List (Tree α), and their generated recursors, and only sketches how the model extends to them. §6 shows that two features of Lean’s definitional equality that the fragment drops, η for structures and unit-likeness, follow from the extensionality of the model, with one proof case each. §7 lists what was left out, and how the real proof differs from the fragment where they share a feature. Section 8 relates the argument to earlier work.
2. The fragment without an environment
This section presents the core of the argument on a fragment of Lean without an environment: first the terms, the levels and the three relations that say what the checker does; then the set theory, the interpretation, the semantic invariant that replaces a typing judgement, and the three soundness claims with their proofs. Everything here is verified in the fragment’s own Lean development and linked, alongside its counterpart in the real proof.
2.1. Terms
The terms of the fragment are the official kernel’s terms, minus what Section 7 leaves out:
A termwhitepaper/Fragment/Syntax.lean:L29-L4829/-- Terms. -/
30inductive Expr where
31 /-- A bound variable, by de Bruijn index. -/
32 | bvar (i : Nat)
33 /-- The sort `Sort l`. -/
34 | sort (l : Level)
35 /-- A constant of the environment at a level instantiation. -/
36 | const (c : Name) (ls : List Level)
37 /-- Application. -/
38 | app (f a : Expr)
39 /-- Abstraction `fun (x : A) => b`. -/
40 | lam (A b : Expr)
41 /-- The Π-type `(x : A) → B`. -/
42 | pi (A B : Expr)
43 /-- `let x : ty := v; b`. Kept as Lean stores it: the
44 checker substitutes the value (`Red.zeta`, `Infer.letE`), and the
45 interpretation reads the body at the value's value, ignoring the type
46 (`Interp.lean`), as con-leche's `Denotes` `let` rule does. -/
47 | letE (ty v b : Expr)
48 deriving DecidableEq
is a variable ; a sort at a universe level (Section 2.2), with ; an application ; a function ; a dependent function type, or Π-type, , written when does not occur in ; or a let-expression . Types are terms; there is no separate class. A sequence of binders, such as the lambdas of , is a telescope.
The paper writes named variables, with the usual conventions: terms are taken up to renaming of bound variables, and is the capture-avoiding substitutionwhitepaper/Fragment/Syntax.lean:L65-L7565/-- Instantiate the bound variable `k` by `a` (and lower the indices
66above it): `e.inst a` is `e[x := a]` for the innermost binder. -/
67def inst : Expr → Expr → (k : Nat := 0) → Expr
68 | bvar i, a, k =>
69 if i < k then bvar i else if i = k then liftN k a else bvar (i - 1)
70 | sort u, _, _ => sort u
71 | const c ls, _, _ => const c ls
72 | app f b, a, k => app (inst f a k) (inst b a k)
73 | lam A b, a, k => lam (inst A a k) (inst b a (k + 1))
74 | pi A B, a, k => pi (inst A a k) (inst B a (k + 1))
75 | letE T v b, a, k => letE (inst T a k) (inst v a k) (inst b a (k + 1))
of for . The Lean fragment uses de Bruijn indices instead (a variable is the number of binders between its occurrence and its own), and a rule that mentions the types of the variables in scope carries a context , a list of those types. No rule in this paper needs index arithmetic in its named form.
2.2. Levels
Universe levelswhitepaper/Fragment/Level.lean:L29-L4329/-- Universe levels (`Lean.Level` without `mvar`). -/
30inductive Level where
31 /-- `Prop`'s level, `0`. -/
32 | zero
33 /-- The successor level `u + 1`. -/
34 | succ (l : Level)
35 /-- The maximum `max u v`. -/
36 | max (a b : Level)
37 /-- The impredicative maximum: `imax u v` is `0` when `v` is `0`,
38 else `max u v` — so a `∀` whose codomain is a proposition is a
39 proposition whatever its domain's level. -/
40 | imax (a b : Level)
41 /-- A level parameter of the enclosing declaration. -/
42 | param (n : Name)
43 deriving DecidableEq, Repr
are Lean’s, without metavariablesConLeche/Kernel/Expr.lean:L39-L4439inductive Level where
40 | zero
41 | succ (u : Level)
42 | max (u v : Level)
43 | imax (u v : Level)
44 | param (n : Name)
:
with a level parameter of the enclosing declaration. A level means a natural numberwhitepaper/Fragment/Level.lean:L50-L5650/-- The value of a level under a *valuation* `φ` of its parameters. -/
51def eval (φ : Name → Nat) : Level → Nat
52 | zero => 0
53 | succ l => eval φ l + 1
54 | max a b => Max.max (eval φ a) (eval φ b)
55 | imax a b => imaxNat (eval φ a) (eval φ b)
56 | param n => φ n
once a valuation fixes the parameters: , successor, maximum, and is when is and otherwise (as in con-lecheConLeche/Verify/Level.lean:L26-L3126@[expose] def eval (φ : Name → Nat) : Level → Nat
27 | .zero => 0
28 | .succ l => eval φ l + 1
29 | .max l r => Max.max (eval φ l) (eval φ r)
30 | .imax l r => if eval φ r = 0 then 0 else Max.max (eval φ l) (eval φ r)
31 | .param n => φ n
). The impredicative maximum is what makes a proposition whenever is one, whatever the level of : its sort is (rule pi in Section 2.3).
The level oracle. The checker decides and between levels with an algorithm. This paper does not present that algorithm and does not verify it. It assumes two Boolean functionswhitepaper/Fragment/Level.lean:L130-L145130/-- **The level oracle** — the checker's decision procedures for `≤` and
131`=` on levels, assumed sound and complete with respect to `eval`: a
132parameter of the development, never defined here. (`le` is what the
133inductive installs of the environment section consume; the env-free
134fragment uses `eq` alone.) -/
135class LevelOracle where
136 /-- `le u v = true` decides `u ≤ v` at every valuation. -/
137 le : Level → Level → Bool
138 /-- `eq u v = true` decides `u = v` at every valuation. -/
139 eq : Level → Level → Bool
140 /-- Soundness and completeness of `le`. -/
141 le_iff : ∀ a b : Level, le a b = true ↔ ∀ φ : Name → Nat, Level.eval φ a ≤ Level.eval φ b
142 /-- Soundness and completeness of `eq`. -/
143 eq_iff : ∀ a b : Level, eq a b = true ↔ ∀ φ : Name → Nat, Level.eval φ a = Level.eval φ b
144
145namespace Level
with a specification: is answered yes exactly when the value of is at most the value of at every valuation, and exactly when the values agree at every valuation (the real ConLeche/Kernel/Level.lean:L144-L146144/-- Decide `l ≤ r` semantically; `none` is an exhausted comparison. -/
145def leq (l r : Level) : Option Bool :=
146 leqCore defaultFuel (simplify l) (simplify r) 0
, the real ConLeche/Kernel/Level.lean:L164-L167164def isEquiv (l r : Level) : Option Bool := do
165 if l == r then pure true
166 else if simplify l = simplify r then pure true
167 else if ← leq l r then leq r l else pure false
and their soundnessConLeche/Verify/Level.lean:L173-L174173theorem leq_sound {l r : Level} (h : leq l r = some true) :
174 ∀ φ, eval φ l ≤ eval φ r := by
proofsConLeche/Verify/Level.lean:L201-L202201theorem isEquiv_sound {l r : Level} (h : isEquiv l r = some true) :
202 ∀ φ, eval φ l = eval φ r := by
). We write for “the oracle says ”.
2.3. The three relations
The checker is described by three relations over terms, defined together by inference rules: reduction , definitional equality , and inference . A context lists the variables in scope with their types. The complete rule sets are one mutual inductive definitionwhitepaper/Fragment/Rules.lean:L56-L79856mutual
57
58/-- **Reduction**: the head steps of the checker's `whnf`, chained. -/
59inductive Red (env : Env) : List Expr → Expr → Expr → Prop where
60 /-- No step (`Red.refl`, `Rel.lean:106`). -/
61 | refl {Γ : List Expr} {e : Expr} : Red env Γ e e
62 /-- Chaining (`Red.trans`, `Rel.lean:111`). A reduction is a
63 denotation identity, so chaining costs nothing semantically. -/
64 | trans {Γ : List Expr} {e₁ e₂ e₃ : Expr} :
65 Red env Γ e₁ e₂ → Red env Γ e₂ e₃ → Red env Γ e₁ e₃
66 /-- Head reduction inside an application (`Red.appFn`,
67 `Rel.lean:116`): the head is reduced before a redex is looked for. -/
68 | appFn {Γ : List Expr} {f f' a : Expr} :
69 Red env Γ f f' → Red env Γ (app f a) (app f' a)
70 /-- **β**: at every binder, with **no premise** — no
71 inference of the argument, no comparison with the binder's domain.
72 This is Lean's own β (`whnf_core`). The model needs no premise
73 (`Sound.lean`, `Red.beta_sound`): a reduction claims nothing of a
74 redex that denotes the point, and a redex that does not has a lambda that
75 does not, so a graph, which determines its domain: the argument lies
76 in the lambda's domain because it lies in the domain of the slot the
77 semantic invariant gives the application.
78 -/
79 | beta {Γ : List Expr} {A b a : Expr} :
80 Red env Γ (app (lam A b) a) (b.inst a)
81 /-- **ζ**: a `let` reduces to its body with the value
82 substituted, with no certificate, as β (con-leche's `whnfCore` on
83 `.letE`, S0's `inst1M b v`; official `whnf_core`). Unlike β it
84 needs no case split on the point: the `let` and its reduct denote
85 alike at every environment (`interp_zeta`). -/
86 | zeta {Γ : List Expr} {T v b : Expr} :
87 Red env Γ (letE T v b) (b.inst v)
88 /-- **δ** (`Red.delta`, `Rel.lean:140`): a definition unfolds to its
89 value, at the use's level instantiation. (With `appFn`, an applied
90 definition unfolds at its head.) -/
91 | delta {Γ : List Expr} {c : Name} {ls : List Level} {ci : ConstInfo} {v : Expr} :
92 env.find? c = some ci → ci.value? = some v →
93 ls.length = ci.lparams.length →
94 Red env Γ (const c ls) (v.instL ci.lparams ls)
95 /-- **ι** (`Red.iota`, `Rel.lean:200`): a stored recursor applied to
… 703 more lines, as in con-lecheConLeche/Rules/Rel.lean:L97-L101 97mutual
98
99/-- **Reduction**: the head steps of `whnfCore` and the `whnf` loop,
100chained. -/
101inductive Red (env : Env) : Nat → Expr → Expr → Prop where
.
How to read them. The three relations describe what the checker does. means that on the terms and the checker’s equality test answers yes. means that, asked for the type of , the checker returns . We write rather than the customary colon so that it is not read as a typing judgement, which this paper does not have. Every relation is stated in the accepting direction only; nothing says what the checker rejects. A rule’s premises are exactly what the checker did at that site: the recursive calls it made, and the tests whose outcome fixes the shape of the conclusion.
A convention: a chain such as abbreviates consecutive premises in the same context, one per arrow, the right end of each arrow being the subject of the next; here and . Chains of any length are read the same way.
2.3.1. Reduction
Reduction chains the head steps the checker makes when it normalises a term. It is reflexive and transitivewhitepaper/Fragment/Rules.lean:L58-L6958/-- **Reduction**: the head steps of the checker's `whnf`, chained. -/
59inductive Red (env : Env) : List Expr → Expr → Expr → Prop where
60 /-- No step (`Red.refl`, `Rel.lean:106`). -/
61 | refl {Γ : List Expr} {e : Expr} : Red env Γ e e
62 /-- Chaining (`Red.trans`, `Rel.lean:111`). A reduction is a
63 denotation identity, so chaining costs nothing semantically. -/
64 | trans {Γ : List Expr} {e₁ e₂ e₃ : Expr} :
65 Red env Γ e₁ e₂ → Red env Γ e₂ e₃ → Red env Γ e₁ e₃
66 /-- Head reduction inside an application (`Red.appFn`,
67 `Rel.lean:116`): the head is reduced before a redex is looked for. -/
68 | appFn {Γ : List Expr} {f f' a : Expr} :
69 Red env Γ f f' → Red env Γ (app f a) (app f' a)
, and it reaches inside the headConLeche/Rules/Rel.lean:L101-L117101inductive Red (env : Env) : Nat → Expr → Expr → Prop where
102 /-- Every `pure e` exit: the value clauses of `whnfCoreBody`
103 (`Kernel/Core.lean`), the stuck fallbacks (`:1003`, `:1033-1035`),
104 an uncertified redex (`:1001`), `iotaRec = none` (`:1003`), the
105 loop's fixpoint (`whnfStep`'s `pure e₁`, `:1088`). -/
106 | refl {d : Nat} {e : Expr} : Red env d e e
107 /-- The recursive `whnfCore` continuations after β/ι/proj
108 (`Core.lean:990`, `:997`, `:1002`, `:1033`), the `whnf` loop
109 (`whnfLoop`, `:1091-1095`), and the major's preparation chain
110 (`prepareMajor`, `:785-807`). -/
111 | trans {d : Nat} {e₁ e₂ e₃ : Expr} :
112 Red env d e₁ e₂ → Red env d e₂ e₃ → Red env d e₁ e₃
113 /-- Head normalisation inside an application (`whnfCoreBody`'s
114 `.app` clause, `Kernel/Core.lean`: `whnfCore f` before the redex
115 tests). -/
116 | appFn {d : Nat} {f f' a : Expr} :
117 Red env d f f' → Red env d (.app f a) (.app f' a)
of an application.
whitepaper/Fragment/Rules.lean:L70-L8070 /-- **β**: at every binder, with **no premise** — no
71 inference of the argument, no comparison with the binder's domain.
72 This is Lean's own β (`whnf_core`). The model needs no premise
73 (`Sound.lean`, `Red.beta_sound`): a reduction claims nothing of a
74 redex that denotes the point, and a redex that does not has a lambda that
75 does not, so a graph, which determines its domain: the argument lies
76 in the lambda's domain because it lies in the domain of the slot the
77 semantic invariant gives the application.
78 -/
79 | beta {Γ : List Expr} {A b a : Expr} :
80 Red env Γ (app (lam A b) a) (b.inst a)
fires at every redex, and whitepaper/Fragment/Rules.lean:L81-L8781 /-- **ζ**: a `let` reduces to its body with the value
82 substituted, with no certificate, as β (con-leche's `whnfCore` on
83 `.letE`, S0's `inst1M b v`; official `whnf_core`). Unlike β it
84 needs no case split on the point: the `let` and its reduct denote
85 alike at every environment (`interp_zeta`). -/
86 | zeta {Γ : List Expr} {T v b : Expr} :
87 Red env Γ (letE T v b) (b.inst v)
substitutes a let-binding’s value into its body; neither has a premise.
As in the official kernel, has no premise, although its soundness needs the argument to lie in the domain of the lambda; Section 2.6 shows where that comes from. is simpler: a let-expression always denotes what its body with the value substituted denotes.
2.3.2. Definitional equality
The checker’s equality testwhitepaper/Fragment/Rules.lean:L547-L566547inductive DefEq (env : Env) : List Expr → Expr → Expr → Prop where
548 /-- The syntactic fast path (`DefEq.refl`, `Rel.lean:362`). -/
549 | refl {Γ : List Expr} {a : Expr} : DefEq env Γ a a
550 /-- Symmetry (`DefEq.symm`, `Rel.lean:365`). Not a checker move: the
551 constructor from which every right-hand variant of a one-sided rule
552 is derived. -/
553 | symm {Γ : List Expr} {a b : Expr} : DefEq env Γ a b → DefEq env Γ b a
554 /-- **Reduce the left side, then continue** (`DefEq.redL`,
555 `Rel.lean:370`): the recursive structure of `isDefEq` — head
556 normalisation of either side (with `symm`) and every lazy-δ
557 continuation. -/
558 | redL {Γ : List Expr} {a a' b : Expr} :
559 Red env Γ a a' → DefEq env Γ a' b → DefEq env Γ a b
560 /-- Two sorts, by the level oracle (`DefEq.sort`, `Rel.lean:373`). -/
561 | sort {Γ : List Expr} {u v : Level} :
562 LevelOracle.eq u v = true → DefEq env Γ (sort u) (sort v)
563 /-- Two constants of the same name at oracle-equal levels
564 (`DefEq.const`, `Rel.lean:384`). -/
565 | const {Γ : List Expr} {c : Name} {ls ls' : List Level} :
566 Level.eqList ls ls' = true → DefEq env Γ (const c ls) (const c ls')
is reflexive and symmetric. The checker never takes a symmetry step; the rule is there so that each one-sided rule below (red-l, ) need be written once, and its mirror image follows. The test interleaves with reduction in one way: reduce the left side, then continue. Sorts are compared through the level oracleConLeche/Rules/Rel.lean:L372-L374372 /-- Two sorts (`Core.lean:1581`). -/
373 | sort {d : Nat} {u v : Level} :
374 Level.isEquiv u v = some true → DefEq env d (.sort u) (.sort v)
.
The congruences descend into the two binders and into applications. Domains are compared first, then the bodies, under the right-hand domain. For whitepaper/Fragment/Rules.lean:L567-L575567 /-- `∀`-congruence (`DefEq.forallE`, `Rel.lean:399`): domains, then
568 bodies under the RIGHT domain, and nothing else. A
569 `∀`'s value is a function of its domain's and its fibres' values
570 (`SetLib.piV`: the propositional function space exactly when every fibre is
571 a truth value), and both are types, never the point, so the
572 premises' claims apply everywhere (`Sound.lean`, `DefEq.pi_sound`). -/
573 | pi {Γ : List Expr} {A₁ B₁ A₂ B₂ : Expr} :
574 DefEq env Γ A₁ A₂ → DefEq env (A₂ :: Γ) B₁ B₂ →
575 DefEq env Γ (pi A₁ B₁) (pi A₂ B₂)
that is all.
Two lambdas are compared as two lambda telescopes, by an auxiliary relation . The checker descends level by levelwhitepaper/Fragment/Rules.lean:L701-L714701inductive DefEqLams (env : Env) : List Expr → Expr → Expr → Prop where
702 /-- Syntactically equal sides (the checker's `a == b` fast path). -/
703 | refl {Γ : List Expr} {a : Expr} : DefEqLams env Γ a a
704 /-- The innermost level: `DefEq.lam`'s premises — domains, bodies,
705 and the bodies' inferred types. -/
706 | inner {Γ : List Expr} {A₁ b₁ A₂ b₂ t₁ t₂ : Expr} :
707 DefEq env Γ A₁ A₂ → DefEq env (A₂ :: Γ) b₁ b₂ →
708 Infer env (A₂ :: Γ) b₁ t₁ → Infer env (A₂ :: Γ) b₂ t₂ → DefEq env (A₂ :: Γ) t₁ t₂ →
709 DefEqLams env Γ (lam A₁ b₁) (lam A₂ b₂)
710 /-- An outer level: the domains, and the bodies by this relation —
711 no inference, no type comparison. -/
712 | outer {Γ : List Expr} {A₁ b₁ A₂ b₂ : Expr} :
713 DefEq env Γ A₁ A₂ → DefEqLams env (A₂ :: Γ) b₁ b₂ →
714 DefEqLams env Γ (lam A₁ b₁) (lam A₂ b₂)
while both bodies are lambdas, comparing the domains at each level (lam-outer). At the innermost level, where the descent stops, it also infers the types of the two bodieswhitepaper/Fragment/Rules.lean:L704-L709704 /-- The innermost level: `DefEq.lam`'s premises — domains, bodies,
705 and the bodies' inferred types. -/
706 | inner {Γ : List Expr} {A₁ b₁ A₂ b₂ t₁ t₂ : Expr} :
707 DefEq env Γ A₁ A₂ → DefEq env (A₂ :: Γ) b₁ b₂ →
708 Infer env (A₂ :: Γ) b₁ t₁ → Infer env (A₂ :: Γ) b₂ t₂ → DefEq env (A₂ :: Γ) t₁ t₂ →
709 DefEqLams env Γ (lam A₁ b₁) (lam A₂ b₂)
under the binder and compares them (lam). That comparison is a check the official kernel does not make. Such checks are typeset in one colour, like this, throughout the paper; a reader who ignores the coloured premises sees the official kernel’s rule. Section 2.8 explains why they are there, and why the outer levels need none.
(Two syntactically equal terms are also related by .) A lambda against a term that is not a lambda is tried by whitepaper/Fragment/Rules.lean:L640-L653640 /-- **η** (`DefEq.eta`, `Rel.lean:430`): a lambda against a term `b`
641 whose inferred type reduces to a `∀` with a definitionally equal
642 domain, when the lambda's body is `b` applied to the variable — and,
643 **typed** as lambda congruence is, the lambda's body's inferred
644 type is definitionally equal to the `∀`'s body, so that the lambda's body
645 and `b` applied lie in one type. Sound because a member of a
646 function space is the abstraction of its applications
647 (`SetLib.lamV_eta`). -/
648 | eta {Γ : List Expr} {A₁ b₁ b tb A₂ B t₁ : Expr} :
649 Infer env Γ b tb → Red env Γ tb (pi A₂ B) →
650 DefEq env Γ A₂ A₁ →
651 DefEq env (A₁ :: Γ) b₁ (app (b.liftN 1) (bvar 0)) →
652 Infer env (A₁ :: Γ) b₁ t₁ → DefEq env (A₁ :: Γ) t₁ B →
653 DefEq env Γ (lam A₁ b₁) b
. The type of is inferred and reduced to a , its domain is compared with the lambda’s, and the body is compared with applied to the bound variable. As at the innermost level of lambda congruence, the type of the lambda’s body is inferred and compared with the ‘s body. Two terms are equal by proof irrelevancewhitepaper/Fragment/Rules.lean:L654-L663654 /-- **Proof irrelevance** (`DefEq.proofIrrel`, `Rel.lean:452`): both
655 sides' inferred types have sort `Prop`. **The two types are never
656 compared**: the model justifies that, because every proof denotes the
657 one point. -/
658 | proofIrrel {Γ : List Expr} {a ta tta b tb ttb : Expr} {u v : Level} :
659 Infer env Γ a ta → Infer env Γ ta tta → Red env Γ tta (sort u) →
660 LevelOracle.eq u .zero = true →
661 Infer env Γ b tb → Infer env Γ tb ttb → Red env Γ ttb (sort v) →
662 LevelOracle.eq v .zero = true →
663 DefEq env Γ a b
when both are proofs: each one’s type has type with oracle-equal to .
Proof irrelevance. The rule compares the sorts of the two types, and never the two types. The official kernel also compares with , so that and are proofs of the same proposition. This checker does not, and so accepts strictly more than Lean at this one site. The model justifies it: every proof denotes the point, so two proofs of two propositions denote the same set.
The list has no transitivity rulewhitepaper/Fragment/Rules.lean:L529-L546529/-- **Definitional equality**: the verdict `true` of the checker's
530`isDefEq`.
531
532**There is no `trans` rule, deliberately** (`Rel.lean:334-357`).
533The relation is the checker's verdict on two terms that are
534well-formed *together*, a fact no rule states because well-formedness
535is semantic (`WellDenoted`) and may not enter this inductive. Every
536rule respects one discipline instead: **the subject of each `DefEq`
537premise is a subterm of the conclusion or is produced by a `Red` or
538`Infer` premise** (a reduct, an inferred type), so the soundness proof
539always has the premise's terms — and their invariant — in hand. A
540`trans` rule is the unique rule that would break it: its middle term
541comes from nowhere, so nothing supplies its invariant, and the
542induction has nothing to apply the hypothesis to. Nor does the
543checker chain through an arbitrary middle term: every comparison it
544makes is one of the rules below, and its one way to chain is
545"reduce, then continue": `redL`.
546-/
, and none can be addedConLeche/Rules/Rel.lean:L334-L357334/-- **Definitional equality**: the verdict `true` of `isDefEq`.
335
336**There is no `trans` rule, deliberately, and none can be added**
337(task #309, the experiment recorded in DESIGN.md). The relation is
338not an equivalence on terms: it is the checker's verdict, and the
339checker only ever compares terms that are well-formed *together* — a
340fact the rules never state, because well-formedness (scoping, the
341readability of the terms, the grading of the reading) is semantic
342and may not enter this inductive. Every rule respects one syntactic
343discipline instead: **the subject of each `DefEq` premise is either a
344subterm of the conclusion or is produced by an existence-form `Red` /
345`Infer` premise** (a reduct, an inferred type), so the soundness proof
346(`ConLeche/Model/Rules/Sound.lean`) always has the premise's terms in
347hand. `trans` is the unique rule that would break it: its middle
348term comes from nowhere, and with it two rules that are each sound
349alone meet — `fvar` compares free variables by index only (their
350types are not compared, as the checker does not), while
351`proofFast` *reads* a head's stored data to decide "definitely a
352proof" — and derive `DefEq env d (.fvar i ty) (.fvar i' ty')` for
353every `i`, `i'`, which no model satisfies. The recursive-structure rules
354(`redL`, `natSucc`, `eta`, `structUnit`) are therefore not a
355proof-engineering convenience but what keeps the relation sound; the
356one chaining that is sound, "reduce, then continue", is `redL`, and
357its right-hand and δ variants are derived in `Derived.lean`. -/
; Section 2.7 explains why. Instead of chaining equalities, the checker chains reductions: it reduces a side, then continues (red-l).
/-- Two free variables of the same index (`Kernel/Core.lean`). The376
carried types are NOT compared, as the checker does not compare them:377
at a well-formed call `CtxOk` pins every opened variable's type, so378
the reading ignores them. This is one half of why the relation has379
no `trans` (see the inductive's docstring). -/380
| fvar {d i : Nat} {ty₁ ty₂ : Expr} :381
DefEq env d (.fvar i ty₁) (.fvar i ty₂), not by the types they carry, and a fast pathConLeche/Rules/Rel.lean:L439-L444439
/-- **The `isProofFast` yes-arm** (`propIrrel`, `Kernel/Core.lean`):440
both heads' stored data say "a proposition at every valuation"441
(`prf_of_isProofFast`). -/442
| proofFast {d : Nat} {a b : Expr} :443
isProofFast env.find? a = true → isProofFast env.find? b = true →444
DefEq env d a bequates two terms whose heads’ declared types say they are proofs. Chained, the two would equate any two variables: give each the type of a proposition, then compare the two proofs.
2.3.3. Inference
A variable’s type is read off the contextwhitepaper/Fragment/Rules.lean:L740-L748740inductive Infer (env : Env) : List Expr → Expr → Expr → Prop where
741 /-- A bound variable's type is its context entry, lifted over the
742 binders in between (`Infer.fvar`, `Rel.lean:555`; the real checker
743 reads the type off the free variable). -/
744 | bvar {Γ : List Expr} {i : Nat} {A : Expr} :
745 Γ[i]? = some A → Infer env Γ (bvar i) (A.liftN (i + 1))
746 /-- `Sort u : Sort (u + 1)` (`Infer.sort`, `Rel.lean:551`). -/
747 | sort {Γ : List Expr} {u : Level} :
748 Infer env Γ (sort u) (sort (.succ u))
, and a sort has the next sortConLeche/Rules/Rel.lean:L549-L565549inductive Infer (env : Env) : Grade → Nat → Expr → Expr → Prop where
550 /-- `Core.lean:1114` / `:1294`. -/
551 | sort {g : Grade} {d : Nat} {u : Level} :
552 Infer env g d (.sort u) (.sort (.succ u))
553 /-- `Kernel/Core.lean` / `:1295-1297`: an opened variable's stored
554 type, at the leaf scope check. -/
555 | fvar {g : Grade} {d idx : Nat} {ty : Expr} :
556 idx < d → Infer env g d (.fvar idx ty) ty
557 /-- `Kernel/Core.lean` / `:1298-1309`: a stored constant that is
558 not a projection table, at the right level arity. -/
559 | const {g : Grade} {d : Nat} {n : Name} {us : List Level}
560 {ci : ConstantInfo} :
561 env.find? n = some ci → ci.isTowerEntry = false →
562 us.length = ci.toConstantVal.levelParams.length →
563 Infer env g d (.const n us)
564 (ci.toConstantVal.type.instantiateLevelParams
565 ci.toConstantVal.levelParams us)
. The rules of this subsection are the checker’s full inference. The checker also has a cheaper mode, infer_only, which skips some of their checks; Section 2.9 presents it.
The two binder ruleswhitepaper/Fragment/Rules.lean:L754-L771754 /-- **`∀`-formation** (`Infer.forallE`, `Rel.lean:576`): the domain's
755 type reduces to a sort `u`, the body's type (under the domain)
756 reduces to a sort `v`, the result is `Sort (imax u v)`. Nothing is
757 checked beyond Lean's own rule. -/
758 | pi {Γ : List Expr} {A B s t : Expr} {u v : Level} :
759 Infer env Γ A s → Red env Γ s (sort u) →
760 Infer env (A :: Γ) B t → Red env (A :: Γ) t (sort v) →
761 Infer env Γ (pi A B) (sort (.imax u v))
762 /-- **Lambda** (`Infer.lam`, `Rel.lean:589`): the domain's type reduces to
763 a sort, the body is inferred under the domain, and the result is the
764 `∀` of the body's type — Lean's own rule. The body's type's sort is
765 not computed: an inferred type is a type (`InferSem`), which is all
766 the `∀`'s invariant asks of it, and the body's value is read by the
767 interpretation, not by its sort. -/
768 | lam {Γ : List Expr} {A b s bt : Expr} {u : Level} :
769 Infer env Γ A s → Red env Γ s (sort u) →
770 Infer env (A :: Γ) b bt →
771 Infer env Γ (lam A b) (pi A bt)
are Lean’s own. For a , the domain’s type must reduce to a sort and the body’s type, under the domain, to a sort ; the result is . For a lambda, the domain’s type must reduce to a sort, and the result is the of the body’s inferred type; the sort of that type is not computed.
An application infers the head’s typewhitepaper/Fragment/Rules.lean:L772-L781772 /-- **Application** (`Infer.app`, `Rel.lean:617`): the head's type
773 reduces to a `∀`, and the argument's inferred type is definitionally
774 equal to its domain. This is where a syntactic proof would need
775 Π-injectivity and the semantic one does not: the `∀` the head's type
776 reduces to *is* a function space, and membership in it is what the
777 model needs (`Sound.lean`, `infer_sound`'s `app` case). -/
778 | app {Γ : List Expr} {f a tf A B ta : Expr} :
779 Infer env Γ f tf → Red env Γ tf (pi A B) →
780 Infer env Γ a ta → DefEq env Γ ta A →
781 Infer env Γ (app f a) (B.inst a)
, reduces it to a , infers the argument’s type and compares it with the domainConLeche/Rules/Rel.lean:L617-L621617 | app {g : Grade} {d : Nat} {f a tf ty body ta : Expr} :
618 Infer env g d f tf → Red env d tf (.forallE ty body) →
619 (g = .full → Infer env .full d a ta) →
620 (g = .full → DefEq env d ta ty) →
621 Infer env g d (.app f a) (body.instantiate1 a)
. A let-expressionwhitepaper/Fragment/Rules.lean:L782-L796782 /-- **`let`, by substitution** (official `infer_let`
783 with the body instantiated rather than opened at a let-variable, as
784 the checker does): the type's type reduces to a
785 sort, the value's inferred type is definitionally equal to the type,
786 and the result is the inferred type of the body with the value
787 substituted. The two checks on the value are Lean's (they reject
788 `let x : T := v; b` with `v : T` false even where the body ignores
789 `x`); the soundness proof does not use them (`Sound.lean`,
790 `Infer.letE_sound`), because `interp` reads no type and the body is
791 inferred at the value itself. -/
792 | letE {Γ : List Expr} {T v b s tv B : Expr} {u : Level} :
793 Infer env Γ T s → Red env Γ s (sort u) →
794 Infer env Γ v tv → DefEq env Γ tv T →
795 Infer env Γ (b.inst v) B →
796 Infer env Γ (letE T v b) B
checks that its type has a sort and that its value’s type agrees with it, as Lean does, and then infers the type of the body with the value substituted.
Reducing to a . The application rule reduces the inferred type of until a is syntactically there. It does not take a type of that is a up to definitional equality and argue that its domain is the right one. That argument is where a syntactic soundness proof needs injectivity of , the property that forces . Our proof never needs it, and the model does not validate it: and denote the same truth value whatever and are. Our proof argues instead that the head denotes a member of what the denotes, because reduction preserves denotations; that the argument denotes a member of the domain, by the comparison premise; and that applying a member of a function space to a member of its domain lands in the fibre. The second half of this section carries this out.
The let-expression. The two checks on the value are what Lean runs, and they decide verdicts: they reject a let-expression whose value does not have the stated type even where the body ignores it. The soundness proof does not use them (Section 2.7): the interpretation reads no type, and the body is inferred with the value itself in place of the variable.
| .letE _ v b =>980
-- Task #342 S0: ζ981
r.whnfCore cheap depth (b.instantiate1 v)and when it infersConLeche/Kernel/Core.lean:L1179-L11841179
| .letE ty v b => do1180
-- Task #342 S0: official `infer_let`1181
let _ ← ensureSort r env depth (← r.infer depth ty)1182
unless ← r.defeq depth (← r.infer depth v) ty do1183
throw (.invalid "let value type mismatch")1184
r.infer depth (b.instantiate1 v); it keeps no variables bound to values. The official kernel opens the body at such a variable instead, which avoids copying the value when the body uses it many times; for con-leche that case is a known performance gap, not a soundness question.
2.4. The set theory we assume
The other side of the argument is a set theory. We do not construct one. The reader is given an axiomatised theory, a type of sets with the operations and laws listed below, and is promised that nothing beyond these laws is used. The theory is stated in Lean’s own logic, so it is higher-order: separation takes any predicate of that logic, and graphs and function spaces are formed from any function of it. It is deliberately not minimal: graphs, dependent function spaces and the universe chain, which a minimal axiom system would construct, are assumed outright with their laws, because how they are built does not matter to the argument. The promise is literal: in the Lean fragment the structure is a class, SetLibwhitepaper/Fragment/Lib.lean:L56-L12656/-- **The set theory we assume.** A type of sets `V` with the
57operators and laws the fragment's proof uses. The theory is stated in
58Lean's logic, so separation is available for every predicate of that
59logic, and the function-space laws quantify over functions on the
60sets. -/
61class SetLib (V : Type u) where
62 /-- Membership. -/
63 Mem : V → V → Prop
64 /-- Extensionality. -/
65 ext : ∀ {x y : V}, (∀ z, Mem z x ↔ Mem z y) → x = y
66 /-- Separation: the members of `A` that satisfy `P`. -/
67 sep : V → (V → Prop) → V
68 /-- The members of a separation. -/
69 mem_sep : ∀ {A : V} {P : V → Prop} {z : V}, Mem z (sep A P) ↔ Mem z A ∧ P z
70 /-- A distinguished set, subject to the point's three laws below. It
71 is the one proof of every true proposition. -/
72 pt : V
73 /-- The one-element set `{pt}`. -/
74 one : V
75 /-- The members of `{pt}`. -/
76 mem_one : ∀ {z : V}, Mem z one ↔ z = pt
77 /-- The universe chain: `univ n` sizes `Sort n`, which denotes the
78 types among its members, `typeU n`. -/
79 univ : Nat → V
80 /-- `univ 0` is the set of subsets of `{pt}` — the truth values. -/
81 mem_univ_zero : ∀ {T : V}, Mem T (univ 0) ↔ ∀ z, Mem z T → z = pt
82 /-- Each universe is a member of the next. -/
83 univ_mem_succ : ∀ n : Nat, Mem (univ n) (univ (n + 1))
84 /-- Cumulativity. -/
85 univ_mono : ∀ {m n : Nat} {x : V}, m ≤ n → Mem x (univ m) → Mem x (univ n)
86 /-- The graph of a function on a domain. -/
87 graph : (V → V) → V → V
88 /-- The dependent function space: the graphs over `A` with values
89 in the fibres `B`. -/
90 piSet : V → (V → V) → V
91 /-- Application. On the point it is the point (a proof applied to
92 anything is a proof); on a graph it reads the graph. -/
93 app : V → V → V
94 /-- A proof applied is the proof. -/
95 app_pt : ∀ a : V, app pt a = pt
… 31 more lines, and every theorem of the fragment is proved against that class.
/-- A model of set theory of exactly the strength the checker needs:96
membership, the six ZF⁻ axioms (extensionality, pairing, union, power97
set, regularity, Lean-level replacement), and an ω-chain of98
Grothendieck universes. Infinity is derivable; choice is inherited99
from the meta-logic (`Classical.choice`); see the module docstring. -/100
class SetTheory (V : Type u) where, ZF without infinity plus a chain of Grothendieck universes (sets closed under all the set-forming operations), from which it derives the operators below.
The laws come in four groups.
Sets, membership, extensionality, separation. A type of sets with a membership relation ; extensionalitywhitepaper/Fragment/Lib.lean:L64-L6564 /-- Extensionality. -/
65 ext : ∀ {x y : V}, (∀ z, Mem z x ↔ Mem z y) → x = y
: two sets with the same members are equal; and separationwhitepaper/Fragment/Lib.lean:L66-L6966 /-- Separation: the members of `A` that satisfy `P`. -/
67 sep : V → (V → Prop) → V
68 /-- The members of a separation. -/
69 mem_sep : ∀ {A : V} {P : V → Prop} {z : V}, Mem z (sep A P) ↔ Mem z A ∧ P z
: for every set and every property of sets, a set whose members are the members of that satisfy . A property here is any predicate on sets expressible in Lean’s type theory, the logic the theory is stated in, not only a first-order formula.
The point and the truth values. A distinguished set , the point, and its singleton whitepaper/Fragment/Lib.lean:L73-L7673 /-- The one-element set `{pt}`. -/
74 one : V
75 /-- The members of `{pt}`. -/
76 mem_one : ∀ {z : V}, Mem z one ↔ z = pt
. The point will be the one proof of every true proposition. For a proposition of the ambient logic we write for the separation and call it the truth value of whitepaper/Fragment/Lib.lean:L142-L144142/-- The truth value of a proposition: `{pt}` if it holds, `∅` if
143not. -/
144def truthVal (P : Prop) : V := sep one (fun _ => P)
. Its only possible member is the point, which it has exactly when holds, so when is true and when it is false. This is an abbreviation, not an axiom. The point itself obeys three lawswhitepaper/Fragment/Lib.lean:L120-L126120 /-- **The point's laws** (1): the point is not a truth value — it has
121 a member other than itself. -/
122 pt_not_mem_univ_zero : ¬ Mem pt (univ 0)
123 /-- (2): the point has at most one member. -/
124 pt_subsingleton : ∀ {x y : V}, Mem x pt → Mem y pt → x = y
125 /-- (3): no graph is a member of the point. -/
126 graph_not_mem_pt : ∀ {F : V → V} {A : V}, ¬ Mem (graph F A) pt
: it is not a truth value (it has a member other than itself), it has at most one member, and no graph (below) is a member of it.
/-- The proof point `pt = {ptTag}`. -/69
noncomputable def pt : V := sing ptTagof the set ConLeche/SetTheory/Derive/Pt.lean:L44-L4644
/-- The proof-point tag `{∅, {{∅}}}` (see the module docstring for the45
selection principle). -/46
noncomputable def ptTag : V := upair empty (sing (sing empty)), chosen so that no encoding of a value of a data type produces it; the three laws are theorems there.
The universes. A chain of setswhitepaper/Fragment/Lib.lean:L77-L8577 /-- The universe chain: `univ n` sizes `Sort n`, which denotes the
78 types among its members, `typeU n`. -/
79 univ : Nat → V
80 /-- `univ 0` is the set of subsets of `{pt}` — the truth values. -/
81 mem_univ_zero : ∀ {T : V}, Mem T (univ 0) ↔ ∀ z, Mem z T → z = pt
82 /-- Each universe is a member of the next. -/
83 univ_mem_succ : ∀ n : Nat, Mem (univ n) (univ (n + 1))
84 /-- Cumulativity. -/
85 univ_mono : ∀ {m n : Nat} {x : V}, m ≤ n → Mem x (univ m) → Mem x (univ n)
, with the set of truth values, the subsets of . Each is a member of . The chain is cumulative (a member of is a member of every later ), and every universe is closed under separationwhitepaper/Fragment/Lib.lean:L118-L119118 /-- Closure of the universes under separation. -/
119 sep_mem_univ : ∀ {n : Nat} {A : V} {P : V → Prop}, Mem A (univ n) → Mem (sep A P) (univ n)
.
Graphs, function spaces, application. For a function from sets to sets (a function of the meta-theory, not a set) and a set , a set ; for a set and a family of sets indexed by sets, again a function of the meta-theory, a set , the dependent function space; and for two sets and a set , the applicationwhitepaper/Fragment/Lib.lean:L86-L9386 /-- The graph of a function on a domain. -/
87 graph : (V → V) → V → V
88 /-- The dependent function space: the graphs over `A` with values
89 in the fibres `B`. -/
90 piSet : V → (V → V) → V
91 /-- Application. On the point it is the point (a proof applied to
92 anything is a proof); on a graph it reads the graph. -/
93 app : V → V → V
. Their laws:
- : a proof applied to anything is the proofwhitepaper/Fragment/Lib.lean:L94-L9594
/-- A proof applied is the proof. -/
95app_pt : ∀ a : V, app pt a = pt
. - βwhitepaper/Fragment/Lib.lean:L96-L9796
/-- β: a graph applied on its domain computes. -/
97app_graph : ∀ {F : V → V} {A a : V}, Mem a A → app (graph F A) a = F a
: if then . - : a graph is never the pointwhitepaper/Fragment/Lib.lean:L98-L9998
/-- A graph is never the point. -/
99graph_ne_pt : ∀ {F : V → V} {A : V}, graph F A ≠ pt
. - Congruencewhitepaper/Fragment/Lib.lean:L100-L103100
/-- A graph depends only on the function's values on the domain. -/
101graph_congr : ∀ {F F' : V → V} {A : V}, (∀ x, Mem x A → F x = F' x) → graph F A = graph F' A
102/-- A function space depends only on the fibres over the domain. -/
103piSet_congr : ∀ {A : V} {B B' : V → V}, (∀ x, Mem x A → B x = B' x) → piSet A B = piSet A B'
: depends only on the values of on , and only on the values of on . - Introductionwhitepaper/Fragment/Lib.lean:L104-L106104
/-- Introduction: fibre-wise members abstract into the space. -/
105graph_mem_piSet : ∀ {A : V} {F B : V → V},
106(∀ x, Mem x A → Mem (F x) (B x)) → Mem (graph F A) (piSet A B)
: if for every , then . - Eliminationwhitepaper/Fragment/Lib.lean:L107-L108107
/-- Elimination. -/
108app_mem_piSet : ∀ {A f a : V} {B : V → V}, Mem f (piSet A B) → Mem a A → Mem (app f a) (B a)
: if and , then . - ηwhitepaper/Fragment/Lib.lean:L109-L110109
/-- η: a member of a function space is the graph of its applications. -/
110graph_app_eq : ∀ {A f : V} {B : V → V}, Mem f (piSet A B) → graph (fun x => app f x) A = f
: if , then . - Domain uniquenesswhitepaper/Fragment/Lib.lean:L111-L113111
/-- A graph determines its domain. -/
112piSet_dom_unique : ∀ {A A' f : V} {B B' : V → V},
113Mem f (piSet A B) → Mem f (piSet A' B') → A = A'
: if and , then . - Closurewhitepaper/Fragment/Lib.lean:L114-L117114
/-- Closure of the positive universes under dependent function
115spaces. -/
116piSet_mem_univ : ∀ {n : Nat} {A : V} {B : V → V}, n ≠ 0 →
117Mem A (univ n) → (∀ x, Mem x A → Mem (B x) (univ n)) → Mem (piSet A B) (univ n)
: for , if and for every , then .
This is all this section needs. When inductive types arrive, §4 extends the theory.
Two of these laws are design choices. The first is that application of the point is the point. In the model a proof of a proposition is the point, and a proof of is again the point, not a function. The law makes application one operation that does the right thing on both kinds of value, so the interpretation of need not know which kind it is applying. The second is that a graph is never the point: a function and a proof are never the same set. A value that is known to be in a function space, or merely known not to be the point, is therefore a graph, and by domain uniqueness a graph carries its own domain. This is what lets β fire with no premise at all.
One law is deliberately restricted: closure of a universe under function spaces is stated for and not for . A proposition has no function space in the model. It has a truth value, and a truth value is in whatever is. This is the model’s impredicativitywhitepaper/Fragment/Lib.lean:L275-L278275/-- **Impredicativity**: a function space landing in `Prop` is a truth value,
276whatever its domain. -/
277theorem piR_true_mem_univ_zero {A : V} {B : V → V} :
278 piR true A B ∈ˢ (univ 0 : V) := truthVal_mem_univ_zero _
.
2.5. The interpretation
Every term denotes a set. The interpretation is total, defined on every term, well-typed or not, and term-directed: it is a structural recursion on the term, consulting no derivation and no type. It takes two parameters:
- a valuation of the level parameters (Section 2.2);
- a variable environment , assigning a set to every variable in scope.
We write for the set the term denotes under ( stays implicit), and for extended with the value for the variable . The clauseswhitepaper/Fragment/Interp.lean:L138-L146138/-- **The interpretation**: total, term-directed, reading no type. -/
139noncomputable def interp (M : Name → List Nat → V) (φ : Name → Nat) : (Nat → V) → Expr → V
140 | ρ, .bvar i => ρ i
141 | _, .sort u => typeU (u.eval φ)
142 | _, .const c ls => M c (ls.map (Level.eval φ))
143 | ρ, .app f a => app (interp M φ ρ f) (interp M φ ρ a)
144 | ρ, .lam A b => lamV (interp M φ ρ A) fun x => interp M φ (cons x ρ) b
145 | ρ, .pi A B => piV (interp M φ ρ A) fun x => interp M φ (cons x ρ) B
146 | ρ, .letE _ v b => interp M φ (cons (interp M φ ρ v) ρ) b
:
with the value of the level at the valuation, and the sets a sort denotes, below. The two binder clauses choose between two readings by the valueswhitepaper/Fragment/Lib.lean:L469-L481469open Classical in
470/-- Is every fibre over the domain a truth value? -/
471noncomputable def propB (A : V) (B : V → V) : Bool := decide (∀ x, x ∈ˢ A → B x ∈ˢ univ 0)
472
473open Classical in
474/-- Is every body value over the domain the point? -/
475noncomputable def ptB (A : V) (F : V → V) : Bool := decide (∀ x, x ∈ˢ A → F x = pt)
476
477/-- **The function space, regime read off the fibres.** -/
478noncomputable def piV (A : V) (B : V → V) : V := piR (propB A B) A B
479
480/-- **The abstraction, regime read off the body values.** -/
481noncomputable def lamV (A : V) (F : V → V) : V := lamR (ptB A F) A F
:
Here is the propositional function spacewhitepaper/Fragment/Lib.lean:L185-L191185/-- **The propositional function space** `Π₀(A, B)`: the truth value "every
186fibre is `{pt}`". When the fibres are truth values it is their
187intersection `⋂ x ∈ A, B x` (the empty intersection being `{pt}`):
188`{pt}` exactly when every fibre is, `∅` otherwise — the function space of
189subsets of a singleton. Reducible, so proofs that unfold `piR true`
190keep working. -/
191abbrev piProp (A : V) (B : V → V) : V := truthVal (∀ x, x ∈ˢ A → B x = one)
, . When the fibres are truth values it is their intersection (the empty intersection being ), which is exactly when every fibre is and otherwise: the function space of subsets of a singleton. A let reads its body at the value’s value, and its type is not read, as no type is read anywhere.
So a binder has two readings. When the body is not a proposition, a lambda is a graph and a is a set of such graphs, a function space. When it is, a lambda is a proof, hence the point, and a is a proposition, true when every fibre (the set at a ) is . Which reading applies is decided by the values the body takes over the domain, and by nothing else. The interpretation does not know the sort of , and does not compute it.
The empty domain. Over a non-empty domain the values choose the reading the sort of the body would choose. Over an empty domain there are no values, and both clauses take their first case: a function space over an empty domain is whitepaper/Fragment/Lib.lean:L625-L631625/-- Over an empty domain the function space is `{pt}`. -/
626theorem piV_of_empty {A : V} {B : V → V} (h : ∀ x, ¬ x ∈ˢ A) : piV A B = one := by
627 unfold piV
628 rw [propB_eq_true.mpr fun x hx => absurd hx (h x), piR_true]
629 exact ext fun z => by
630 rw [mem_truthVal, mem_one]
631 exact ⟨fun h' => h'.1, fun h' => ⟨h', fun x hx => absurd hx (h x)⟩⟩
, and an abstraction over one is the pointwhitepaper/Fragment/Lib.lean:L633-L635633/-- Over an empty domain the abstraction is the point. -/
634theorem lamV_of_empty {A : V} {F : V → V} (h : ∀ x, ¬ x ∈ˢ A) : lamV A F = pt :=
635 lamV_eq_pt fun x hx => absurd hx (h x)
, whatever the sort of the body. So False → Nat, a type of sort Type, denotes , and its inhabitant fun h => h.elim denotes the point. A function space over an inhabited domain can do the same: Nat → False → Nat has as every fibre and is itself whitepaper/Fragment/Probe.lean:L52-L5452/-- `Nat → False → Nat`: every fibre is `{pt}`, so the function space is the
53truth value `{pt}`, although its sort is `Type`. -/
54theorem piV_const_one {A : V} : piV A (fun _ => one) = one := by
, and fun n h => h.elim is the point. No other reading over the empty domain does better. Whether the codomain is a proposition there is a fact about the syntax, not about any value, and reading an empty Π-type as a function space instead would make the proposition False → False denote a one-element set of graphs, which no truth value is.
What a sort denotes. A set is a typewhitepaper/Fragment/Lib.lean:L298-L302298/-- A **strict** set: not the point, and the point is not a member. -/
299def Strict (X : V) : Prop := X ≠ pt ∧ ¬ pt ∈ˢ X
300
301/-- A **type**: a truth value, or a strict set. -/
302def IsTy (X : V) : Prop := X ∈ˢ univ 0 ∨ Strict X
when it is a truth value or strict, that is, neither the point nor a set holding the point. The members of a type are all the point or none of itwhitepaper/Fragment/Lib.lean:L359-L364359/-- The members of a type are all the point or none of them. -/
360theorem IsTy.eq_of_ne_pt {T x y : V} (hT : IsTy T) (hx : x ∈ˢ T) (hy : y ∈ˢ T)
361 (h : x ≠ pt → y ≠ pt → x = y) : x = y := by
362 rcases hT with hT | hT
363 · rw [eq_pt_of_mem_univ_zero hT hx, eq_pt_of_mem_univ_zero hT hy]
364 · exact h (ne_pt_of_mem_strict hT hx) (ne_pt_of_mem_strict hT hy)
, and this is the one fact about types the soundness proof reads. A sort denotes the types of its universewhitepaper/Fragment/Lib.lean:L304-L306304/-- **What `Sort n` denotes**: the truth values at `0`, the members of
305`univ n` that are types above. -/
306def typeU (n : Nat) : V := if n = 0 then univ 0 else sep (univ n) IsTy
: , the truth values, and for the members of that are types. These are cumulative, and holds the truth values too. It must, since a function space over an empty domain is , which is no strict setwhitepaper/Fragment/ValueReading.lean:L58-L6358/-- A function space over an empty domain is `{pt}`, which is no strict set;
59so a positive sort, which holds `False → Nat`, holds a set that is not
60strict. -/
61theorem piV_empty_not_strict {A : V} {B : V → V} (h : ∀ x, ¬ x ∈ˢ A) :
62 ¬ Strict (piV A B) := by
63 rw [piV_of_empty h]; exact fun hs => hs.2 (mem_one.mpr rfl)
. The point is no type, so no sort holds it.
/-- **What `Sort n` means**: the universe of types `tyU n`. -/35
noncomputable def univ : Nat → V := tyUis the universe of typesConLeche/SetTheory/Derive/TypeU.lean:L214-L216214
open Classical in215
/-- **The universe of types at level `n`**: the truth values at `0`,216
above the members of the axiomatic level `tg n` that are types. -/, read off the same notions of a strict setConLeche/SetTheory/Derive/TypeU.lean:L57-L5857
/-- A **strict** set: not the point, and the point is not a member. -/58
def Strict (X : V) : Prop := X ≠ pt ∧ ¬ pt ∈ˢ Xand a typeConLeche/SetTheory/Derive/TypeU.lean:L60-L6160
/-- A **type**: a truth value, or a strict set. -/61
def IsTy (X : V) : Prop := X ∈ˢ univZero ∨ Strict X.
This is the whole of the interpretation. Nothing had to be well-typed, no typing judgement was consulted, and there is no partiality to discharge. The next subsection says what replaces typing. Three lemmas about the interpretation are needed later, each proved by induction on the term. Substituting a term for a variablewhitepaper/Fragment/Interp.lean:L242-L246242/-- **The β-substitution lemma**: substituting for the innermost
243variable is consing its value. -/
244theorem interp_inst0 (e a : Expr) (ρ : Nat → V) :
245 interp M φ ρ (e.inst a) = interp M φ (cons (interp M φ ρ a) ρ) e := by
246 rw [interp_inst, shiftE_zero_zero, instE_zero]
is extending the environment with the term’s value, ; hence a let denotes what its -reduct denoteswhitepaper/Fragment/Interp.lean:L248-L253248/-- **ζ is an identity**: a `let` denotes what its body with the value
249substituted denotes — at every environment, the point included (as β
250is at a lambda that is a graph; ζ has no lambda that could be the point). -/
251theorem interp_zeta (T v b : Expr) (ρ : Nat → V) :
252 interp M φ ρ (.letE T v b) = interp M φ ρ (b.inst v) := by
253 rw [interp_letE, interp_inst0]
, at every environment; and a term does not see a variable it does not mentionwhitepaper/Fragment/Hygiene.lean:L464-L467464/-- **Variables.** The interpretation of a term closed below `k` reads
465the environment below `k` only. -/
466theorem interp_closedAt {k : Nat} {e : Expr} (he : e.closedAt k = true) {ρ ρ' : Nat → V}
467 (h : ∀ i, i < k → ρ i = ρ' i) : interp M φ ρ e = interp M φ ρ' e := by
.
2.6. The semantic invariant
There is no typing judgement in the proof. In its place is a predicate on terms, the semantic invariant: a term is well-denotedwhitepaper/Fragment/WellDenoted.lean:L55-L7855/-- **The semantic invariant** (see the module docstring): a binder's
56domain is a type; a binder's codomain fibres are types; an application
57in the graph regime has a type as its slot's domain. -/
58def WellDenoted : (Nat → V) → Expr → Prop
59 | _, .bvar _ => True
60 | _, .sort _ => True
61 | _, .const _ _ => True
62 | ρ, .app f a =>
63 WellDenoted ρ f ∧ WellDenoted ρ a ∧
64 ∃ (p : Bool) (A : V) (B : V → V),
65 interp M φ ρ f ∈ˢ piR p A B ∧ interp M φ ρ a ∈ˢ A ∧
66 (p = true → ∀ x, x ∈ˢ A → B x ∈ˢ univ 0) ∧ (p = false → IsTy A)
67 | ρ, .lam A b =>
68 WellDenoted ρ A ∧ IsTy (interp M φ ρ A) ∧
69 (∀ x, x ∈ˢ interp M φ ρ A → WellDenoted (cons x ρ) b) ∧
70 ∃ B : V → V,
71 (∀ x, x ∈ˢ interp M φ ρ A → interp M φ (cons x ρ) b ∈ˢ B x) ∧
72 (∀ x, x ∈ˢ interp M φ ρ A → IsTy (B x))
73 | ρ, .pi A B =>
74 WellDenoted ρ A ∧ IsTy (interp M φ ρ A) ∧
75 (∀ x, x ∈ˢ interp M φ ρ A → WellDenoted (cons x ρ) B) ∧
76 (∀ x, x ∈ˢ interp M φ ρ A → IsTy (interp M φ (cons x ρ) B))
77 | ρ, .letE _ v b =>
78 WellDenoted ρ v ∧ WellDenoted (cons (interp M φ ρ v) ρ) b
when its applications and binders respect domains. It is stated under a valuation and an environment , like the interpretation, by recursion on the term: it holds of a term when it holds of the subterms and one condition on the term’s own shape is met. We write for “ is well-denoted under ” ( is fixed throughout). Variables and sorts are always well-denoted, and
In app, is either a function space whose domain is a type, or a propositional function space whose fibres are truth values. In lam, is some family of types bounding the body’s values, its codomain. In words: an application applies a function, or a proof of a propositional , to a member of its domain; a binder’s domain is a type; a lambda’s values lie in a bounded family of types, and a ‘s fibres are types. The invariant records which sets are types, and that is what makes the choice between the two readings agree with membership. An abstraction is a member of the function spacewhitepaper/Fragment/Lib.lean:L552-L558552/-- **Introduction**: body values in fibres that are types abstract into
553the function space. Where every fibre is a truth value every body value is
554the point, and so is the abstraction; where one fibre is not, it is
555strict, its body value is not the point, and the abstraction is the
556graph. -/
557theorem lamV_mem {A : V} {F B : V → V} (hF : ∀ x, x ∈ˢ A → F x ∈ˢ B x)
558 (hB : ∀ x, x ∈ˢ A → IsTy (B x)) : lamV A F ∈ˢ piV A B := by
of a family of types bounding its values: if every fibre is a truth value, every value is the point and so is the abstraction; if one fibre is not, it is strict, the body’s value there is not the point, and the abstraction is the graph.
The semantic invariant is transported by substitutionwhitepaper/Fragment/WellDenoted.lean:L196-L199196theorem WellDenoted_inst0 {e a : Expr} {ρ : Nat → V} (ha : WellDenoted M φ ρ a) :
197 WellDenoted M φ ρ (e.inst a) ↔ WellDenoted M φ (cons (interp M φ ρ a) ρ) e := by
198 have h := WellDenoted_inst M φ e a 0 ρ (by rwa [shiftE_zero_zero])
199 rwa [shiftE_zero_zero, instE_zero] at h
: is well-denoted under exactly when is well-denoted under , provided itself is. The proof is by induction on the term, using the interpretation’s substitution lemma; no typing derivation is involved.
A context , the list of the types of the variables in scope, is satisfiedwhitepaper/Fragment/WellDenoted.lean:L252-L259252/-- **Satisfaction of a context**: every entry is a well-denoted type
253under the environment beyond it, and the variable's value is a member
254of it. The semantic `Γ ⊢`. -/
255def Sat : List Expr → (Nat → V) → Prop
256 | [], _ => True
257 | A :: Γ, ρ =>
258 Sat Γ (fun i => ρ (i + 1)) ∧ WellDenoted M φ (fun i => ρ (i + 1)) A ∧
259 IsTy (interp M φ (fun i => ρ (i + 1)) A) ∧ ρ 0 ∈ˢ interp M φ (fun i => ρ (i + 1)) A
by when every entry is well-denoted under the environment beyond it, denotes a type there, and has the variable’s value as a member. This is the semantic reading of “ is a well-formed context”, and it is the only thing the claims below assume about the context.
The first non-routine step is β. Take a well-denoted redex . From the application rule we know that the lambda’s value lies in some whose domain contains ; from the lambda rule we know that the body is bounded over . For the β law we need , the argument in the lambda’s own domain, and the semantic invariant has given us , not .
/-- **β at every binder, with no premise** — `beta_of_ne_pt`. The108
redex is not the point, so neither is its lambda; a lambda that is not the point109
is the graph of its body; and the graph's domain is the slot's: the110
argument is in it. -/111
theorem beta_sound {Γ : List Expr} {A b a : Expr} :112
RedSem m φ Γ (.app (.lam A b) a) (b.inst a) := by113
intro ρ _ hw hn114
rw [WellDenoted_app] at hw115
obtain ⟨hl, ha, p, A', B', hslot, hmem, -, -⟩ := hw116
rw [WellDenoted_lam] at hl117
obtain ⟨-, -, hbody, B, hfib, -⟩ := hl118
have hlam := fn_ne_pt hn119
have hval : interp m.M φ ρ (.lam A b) =120
graph (fun x => interp m.M φ (cons x ρ) b) (interp m.M φ ρ A) := by121
rw [interp_lam] at hlam ⊢; exact lamV_eq_graph hlam122
have hp : p = false := by123
cases p124
· rfl125
· exact absurd (eq_pt_of_mem_piR_true hslot) hlam126
subst hp127
have hown : interp m.M φ ρ (.lam A b) ∈ˢ piR false (interp m.M φ ρ A) B := by128
rw [hval, piR_false]; exact graph_mem_piSet hfib129
have haA : interp m.M φ ρ a ∈ˢ interp m.M φ ρ A := by130
rw [piR_dom_unique hown hslot]; exact hmem131
refine ⟨(WellDenoted_inst0 m.M φ ha).mpr (hbody _ haA), ?_⟩132
rw [interp_app, interp_lam, app_lamV haA, interp_inst0].
For a redex that does denote the point there is no such lemma. Its lambda may be the point, which carries no domain, and its argument may lie anywhere. Such a redex is well-denoted, and its reduct need not be: whitepaper/Fragment/EtaIota.lean:L707-L724707open EtaWit in
708/-- **Eta on the reduct of the major is outside the claims**.
709The major `maj := (fun x : A => g (k x)) a` is well-denoted
710and the point (a lambda that is the point constrains nothing, so `a` may
711lie outside `A`), and it β-reduces to `maj' := g (k a)`, which is not
712well-denoted (`k` is a graph over `A`). The redex `rec minor maj` is
713well-denoted and not the point. Eta-expanding the major's reduct,
714`minor (p0 maj')`, breaks the reduction claim; eta-expanding the major
715as it stands, `minor (p0 maj)`, satisfies it. So the checker's eta
716path must not reduce the major first (lean4#15371 expands before the
717major's `whnf`). -/
718theorem etaIota_reduced_major_witness [LevelOracle] (φ : Name → Nat) (env : Env)
719 (Γ : List Expr) :
720 Red env Γ maj maj' ∧
721 (∀ ρ : Nat → V, WellDenoted (witM (V := V)) φ ρ redex2 ∧ interp witM φ ρ redex2 ≠ pt ∧
722 interp witM φ ρ maj = pt) ∧
723 ¬ RedSem (EnvModel.empty (witM (V := V))) φ [] redex2 etaRed2 ∧
724 RedSem (EnvModel.empty (witM (V := V))) φ [] redex2 eta2 := by
, with a lambda that is the point and an argument outside the domain of the graph , reduces to , which is not well-denoted. So the reduction claim below says nothing about a term that denotes the point, and β needs no premise. Section 2.7 shows that the other rules need no more than such a claim.
Why no sort holds the point. The claims below rely on one fact: an inferred type is a type, so it is never the point. It also keeps β sound on stored values. A stored constant whose value is the ill-typed redex reduces by δ and β to . The redex denotes the point (its lambda is an abstraction over an empty domain), and does not. Such an environment has no modelwhitepaper/Fragment/UniformBeta.lean:L76-L8076/-- **The witness has no model**: the environment
77`c : Sort 1 := (λ (h : ∀ P : Prop, P). h) Prop` — its value denotes the
78point (an abstraction over an empty domain), and `Sort 1`'s members are
79types. -/
80theorem badEnv_no_model (m : EnvModel V badEnv) : False := by
: the constant’s set would have to be the point and a member of , which holds no point.
2.7. The three claims and their proof
Theorem 2 (Soundness of the three relations). Fix a valuation and let satisfy . Then:
- reduction preserves the denotation and the semantic invariant of a term that is not the point: if , is well-denoted and , then is well-denoted and ;
- equality means equal sets where neither side is the point: if , both and are well-denoted and neither denotes the point, then ;
- inference establishes the semantic invariant and a membership: if , then and are well-denoted, is a type, and .
(The theorem is soundnesswhitepaper/Fragment/Sound.lean:L2322-L23262322theorem soundness [LevelOracle] (m : EnvModel V env) (φ : Name → Nat) :
2323 (∀ {Γ : List Expr} {e e' : Expr}, Red env Γ e e' → RedSem m φ Γ e e') ∧
2324 (∀ {Γ : List Expr} {a b : Expr}, DefEq env Γ a b → DefEqSem m φ Γ a b) ∧
2325 (∀ {Γ : List Expr} {e T : Expr}, Infer env Γ e T → InferSem m φ Γ e T) :=
2326 ⟨red_sound, defeq_sound, infer_sound⟩
, with the three claims statedwhitepaper/Fragment/Motive.lean:L51-L8451/-- **`Red`'s claim**: on a subject that is not the point, reduction
52preserves the invariant and the denotation. Nothing is claimed of a
53proof. -/
54def RedSem (m : EnvModel V env) (φ : Name → Nat) (Γ : List Expr) (e e' : Expr) : Prop :=
55 ∀ ρ : Nat → V, Sat m.M φ Γ ρ → WellDenoted m.M φ ρ e → interp m.M φ ρ e ≠ pt →
56 WellDenoted m.M φ ρ e' ∧ interp m.M φ ρ e = interp m.M φ ρ e'
57
58/-- **`DefEqLams`' claim**: two well-denoted sides have
59equal denotations — unconditionally, the point included. -/
60def LamsSem (m : EnvModel V env) (φ : Name → Nat) (Γ : List Expr) (a b : Expr) : Prop :=
61 ∀ ρ : Nat → V, Sat m.M φ Γ ρ → WellDenoted m.M φ ρ a → WellDenoted m.M φ ρ b →
62 interp m.M φ ρ a = interp m.M φ ρ b
63
64/-- **`DefEq`'s claim**: two well-denoted sides that are not the point
65have equal denotations. (Two proofs are the point.) -/
66def DefEqSem (m : EnvModel V env) (φ : Name → Nat) (Γ : List Expr) (a b : Expr) : Prop :=
67 ∀ ρ : Nat → V, Sat m.M φ Γ ρ → WellDenoted m.M φ ρ a → WellDenoted m.M φ ρ b →
68 interp m.M φ ρ a ≠ pt → interp m.M φ ρ b ≠ pt → interp m.M φ ρ a = interp m.M φ ρ b
69
70/-- **`DefEqIn`'s claim**: two well-denoted members of one
71well-denoted type have equal denotations — unconditionally, since a
72type's members are all the point or none. -/
73def DefEqInSem (m : EnvModel V env) (φ : Name → Nat) (Γ : List Expr) (T a b : Expr) : Prop :=
74 ∀ ρ : Nat → V, Sat m.M φ Γ ρ → WellDenoted m.M φ ρ T → IsTy (interp m.M φ ρ T) →
75 WellDenoted m.M φ ρ a → WellDenoted m.M φ ρ b →
76 interp m.M φ ρ a ∈ˢ interp m.M φ ρ T → interp m.M φ ρ b ∈ˢ interp m.M φ ρ T →
77 interp m.M φ ρ a = interp m.M φ ρ b
78
79/-- **`Infer`'s claim**: the term and its type are well-denoted, the
80type is a type, and the term's denotation is a member of the type's. -/
81def InferSem (m : EnvModel V env) (φ : Name → Nat) (Γ : List Expr) (e T : Expr) : Prop :=
82 ∀ ρ : Nat → V, Sat m.M φ Γ ρ →
83 WellDenoted m.M φ ρ e ∧ WellDenoted m.M φ ρ T ∧ IsTy (interp m.M φ ρ T) ∧
84 interp m.M φ ρ e ∈ˢ interp m.M φ ρ T
.)
Reduction and equality assume the semantic invariant; inference establishes it. The inference rules are the ones that check a term’s shape, so their soundness proves that the term’s applications and binders respect domains; the other two relations are handed well-denoted terms and pass the semantic invariant along. The first two claims are also conditional on the point: they say nothing about a term that denotes it.
Why the claims may say nothing about the point. A claim that is silent about the point is only useful if every rule that uses a premise’s claim has that premise not the point, or does not need the claim there. Every rule falls into one of four cases.
- The subject is a type, never the point. The reductions of an inferred type to a sort or a (in the binder rules, in app, in , in proof irrelevance), the comparisons of two types (an argument’s type with a domain in app, a domain with a domain in the congruences, the checker’s type comparisons in ), the fibres compared in -congruence. The third claim made each subject a type, so the conditional claim applies outright.
- The subject is not the point by hypothesis. trans passes the first claim’s denotation on to the second; head reduces the head of an application that is not the point, which is therefore not the point either; red-l reduces a side of an equality that is not the point; app compares the heads of two applications that are not the point.
- The two sides share a type, so they are both the point or neither. app’s arguments: the heads are one graph, whose domain is a type and holds both arguments. ι’s comparisons and its major premise (Section 4) are of the same kind. The bodies of lambda congruence and are the one place where nothing else supplies a common type, and that is why those rules compare the bodies’ types (Section 2.8).
- The step is an identity everywhere. β on a redex that is not the point (Lemma 1), and δ (Section 3) claim nothing beyond what holds at every environment.
theorem closed_infer [LevelOracle] (m : EnvModel V env) (φ : Name → Nat) {e T : Expr}2331
(h : Infer env [] e T) (ρ : Nat → V) :2332
interp m.M φ ρ e ∈ˢ interp m.M φ ρ T :=2333
(infer_sound (m := m) (φ := φ) h ρ (Sat_nil m.M φ ρ)).2.2.2and every .
This is the statement the environment section builds on: a declaration is accepted when its value’s inferred type is definitionally equal to its declared type, and the corollary, with the second claim, puts the value’s set into the declared type’s set.
Proofs that are not proofs. The proof asks whether a term’s value is the point, never whether the term is a proof in Lean’s sense (except in the proof-irrelevance rule, whose claim turns out to be empty), and it learns that only through a type the term lies in. So a term whose value is the point is treated like a proof whatever the sort of its type, also when its value is the point only at some values of a variablewhitepaper/Fragment/Probe.lean:L65-L7265/-- `fun (P : Prop) (h : P) => c`, for `c` not the point: a graph, whose
66application at `False` is the point (an abstraction over an empty
67domain) and at `True` is not — a body whose point-ness varies with the
68variable. -/
69theorem mixed_lam {c : V} (hc : c ≠ pt) :
70 lamV (typeU 0) (fun P => lamV P fun _ => c) ≠ pt ∧
71 SetLib.app (lamV (typeU 0) (fun P => lamV P fun _ => c)) (truthVal False) = pt ∧
72 SetLib.app (lamV (typeU 0) (fun P => lamV P fun _ => c)) one ≠ pt := by
.
The three claims are proved together, by one structural inductionwhitepaper/Fragment/Sound.lean:L2202-L23112202theorem red_sound [LevelOracle] : ∀ {Γ : List Expr} {e e' : Expr},
2203 Red env Γ e e' → RedSem m φ Γ e e'
2204 | _, _, _, .refl => Red.refl_sound
2205 | _, _, _, .trans h₁ h₂ => Red.trans_sound (red_sound h₁) (red_sound h₂)
2206 | _, _, _, .appFn h => Red.appFn_sound (red_sound h)
2207 | _, _, _, .beta => Red.beta_sound
2208 | _, _, _, .zeta => Red.zeta_sound
2209 | _, _, _, .delta hfind hv hls => Red.delta_sound hfind hv hls
2210 | _, _, _, .iota hfind hkind hus hlen hpis hmaj hmeq hrl hctor hinst hcij husj hmlen hpis' hresM
2211 hIM hDM hdoms' htys' hI' hD' hlv hP hres hshape hrps hX =>
2212 Red.iota_sound rfl rfl hfind hkind hus hlen hpis (red_sound hmaj) hmeq hrl hctor hinst hcij husj
2213 hmlen hpis' hresM (infer_sound hIM) (defeq_sound hDM)
2214 hdoms' htys' (fun p hp => infer_sound (hI' p hp)) (fun p hp => defeq_sound (hD' p hp))
2215 hlv (fun p hp => defeq_sound (hP p hp)) hres hshape hrps
2216 (fun p hp => defeq_sound (hX p hp))
2217 | _, _, _, .iotaProp hfind hkind hus hlen hpis hmaj hmeq hrl hctor hinst hcij husj hmlen hpis' hresM
2218 hdoms' htys' hI' hD' hlv hP hres hshape hrps hX hI hlsI hpisI hsort hu =>
2219 Red.iotaProp_sound rfl rfl hfind hkind hus hlen hpis (red_sound hmaj) hmeq hrl hctor hinst hcij husj
2220 hmlen hpis' hresM hdoms' htys' (fun p hp => infer_sound (hI' p hp))
2221 (fun p hp => defeq_sound (hD' p hp)) hlv (fun p hp => defeq_sound (hP p hp)) hres hshape hrps
2222 (fun p hp => defeq_sound (hX p hp)) hI hlsI hpisI hsort hu
2223 | _, _, _, .iotaNested hfind hkind hus hlen hpis hmaj hmeq hrl hctor hinst hcij hcijk husj hmlen
2224 hpis' hresM hIM hDM hdoms' htys' hI' hD' _ _ _ hres hshape hrps hX =>
2225 Red.iotaNested_sound rfl rfl hfind hkind hus hlen hpis (red_sound hmaj) hmeq hrl hctor hinst
2226 hcij hcijk husj hmlen hpis' hresM (infer_sound hIM)
2227 (defeq_sound hDM) hdoms' htys' (fun p hp => infer_sound (hI' p hp))
2228 (fun p hp => defeq_sound (hD' p hp)) hres hshape hrps (fun p hp => defeq_sound (hX p hp))
2229 | _, _, _, .iotaSlot hfind hkind hus hlen hpis hmaj hmeq hrl hctor hinst hcij husj hmlen hpis' hresM
2230 hdoms' htys' hI' hD' hlv hP hres hshape hrps hX =>
2231 Red.iotaSlot_sound rfl rfl hfind hkind hus hlen hpis (red_sound hmaj) hmeq hrl hctor hinst hcij husj
2232 hmlen hpis' hresM hdoms' htys' (fun p hp => infer_sound (hI' p hp))
2233 (fun p hp => defeq_sound (hD' p hp)) hlv (fun p hp => defeq_sound (hP p hp)) hres hshape hrps
2234 (fun p hp => defeq_sound (hX p hp))
2235 | _, _, _, .iotaNestedSlot hfind hkind hus hlen hpis hmaj hmeq hrl hctor hinst hcij hcijk husj hmlen
2236 hpis' hresM hdoms' htys' hI' hD' _ _ _ hres hshape hrps hX =>
2237 Red.iotaNestedSlot_sound rfl rfl hfind hkind hus hlen hpis (red_sound hmaj) hmeq hrl hctor hinst
2238 hcij hcijk husj hmlen hpis' hresM hdoms' htys' (fun p hp => infer_sound (hI' p hp))
2239 (fun p hp => defeq_sound (hD' p hp)) hres hshape hrps (fun p hp => defeq_sound (hX p hp))
2240 | _, _, _, .iotaStrict hfind hkind hus hlen hpis hmaj hmeq hrl hctor hinst hcij husj hmlen hpis'
2241 hresM hdM hI₀ hk₀ hls₀ hd₀ hpis₀ hlvl hlv hP hres hshape hrps hX =>
… 70 more lines over the three mutually inductive relations (as in con-lecheConLeche/Model/Rules/Sound.lean:L49-L5049theorem red_sound (hin : RulesInputs V m φ) :
50 ∀ {d : Nat} {e e' : Expr}, Red env d e e' → RedSem m φ d e e'
). Every rule is one case, and every case is a lemma about that rule alone, with the induction hypothesis for each premise as an assumption. The routine cases are listed at the end; the others follow.
Proof. Rule betawhitepaper/Fragment/Sound.lean:L111-L132111theorem beta_sound {Γ : List Expr} {A b a : Expr} :
112 RedSem m φ Γ (.app (.lam A b) a) (b.inst a) := by
113 intro ρ _ hw hn
114 rw [WellDenoted_app] at hw
115 obtain ⟨hl, ha, p, A', B', hslot, hmem, -, -⟩ := hw
116 rw [WellDenoted_lam] at hl
117 obtain ⟨-, -, hbody, B, hfib, -⟩ := hl
118 have hlam := fn_ne_pt hn
119 have hval : interp m.M φ ρ (.lam A b) =
120 graph (fun x => interp m.M φ (cons x ρ) b) (interp m.M φ ρ A) := by
121 rw [interp_lam] at hlam ⊢; exact lamV_eq_graph hlam
122 have hp : p = false := by
123 cases p
124 · rfl
125 · exact absurd (eq_pt_of_mem_piR_true hslot) hlam
126 subst hp
127 have hown : interp m.M φ ρ (.lam A b) ∈ˢ piR false (interp m.M φ ρ A) B := by
128 rw [hval, piR_false]; exact graph_mem_piSet hfib
129 have haA : interp m.M φ ρ a ∈ˢ interp m.M φ ρ A := by
130 rw [piR_dom_unique hown hslot]; exact hmem
131 refine ⟨(WellDenoted_inst0 m.M φ ha).mpr (hbody _ haA), ?_⟩
132 rw [interp_app, interp_lam, app_lamV haA, interp_inst0]
, :
This is Lemma 1. The rule has no premise, and the lemma needs none, because the claim is asked only where the redex is not the point.
Rule zetawhitepaper/Fragment/Sound.lean:L134-L142134/-- **ζ, with no certificate**: the `let` and its
135reduct denote alike at every environment (`interp_zeta`), so neither
136the point hypothesis nor the context is used; the reduct's invariant is
137the `let`'s, through the substitution lemma. -/
138theorem zeta_sound {Γ : List Expr} {T v b : Expr} :
139 RedSem m φ Γ (.letE T v b) (b.inst v) := by
140 intro ρ _ hw _
141 rw [WellDenoted_letE] at hw
142 exact ⟨(WellDenoted_inst0 m.M φ hw.1).mpr hw.2, interp_zeta m.M φ T v b ρ⟩
, :
The two sides denote the same set at every environment, the point included, and the reduct’s semantic invariant is the let’s, through the substitution transport.
Rule red-lwhitepaper/Fragment/Sound.lean:L165-L171165/-- Reduce, then continue — sound with no typing: the side is not the
166point, so its reduction is. -/
167theorem redL_sound {Γ : List Expr} {a a' b : Expr}
168 (hr : RedSem m φ Γ a a') (h : DefEqSem m φ Γ a' b) : DefEqSem m φ Γ a b := by
169 intro ρ hs ha hb hna hnb
170 obtain ⟨ha', he⟩ := hr ρ hs ha hna
171 exact he.trans (h ρ hs ha' hb (he ▸ hna) hnb)
, from and :
is not the point, so by the first claim is well-denoted and . Now and are both well-denoted and neither is the point, so the second claim applies to the continuation, and the two equalities chain. This is the one sound way to chain in the equality relation, because a reduction step produces the semantic invariant of its result; see the discussion of transitivity below.
Rule sortwhitepaper/Fragment/Sound.lean:L173-L175173theorem sort_sound [LevelOracle] {Γ : List Expr} {u v : Level} (h : LevelOracle.eq u v = true) :
174 DefEqSem m φ Γ (.sort u) (.sort v) :=
175 fun _ _ _ _ _ _ => by simp [eval_eq_of_oracle h]
, when :
The oracle is assumed correct: it answers yes only if the levels agree at every valuation (Section 2.2), so the two sorts denote the same set.
Rule piwhitepaper/Fragment/Sound.lean:L181-L198181/-- **`∀`-congruence, with no premise beyond domains and bodies**:
182a `∀`'s value is a function of its domain's and fibres' values
183(`SetLib.piV_congr`), and both are types, never the point. -/
184theorem pi_sound {Γ : List Expr} {A₁ B₁ A₂ B₂ : Expr}
185 (hA : DefEqSem m φ Γ A₁ A₂) (hB : DefEqSem m φ (A₂ :: Γ) B₁ B₂) :
186 DefEqSem m φ Γ (.pi A₁ B₁) (.pi A₂ B₂) := by
187 intro ρ hs h₁ h₂ _ _
188 rw [WellDenoted_pi] at h₁ h₂
189 have heA := hA ρ hs h₁.1 h₂.1 h₁.2.1.ne_pt h₂.2.1.ne_pt
190 have hfib : ∀ x, x ∈ˢ interp m.M φ ρ A₂ →
191 interp m.M φ (cons x ρ) B₁ = interp m.M φ (cons x ρ) B₂ := by
192 intro x hx
193 have hx₁ : x ∈ˢ interp m.M φ ρ A₁ := by rw [heA]; exact hx
194 exact hB (cons x ρ) ((Sat_cons m.M φ).mpr ⟨hs, h₂.1, h₂.2.1, hx⟩) (h₁.2.2.1 x hx₁)
195 (h₂.2.2.1 x hx) (h₁.2.2.2 x hx₁).ne_pt (h₂.2.2.2 x hx).ne_pt
196 simp only [interp_pi]
197 rw [heA]
198 exact piV_congr hfib
, from and :
Both s are well-denoted, so their domains are types, never the point, and the second claim on the domains gives . At every in that domain the fibres are types too, so the claim on the bodies makes them equal. A ‘s set is a function of its domain’s set and its fibres, the choice of reading included, since that is read off the fibres; so the two s are equal. No comparison of the two sides’ sorts is needed.
Rule lamwhitepaper/Fragment/Sound.lean:L200-L220200/-- **Lambda congruence, typed**: the two bodies lie in one type
201at every value of the variable — their inferred types are equal there
202— so they are both the point or neither is, and where neither is the
203bodies' claim makes them equal. Neither lambda need be a graph. -/
204theorem lam_sound {Γ : List Expr} {A₁ b₁ A₂ b₂ t₁ t₂ : Expr}
205 (hA : DefEqSem m φ Γ A₁ A₂) (hb : DefEqSem m φ (A₂ :: Γ) b₁ b₂)
206 (ht₁ : InferSem m φ (A₂ :: Γ) b₁ t₁) (ht₂ : InferSem m φ (A₂ :: Γ) b₂ t₂)
207 (ht : DefEqSem m φ (A₂ :: Γ) t₁ t₂) :
208 DefEqSem m φ Γ (.lam A₁ b₁) (.lam A₂ b₂) := by
209 intro ρ hs h₁ h₂ _ _
210 rw [WellDenoted_lam] at h₁ h₂
211 have heA := hA ρ hs h₁.1 h₂.1 h₁.2.1.ne_pt h₂.2.1.ne_pt
212 simp only [interp_lam]
213 rw [heA]
214 apply lamV_congr
215 intro x hx
216 have hs' : Sat m.M φ (A₂ :: Γ) (cons x ρ) := (Sat_cons m.M φ).mpr ⟨hs, h₂.1, h₂.2.1, hx⟩
217 obtain ⟨hw₁, hwt₁, hT₁, hm₁⟩ := ht₁ _ hs'
218 obtain ⟨hw₂, hwt₂, hT₂, hm₂⟩ := ht₂ _ hs'
219 rw [ht _ hs' hwt₁ hwt₂ hT₁.ne_pt hT₂.ne_pt] at hm₁
220 exact hT₂.eq_of_ne_pt hm₁ hm₂ (hb _ hs' hw₁ hw₂)
, from the domains, the bodies, and the bodies’ types :
The domains are equal as for . Fix in the domain. The third claim puts in and in (at ), both types, and the second claim on the types, which are not the point, makes them one type. Its members are all the point or none, so either both bodies are the point, or neither is and the claim on the bodies makes them equal. Either way they are equal at every , and so are the two abstractions, whether or not either of them is the point. The claim is therefore unconditional; lam-outer passes such a claim outwards (Section 2.8), and lams weakens it to the second claim. Without the type premises the argument breaks at “both or neither”: a body may be the point at some and not at others, and the claim on the bodies says nothing at such a .
Rule appwhitepaper/Fragment/Sound.lean:L233-L254233/-- **Application congruence**: the two heads are equal graphs, so the
234two arguments lie in one domain, which is a type — both arguments are
235the point, or neither is. -/
236theorem app_sound {Γ : List Expr} {f₁ a₁ f₂ a₂ : Expr}
237 (hf : DefEqSem m φ Γ f₁ f₂) (ha : DefEqSem m φ Γ a₁ a₂) :
238 DefEqSem m φ Γ (.app f₁ a₁) (.app f₂ a₂) := by
239 intro ρ hs h₁ h₂ hn₁ hn₂
240 obtain ⟨A₁, B₁, hs₁, hm₁, hT₁⟩ := app_slot h₁ hn₁
241 obtain ⟨A₂, B₂, hs₂, hm₂, -⟩ := app_slot h₂ hn₂
242 have h₁' := h₁
243 have h₂' := h₂
244 rw [WellDenoted_app] at h₁' h₂'
245 have heF := hf ρ hs h₁'.1 h₂'.1 (fn_ne_pt hn₁) (fn_ne_pt hn₂)
246 rw [← heF] at hs₂
247 have hA : A₁ = A₂ := piSet_dom_unique hs₁ hs₂
248 rw [← hA] at hm₂
249 have heA : interp m.M φ ρ a₁ = interp m.M φ ρ a₂ := by
250 rcases hT₁ with hT | hT
251 · rw [eq_pt_of_mem_univ_zero hT hm₁, eq_pt_of_mem_univ_zero hT hm₂]
252 · exact ha ρ hs h₁'.2.1 h₂'.2.1 (ne_pt_of_mem_strict hT hm₁) (ne_pt_of_mem_strict hT hm₂)
253 simp only [interp_app]
254 rw [heF, heA]
, :
Neither application is the point, so neither head is, and the claim on the heads makes them one graph. Each application’s rule puts its head in some , with its argument in ’s domain. A graph is in no propositional function space, so each is a function space, and domain uniqueness puts both arguments in one domain, a type. They are both the point, or neither is and the claim on the arguments makes them equal.
Rule fun-etawhitepaper/Fragment/Sound.lean:L391-L436391/-- **η, typed**: at every value of the variable the lambda's
392body and `b` applied lie in one type — the body's inferred type is the
393`∀`'s fibre — so they are both the point or neither is, and where
394neither is the body's claim makes them equal; a member of a function space is
395the abstraction of its applications (`SetLib.lamV_eta`). -/
396theorem eta_sound {Γ : List Expr} {A₁ b₁ b tb A₂ B t₁ : Expr}
397 (htb : ∀ ρ, Sat m.M φ Γ ρ → WellDenoted m.M φ ρ b → interp m.M φ ρ b ≠ pt →
398 WellDenoted m.M φ ρ tb ∧ IsTy (interp m.M φ ρ tb) ∧
399 interp m.M φ ρ b ∈ˢ interp m.M φ ρ tb)
400 (hr : RedSem m φ Γ tb (.pi A₂ B))
401 (hA : DefEqSem m φ Γ A₂ A₁)
402 (hbody : DefEqSem m φ (A₁ :: Γ) b₁ (.app (b.liftN 1) (.bvar 0)))
403 (ht₁ : InferSem m φ (A₁ :: Γ) b₁ t₁) (htB : DefEqSem m φ (A₁ :: Γ) t₁ B) :
404 DefEqSem m φ Γ (.lam A₁ b₁) b := by
405 intro ρ hs hlam hwb _ hnb
406 obtain ⟨hwtb, hTtb, hmemb⟩ := htb ρ hs hwb hnb
407 obtain ⟨hwpi, hepi⟩ := hr ρ hs hwtb hTtb.ne_pt
408 rw [hepi, interp_pi] at hmemb
409 rw [WellDenoted_pi] at hwpi
410 have hlam' := hlam
411 rw [WellDenoted_lam] at hlam'
412 have heA := hA ρ hs hwpi.1 hlam'.1 hwpi.2.1.ne_pt hlam'.2.1.ne_pt
413 have hsh : ∀ x, shiftE 1 0 (cons x ρ) = ρ := by
414 intro x; funext i; simp [shiftE]
415 have hlift : ∀ x, interp m.M φ (cons x ρ) (b.liftN 1) = interp m.M φ ρ b := by
416 intro x; rw [interp_liftN, hsh]
417 have hpt : ∀ x, x ∈ˢ interp m.M φ ρ A₁ →
418 interp m.M φ (cons x ρ) b₁ = SetLib.app (interp m.M φ ρ b) x := by
419 intro x hx
420 have hx₂ : x ∈ˢ interp m.M φ ρ A₂ := by rw [heA]; exact hx
421 have hs' : Sat m.M φ (A₁ :: Γ) (cons x ρ) :=
422 (Sat_cons m.M φ).mpr ⟨hs, hlam'.1, hlam'.2.1, hx⟩
423 have happ : WellDenoted m.M φ (cons x ρ) (.app (b.liftN 1) (.bvar 0)) := by
424 rw [WellDenoted_app]
425 exact ⟨(WellDenoted_liftN m.M φ 1 b 0 (cons x ρ)).mpr (by rw [hsh]; exact hwb), trivial,
426 _, interp m.M φ ρ A₂, fun y => interp m.M φ (cons y ρ) B,
427 by rw [hlift]; exact hmemb, hx₂, fun hp => propB_eq_true.mp hp, fun _ => hwpi.2.1⟩
428 obtain ⟨hwb₁, hwt₁, hT₁, hm₁⟩ := ht₁ _ hs'
429 rw [htB _ hs' hwt₁ (hwpi.2.2.1 x hx₂) hT₁.ne_pt (hwpi.2.2.2 x hx₂).ne_pt] at hm₁
430 have hm₂ : interp m.M φ (cons x ρ) (.app (b.liftN 1) (.bvar 0)) ∈ˢ
… 6 more lines, when , , , and the body’s type agrees with :
is not the point, so the third claim, then the first, put in the denotation of the , which is well-denoted; the domains are equal as before. Fix in the domain. The lambda’s body lies in its inferred type, which the type premise makes the fibre ; the term lies there by elimination. In one type, they are both the point or neither, and in the second case the claim on the bodies makes them equal. So the lambda denotes the abstraction of , and a member of a function space of types is the abstraction of its applicationswhitepaper/Fragment/Lib.lean:L574-L577574/-- **η**: a member of a function space whose fibres are types is the
575abstraction of its applications. -/
576theorem lamV_eta {A f : V} {B : V → V} (hf : f ∈ˢ piV A B) (hB : ∀ x, x ∈ˢ A → IsTy (B x)) :
577 lamV A (fun x => app f x) = f := by
.
Rule proof-irrelwhitepaper/Fragment/Sound.lean:L579-L589579/-- **Proof irrelevance claims nothing**: a side whose type's sort is
580`Prop` is the point, and the claim is about sides that are not. -/
581theorem proofIrrel_sound [LevelOracle] {Γ : List Expr} {a ta tta b : Expr} {u : Level}
582 (hta : InferSem m φ Γ a ta) (htta : InferSem m φ Γ ta tta)
583 (hu : RedSem m φ Γ tta (.sort u)) (hu0 : LevelOracle.eq u .zero = true) :
584 DefEqSem m φ Γ a b := by
585 intro ρ hs _ _ hna _
586 obtain ⟨-, hmemT⟩ := Infer.mem_typeU_of htta hu hs
587 obtain ⟨-, -, -, hmem⟩ := hta ρ hs
588 rw [eval_eq_of_oracle hu0, Level.eval_zero, mem_typeU_zero] at hmemT
589 exact absurd (eq_pt_of_mem_univ_zero hmemT hmem) hna
, when with , and likewise for :
By the third claim twice and the first once, , a truth value, whose only possible member is the point. So denotes the point, and the claim, which is about sides that are not the point, holds vacuously. The two types and were never compared, and the semantic invariants of and were not used.
Rule pi, inferencewhitepaper/Fragment/Sound.lean:L292-L306292theorem pi_sound {Γ : List Expr} {A B s t : Expr} {u v : Level}
293 (hA : InferSem m φ Γ A s) (hu : RedSem m φ Γ s (.sort u))
294 (hB : InferSem m φ (A :: Γ) B t) (hv : RedSem m φ (A :: Γ) t (.sort v)) :
295 InferSem m φ Γ (.pi A B) (.sort (.imax u v)) := by
296 intro ρ hs
297 obtain ⟨hwA, hmemA⟩ := mem_typeU_of hA hu hs
298 have hTA := IsTy_of_mem_typeU hmemA
299 have hbody : ∀ x, x ∈ˢ interp m.M φ ρ A →
300 WellDenoted m.M φ (cons x ρ) B ∧ interp m.M φ (cons x ρ) B ∈ˢ typeU (Level.eval φ v) :=
301 fun x hx => mem_typeU_of hB hv ((Sat_cons m.M φ).mpr ⟨hs, hwA, hTA, hx⟩)
302 refine ⟨?_, trivial, Or.inr (strict_typeU _), ?_⟩
303 · rw [WellDenoted_pi]
304 exact ⟨hwA, hTA, fun x hx => (hbody x hx).1, fun x hx => IsTy_of_mem_typeU (hbody x hx).2⟩
305 · rw [interp_pi, interp_sort, Level.eval_imax]
306 exact piV_mem_typeU hmemA fun x hx => (hbody x hx).2
, when and :
The third claim for and the first for put in , so it is a type, and for every the environment satisfies ; the same claims for put every fibre in . That is the rule of the semantic invariant, and lies in whitepaper/Fragment/Lib.lean:L595-L601595/-- **`∀`-formation in the sorts' sets `typeU`**, with no regime
596premise: over fibres in `Sort 0` the function space is a truth value; over
597fibres in a positive sort it is a truth value or a strict function
598space, a type either way. -/
599theorem piV_mem_typeU {u v : Nat} {A : V} {B : V → V}
600 (hA : A ∈ˢ typeU u) (hB : ∀ x, x ∈ˢ A → B x ∈ˢ typeU v) :
601 piV A B ∈ˢ typeU (Level.imaxNat u v) := by
. If , every fibre is a truth value, so is , and . Otherwise is a truth value or a function space in by cumulativity and closure, a type either way. need not be a function space when : over an empty domain it is , and admits it.
Rule lam, inferencewhitepaper/Fragment/Sound.lean:L308-L329308/-- **Lambda**: Lean's rule. The body's type is a type (`InferSem`), which
309is all `lamV_mem` asks of the fibres. -/
310theorem lam_sound {Γ : List Expr} {A b s bt : Expr} {u : Level}
311 (hA : InferSem m φ Γ A s) (hu : RedSem m φ Γ s (.sort u))
312 (hb : InferSem m φ (A :: Γ) b bt) :
313 InferSem m φ Γ (.lam A b) (.pi A bt) := by
314 intro ρ hs
315 obtain ⟨hwA, hmemA⟩ := mem_typeU_of hA hu hs
316 have hTA := IsTy_of_mem_typeU hmemA
317 have hbody : ∀ x, x ∈ˢ interp m.M φ ρ A →
318 WellDenoted m.M φ (cons x ρ) b ∧ WellDenoted m.M φ (cons x ρ) bt ∧
319 IsTy (interp m.M φ (cons x ρ) bt) ∧
320 interp m.M φ (cons x ρ) b ∈ˢ interp m.M φ (cons x ρ) bt :=
321 fun x hx => hb (cons x ρ) ((Sat_cons m.M φ).mpr ⟨hs, hwA, hTA, hx⟩)
322 refine ⟨?_, ?_, by rw [interp_pi]; exact IsTy_piV, ?_⟩
323 · rw [WellDenoted_lam]
324 exact ⟨hwA, hTA, fun x hx => (hbody x hx).1, fun x => interp m.M φ (cons x ρ) bt,
325 fun x hx => (hbody x hx).2.2.2, fun x hx => (hbody x hx).2.2.1⟩
326 · rw [WellDenoted_pi]
327 exact ⟨hwA, hTA, fun x hx => (hbody x hx).2.1, fun x hx => (hbody x hx).2.2.1⟩
328 · rw [interp_lam, interp_pi]
329 exact lamV_mem (fun x hx => (hbody x hx).2.2.2) fun x hx => (hbody x hx).2.2.1
, when and :
The same argument one level down. The domain is a type, and for every in it the third claim for gives and well-denoted, a type and . So the family of the is a bounded codomain of types, which is the lambda rule, and the rule of the inferred type is met too; and the introduction law for values puts the abstraction into . Nothing about the sort of is needed, so the rule computes none.
Rule app, inferencewhitepaper/Fragment/Sound.lean:L331-L349331theorem app_sound {Γ : List Expr} {f a tf A B ta : Expr}
332 (hf : InferSem m φ Γ f tf) (hr : RedSem m φ Γ tf (.pi A B))
333 (ha : InferSem m φ Γ a ta) (hd : DefEqSem m φ Γ ta A) :
334 InferSem m φ Γ (.app f a) (B.inst a) := by
335 intro ρ hs
336 obtain ⟨hwf, hwtf, hTtf, hmemf⟩ := hf ρ hs
337 obtain ⟨hwpi, hepi⟩ := hr ρ hs hwtf hTtf.ne_pt
338 rw [hepi, interp_pi] at hmemf
339 have hwpi' := hwpi
340 rw [WellDenoted_pi] at hwpi'
341 obtain ⟨hwA, hTA, hwB, hfib⟩ := hwpi'
342 obtain ⟨hwa, hwta, hTta, hmema⟩ := ha ρ hs
343 rw [hd ρ hs hwta hwA hTta.ne_pt hTA.ne_pt] at hmema
344 refine ⟨?_, (WellDenoted_inst0 m.M φ hwa).mpr (hwB _ hmema), ?_, ?_⟩
345 · rw [WellDenoted_app]
346 exact ⟨hwf, hwa, _, _, _, hmemf, hmema, fun hp => propB_eq_true.mp hp, fun _ => hTA⟩
347 · rw [interp_inst0]; exact hfib _ hmema
348 · rw [interp_app, interp_inst0]
349 exact app_mem_piV hmemf hmema
, when , and :
By the third claim, and are well-denoted and ; is a type, so by the first claim the is well-denoted and . By the third claim for , and are well-denoted, is a type and ; is a type by the rule, so the second claim gives and . Now is well-denoted by the application rule of Section 2.6, with the ‘s denotation as ; is well-denoted by the substitution transport; and the elimination law puts into the fibre at , which is by the substitution lemma.
A syntactic proof would here invert a derivation of to learn the domain and the codomain, using the injectivity of that Section 2.3 discussed. Our proof inverts nothing. The checker itself reduced to a syntactic , the first claim says that the reduction did not change the set, and that set is by the interpretation’s rule.
Rule let, inferencewhitepaper/Fragment/Sound.lean:L351-L367351/-- **`let`, by substitution**: the body with the value
352substituted carries the claim, and the `let` denotes what it denotes
353(`interp_zeta`); its invariant is the `let`'s (`WellDenoted_letE`
354against `WellDenoted_inst0`). The type checks on the value (`hT`,
355`hu`, `hv`, `hd`) are not used: `interp` reads no type, and the body
356is inferred at the value itself, not at a variable of the type. -/
357theorem letE_sound {Γ : List Expr} {T v b s tv B : Expr} {u : Level}
358 (_hT : InferSem m φ Γ T s) (_hu : RedSem m φ Γ s (.sort u))
359 (hv : InferSem m φ Γ v tv) (_hd : DefEqSem m φ Γ tv T)
360 (hb : InferSem m φ Γ (b.inst v) B) :
361 InferSem m φ Γ (.letE T v b) B := by
362 intro ρ hs
363 obtain ⟨hwv, -⟩ := hv ρ hs
364 obtain ⟨hwb, hwB, hTB, hmem⟩ := hb ρ hs
365 refine ⟨?_, hwB, hTB, ?_⟩
366 · rw [WellDenoted_letE]; exact ⟨hwv, (WellDenoted_inst0 m.M φ hwv).mp hwb⟩
367 · rw [interp_zeta]; exact hmem
, when and the checks on the value:
The claim for the substituted body is the claim for the let: the two denote the same set, and the let’s semantic invariant is the substituted body’s, by the transport. The two checks on the value are not used. They would be if the body were inferred at a variable of type instead: then the value must lie in the typewhitepaper/Fragment/Sound.lean:L369-L385369/-- **What the value checks give**: the value's value is
370a member of the type's, so the value extends a satisfying environment
371to one satisfying the context extended by the type. The substitution
372version does not need it (`letE_sound`); a version that opens the body
373at a let-variable of type `T` would (the body's claim is then stated at
374`T :: Γ`). The type and the value's inferred type are both types,
375never the point, so `DefEq`'s conditional claim applies to them. -/
376theorem letE_sat {Γ : List Expr} {T v s tv : Expr} {u : Level}
377 (hT : InferSem m φ Γ T s) (hu : RedSem m φ Γ s (.sort u))
378 (hv : InferSem m φ Γ v tv) (hd : DefEqSem m φ Γ tv T) {ρ : Nat → V}
379 (hs : Sat m.M φ Γ ρ) :
380 Sat m.M φ (T :: Γ) (cons (interp m.M φ ρ v) ρ) := by
381 obtain ⟨hwT, hmemT⟩ := mem_typeU_of hT hu hs
382 have hTT := IsTy_of_mem_typeU hmemT
383 obtain ⟨-, hwtv, hTtv, hmem⟩ := hv ρ hs
384 rw [hd ρ hs hwtv hwT hTtv.ne_pt hTT.ne_pt] at hmem
385 exact (Sat_cons m.M φ).mpr ⟨hs, hwT, hTT, hmem⟩
to extend a satisfying environment, and the checks give that.
The rest, by induction on the derivation.
- Reductionwhitepaper/Fragment/Sound.lean:L87-L105 87
theorem refl_sound {Γ : List Expr} {e : Expr} : RedSem m φ Γ e e :=
88fun _ _ h _ => ⟨h, rfl⟩
89
90theorem trans_sound {Γ : List Expr} {e₁ e₂ e₃ : Expr}
91(h₁ : RedSem m φ Γ e₁ e₂) (h₂ : RedSem m φ Γ e₂ e₃) : RedSem m φ Γ e₁ e₃ := by
92intro ρ hs hw hn
93obtain ⟨hw₁, he₁⟩ := h₁ ρ hs hw hn
94obtain ⟨hw₂, he₂⟩ := h₂ ρ hs hw₁ (he₁ ▸ hn)
95exact ⟨hw₂, he₁.trans he₂⟩
96
97theorem appFn_sound {Γ : List Expr} {f f' a : Expr} (h : RedSem m φ Γ f f') :
98RedSem m φ Γ (.app f a) (.app f' a) := by
99intro ρ hs hw hn
100rw [WellDenoted_app] at hw
101obtain ⟨hf, ha, p, A, B, hslot, hmem, hB, hT⟩ := hw
102obtain ⟨hf', he⟩ := h ρ hs hf (fn_ne_pt hn)
103refine ⟨?_, by simp [he]⟩
104rw [WellDenoted_app]
105exact ⟨hf', ha, p, A, B, he ▸ hslot, hmem, hB, hT⟩
: the no-step reduction is ; trans chains two reductions, passing the semantic invariant along; head reduces the head of an application that is not the point, which is not the point either, and keeps the application’s rule, because the head’s set did not change. - Equalitywhitepaper/Fragment/Sound.lean:L158-L163158
theorem refl_sound {Γ : List Expr} {a : Expr} : DefEqSem m φ Γ a a :=
159fun _ _ _ _ _ _ => rfl
160
161theorem symm_sound {Γ : List Expr} {a b : Expr} (h : DefEqSem m φ Γ a b) :
162DefEqSem m φ Γ b a :=
163fun ρ hs hb ha hnb hna => (h ρ hs ha hb hna hnb).symm
: refl is again , and sym swaps the two sides. - Inferencewhitepaper/Fragment/Sound.lean:L262-L274262
theorem bvar_sound {Γ : List Expr} {i : Nat} {A : Expr} (h : Γ[i]? = some A) :
263InferSem m φ Γ (.bvar i) (A.liftN (i + 1)) := by
264intro ρ hs
265obtain ⟨hw, hT, hmem⟩ := Sat_get m.M φ hs h
266refine ⟨trivial, ?_, ?_, ?_⟩
267· rw [WellDenoted_liftN]; exact hw
268· rw [interp_liftN]; exact hT
269· rw [interp_bvar, interp_liftN]; exact hmem
270
271theorem sort_sound {Γ : List Expr} {u : Level} :
272InferSem m φ Γ (.sort u) (.sort (.succ u)) :=
273fun _ _ => ⟨trivial, trivial, Or.inr (strict_typeU _), by
274simp only [interp_sort, Level.eval_succ]; exact typeU_mem_typeU_succ _⟩
: a variable’s type is read off the satisfied context; a sort’s type is the next sort, which contains it.
□
Why there is no transitivity. The proof above shows why the rule “ and give ” cannot be added. The second claim assumes both sides well-denoted. In a transitivity case the induction would have to apply the hypothesis to , and for that it needs well-denoted. But is neither a subterm of or nor produced by a premise, so nothing supplies its semantic invariant. Every other rulewhitepaper/Fragment/Rules.lean:L529-L546529/-- **Definitional equality**: the verdict `true` of the checker's
530`isDefEq`.
531
532**There is no `trans` rule, deliberately** (`Rel.lean:334-357`).
533The relation is the checker's verdict on two terms that are
534well-formed *together*, a fact no rule states because well-formedness
535is semantic (`WellDenoted`) and may not enter this inductive. Every
536rule respects one discipline instead: **the subject of each `DefEq`
537premise is a subterm of the conclusion or is produced by a `Red` or
538`Infer` premise** (a reduct, an inferred type), so the soundness proof
539always has the premise's terms — and their invariant — in hand. A
540`trans` rule is the unique rule that would break it: its middle term
541comes from nowhere, so nothing supplies its invariant, and the
542induction has nothing to apply the hypothesis to. Nor does the
543checker chain through an arbitrary middle term: every comparison it
544makes is one of the rules below, and its one way to chain is
545"reduce, then continue": `redL`.
546-/
keeps one discipline: each term of an equality premise is either a subterm of the conclusion (the congruences, the body) or a term that another premise produced, a reduct (red-l) or an inferred type (lam, ), and the corresponding claim delivers the semantic invariant of every produced term. The one way to chain is therefore “reduce, then continue”, and that is how the checker’s equality test is structured: it head-normalises a side and compares again.
2.8. The certification tax
The checker makes two kinds of check that other checkers for Lean (the official kernel, Lean4Lean, nanoda) skip. Both come from one fact: the semantic invariant admits terms that are well-denoted but not well-typed. A typing judgement would exclude them, and with them the need for the checks. The semantic proof has no typing judgement, so the checker verifies the one property the proof would otherwise take from typing. The two kinds are:
- The bodies’ types at lambda congruence and : inferred by full inference and compared, at the innermost level of two lambda telescopes only. This subsection explains them.
- The constructor application in one case of ι-reduction: when the family of the major premise may be a proposition at the instance and the rule is not K-like (one constructor, no fields, in
Prop; Section 4.2.8), the checker compares the constructor’s levels, parameters and index expressions with the recursor’s, and type-checks every parameter and field of the constructor application against the constructor’s type. The official kernel makes none of these checks at an ι step; Section 4.2.7 and Section 4.2.8 explain them.
/-- The innermost level of `defeqLams`: the opened bodies, and at the1338
verified mode their FULL-grade types. -/1339
def defeqLamsInner (r : CoreFns m) (depth : Nat) (b₁ b₂ : Expr) : m Bool := do1340
unless ← r.defeq (depth + 1) b₁ b₂ do return false1341
if mode.verifiedChecks then1342
unless ← r.defeq (depth + 1) (← r.infer (depth + 1) b₁)1343
(← r.infer (depth + 1) b₂) do return false, its ConLeche/Kernel/Core.lean:L483-L488483
-- Task #342 S0: the λ/η tax (T2)484
if mode.verifiedChecks then485
unless ← r.defeq (depth + 1)486
(← r.infer (depth + 1)487
(body₁.instantiate1 (.fvar depth ty₁)))488
(cod₂.instantiate1 (.fvar depth ty₁)) do return falseand the case of ιConLeche/Kernel/Core.lean:L851-L865851
if ← (if mode.verifiedChecks then852
(if iotaCase3 env.find? rl cvj cmp.1 then do853
if ← liftFueled "level comparison" (Level.isEquivList usj cmp.1) then854
if ← defEqList r env depth (margs.take rl.ctorParams) cmp.2 then855
if ← iotaCertsFull r env depth856
(cvj.type.instantiateLevelParams cvj.levelParams usj)857
margs then858
iotaIndexOk r env depth mI rP rl.ctorParams859
(cvj.type.instantiateLevelParams cvj.levelParams usj)860
margs ((args.take mI).drop rP)861
else pure false862
else pure false863
else pure false864
else pure true)865
else pure true) then. On real input neither kind ever fails: they never change a verdict on a stream the official kernel accepts. Both run at
--verified only; --trusted skips them.Why lambda congruence needs the bodies’ types. The claim on the bodies says nothing where a body is the point, and whether a body is the point can vary with the variable. Without a common type of the two bodies the rule is unsound in the model. Take the two lambdaswhitepaper/Fragment/ValueReading.lean:L177-L184177/-- **Untyped lambda congruence is unsound** in the value-driven model: the
178rule's two premises are checker derivations — the domains by `refl`,
179the bodies by β then `refl` — yet in a model of the empty environment
180the two lambdas are well-denoted, neither is the point, and they differ. -/
181theorem lam_congr_untyped_unsound [LevelOracle] (φ : Name → Nat) (ρ : Nat → V) :
182 DefEq Env.empty [] (.sort .zero) (.sort .zero) ∧
183 DefEq Env.empty [.sort .zero] mixedBody (.const nName []) ∧
184 ¬ DefEqSem (EnvModel.empty (mixedM (V := V))) φ [] lamMixed lamConst := by
with sending True to Prop and False to False, and denoting True. They have equal domains, and their bodies are equal by β then refl; both premises are derivations of the checker. Yet at the left body is an abstraction over an empty domain, applied, hence the point, while the right body is True. The two lambdas are well-denoted, neither is the point, and they differ. The left body is ill-typed ( is no member of ), but it is well-denoted, because the abstraction over is the point and constrains nothing.
Why other checkers do without it. Their argument only ever compares two terms of one type: the caller compared the types, or knows them, and lambda inversion gives the two bodies a common type, by subject reduction and uniqueness of types. The model has neither, and the semantic invariant is satisfied by terms no typing judgement derives, so the common type is recomputed.
Innermost only. The checker compares two lambda telescopes level by levelwhitepaper/Fragment/Rules.lean:L670-L714670/-- **Lambda telescope congruence, typed at the innermost level only**.
671Two lambda telescopes are compared level by level: the domains
672at every level, the bodies' types only where the comparison stops
673descending through two lambdas.
674
675*Why the outer levels need no type comparison.* Syntactically: the
676type of `fun x : A => fun y : B => c` is `∀ x : A, ∀ y : B, T` where
677`T` is `c`'s type, so for two telescopes compared with equal domains,
678each outer level's bodies' types are `∀`s over the domain the next
679level compares, with the next level's bodies' types as codomains; equal
680innermost bodies' types give equal bodies' types at every level, by
681`∀`-congruence. Semantically (`Sound.lean`, `DefEqLams.outer_sound`):
682the type comparison is what makes lambda congruence's claim
683**unconditional** — `DefEq`'s claim says nothing where a side is the
684point, and two bodies in one type are both the point or neither — and
685an unconditional equality of the bodies at every value of the variable
686is an equality of the lambdas, with no type at all. So the claim of this
687relation, `LamsSem`, is unconditional, and it propagates outward
688through any number of levels.
689
690*Unequal depth, and reduction between levels.* The descent continues
691only through two **syntactic** lambdas (`outer`'s premise is this relation
692itself, which has no reduction rule). Where one body is not a lambda — the
693comparison ends in η, or in any other rule, possibly after δ or whnf
694steps — that level is the innermost one (`inner`), and its bodies'
695types are inferred and compared at the full grade. A δ or whnf step
696between levels therefore resets the accounting: the δ-witness `d :=
697fun x => (fun h => h) n` (`Probe.lean`, `lam_congr_sorts_unsound`)
698puts the reduction inside `inner`'s untyped premise `DefEq … b₁ b₂`,
699where the type comparison is still required. Syntactically equal
700lambdas (`refl`) are equal outright. -/
701inductive DefEqLams (env : Env) : List Expr → Expr → Expr → Prop where
702 /-- Syntactically equal sides (the checker's `a == b` fast path). -/
703 | refl {Γ : List Expr} {a : Expr} : DefEqLams env Γ a a
704 /-- The innermost level: `DefEq.lam`'s premises — domains, bodies,
705 and the bodies' inferred types. -/
706 | inner {Γ : List Expr} {A₁ b₁ A₂ b₂ t₁ t₂ : Expr} :
707 DefEq env Γ A₁ A₂ → DefEq env (A₂ :: Γ) b₁ b₂ →
708 Infer env (A₂ :: Γ) b₁ t₁ → Infer env (A₂ :: Γ) b₂ t₂ → DefEq env (A₂ :: Γ) t₁ t₂ →
709 DefEqLams env Γ (lam A₁ b₁) (lam A₂ b₂)
… 5 more lines, and infers and compares the bodies’ types only at the innermost level, where the descent through two syntactic lambdas stops (the next comparison is , or a reduction, or any other rule). At the outer levels the domains are compared and nothing else. This is sound because the type comparison makes the claim at the innermost level unconditional: two bodies in one type are equal at every value of the variable, the point included. An unconditional equality of the bodies at every value is an equality of the abstractions, with no type at all, so the claim propagates outwardswhitepaper/Fragment/Sound.lean:L722-L739722/-- **An outer level needs no type**: the bodies are equal
723at every value of the variable, unconditionally, so the lambdas are; the
724domains are types, never the point, so their conditional claim
725applies. This is why the type comparison is owed only at the
726innermost level of a lambda telescope pair. -/
727theorem outer_sound {Γ : List Expr} {A₁ b₁ A₂ b₂ : Expr}
728 (hA : DefEqSem m φ Γ A₁ A₂) (hb : LamsSem m φ (A₂ :: Γ) b₁ b₂) :
729 LamsSem m φ Γ (.lam A₁ b₁) (.lam A₂ b₂) := by
730 intro ρ hs h₁ h₂
731 rw [WellDenoted_lam] at h₁ h₂
732 have heA := hA ρ hs h₁.1 h₂.1 h₁.2.1.ne_pt h₂.2.1.ne_pt
733 simp only [interp_lam]
734 rw [heA]
735 apply lamV_congr
736 intro x hx
737 have hx₁ : x ∈ˢ interp m.M φ ρ A₁ := by rw [heA]; exact hx
738 exact hb (cons x ρ) ((Sat_cons m.M φ).mpr ⟨hs, h₂.1, h₂.2.1, hx⟩) (h₁.2.2.1 x hx₁)
739 (h₂.2.2.1 x hx)
through any number of levels. A δ step or a reduction between two levels ends the telescope, and the level below it is an innermost level again.
Full inference. The bodies’ types are inferred by full inference (Section 2.3.3), not by infer_only (Section 2.9). With infer_only there is a typed definition at which every premise of lambda congruence holdswhitepaper/Fragment/IOWitness.lean:L444-L456444/-- **Lambda congruence with io-inferred bodies' types is unsound.** In a
445model of `ioEnv` (both definitions typed): the domains by `refl`, the
446bodies `g x n` and `n` by δ, β, β and `refl`, both bodies io-infer
447`Prop`, `Prop ≡ Prop` by `refl` — every premise of `DefEq.lam` with
448`InferIO` in place of `Infer`; the lambdas are well-denoted, not the point,
449and different. -/
450theorem lam_congr_io_unsound [LevelOracle] (φ : Name → Nat) :
451 DefEq ioEnv [] (.sort .zero) (.sort .zero) ∧
452 DefEq ioEnv [.sort .zero] gxn (.const nName []) ∧
453 InferIO ioEnv [.sort .zero] gxn (.sort .zero) ∧
454 InferIO ioEnv [.sort .zero] (.const nName []) (.sort .zero) ∧
455 DefEq ioEnv [.sort .zero] (.sort .zero) (.sort .zero) ∧
456 ¬ DefEqSem (ioModel (V := V)) φ [] lamG lamConst := by
while the two lambdas differ. Every premise is a derivation of the relations, so the rule itself is unsound, whatever claim infer_only makes.
2.9. The cheaper mode of inference
The official kernel infers the type of a term it has already checked by a cheaper mode, infer_only. The fragment has that modewhitepaper/Fragment/IOGrade.lean:L60-L9560/-- **The fast-inference grade** (Lean's `infer_only`): the rules of
61`Infer` without the application's argument check and without the lambda's
62domain check. `∀`-formation still infers the sorts, which its result
63needs. -/
64inductive InferIO [LevelOracle] (env : Env) : List Expr → Expr → Expr → Prop where
65 /-- A bound variable: its context entry. -/
66 | bvar {Γ : List Expr} {i : Nat} {A : Expr} :
67 Γ[i]? = some A → InferIO env Γ (bvar i) (A.liftN (i + 1))
68 /-- `Sort u : Sort (u + 1)`. -/
69 | sort {Γ : List Expr} {u : Level} : InferIO env Γ (sort u) (sort (.succ u))
70 /-- A stored constant: its declared type. -/
71 | const {Γ : List Expr} {c : Name} {ls : List Level} {ci : ConstInfo} :
72 env.find? c = some ci → ls.length = ci.lparams.length →
73 InferIO env Γ (const c ls) (ci.type.instL ci.lparams ls)
74 /-- `∀`-formation: the sorts of the domain and the body. -/
75 | pi {Γ : List Expr} {A B s t : Expr} {u v : Level} :
76 InferIO env Γ A s → Red env Γ s (sort u) →
77 InferIO env (A :: Γ) B t → Red env (A :: Γ) t (sort v) →
78 InferIO env Γ (pi A B) (sort (.imax u v))
79 /-- Lambda: the `∀` of the body's type; the domain is not checked. -/
80 | lam {Γ : List Expr} {A b bt : Expr} :
81 InferIO env (A :: Γ) b bt → InferIO env Γ (lam A b) (pi A bt)
82 /-- **Application without a certificate**: the head's type reduces
83 to a `∀`, and the result is its body at the argument. The
84 argument's type is not inferred. -/
85 | app {Γ : List Expr} {f a tf A B : Expr} :
86 InferIO env Γ f tf → Red env Γ tf (pi A B) →
87 InferIO env Γ (app f a) (B.inst a)
88 /-- **`let`, by substitution**:
89 as official `infer_only`, the value is not checked
90 against the type — the full grade (`Infer.letE`) checks it — and the
91 body with the value substituted is inferred at the io grade. The
92 soundness proof needs no check on the value (`InferIO.letE_sound`). -/
93 | letE {Γ : List Expr} {T v b B : Expr} :
94 InferIO env Γ (b.inst v) B →
95 InferIO env Γ (letE T v b) B
: the rules of Section 2.3.3 without app’s comparison of the argument’s type with the domain, without the lambda’s check of its domain, and without the check of a let-binding’s value against its type.
Its result is not always a type of the term: gets the type Nat, is well-denoted, and denotes the point (the lambda is an abstraction over an empty domain), which is no member of the set Nat denotes. What holds is a hereditary claimwhitepaper/Fragment/IOGrade.lean:L148-L172148/-- **Membership with the type's invariant.** -/
149def IOMem (T : Expr) (ρ : Nat → V) (v : V) : Prop :=
150 WellDenoted M φ ρ T ∧ IsTy (interp M φ ρ T) ∧ v ∈ˢ interp M φ ρ T
151
152/-- **The hereditary part**, by recursion on the type's syntax: at a
153`∀`, a graph over its domain that is not everywhere the point, whose
154applications satisfy the claim at the body. Nothing at any other
155type. -/
156def IOHered : Expr → (Nat → V) → V → Prop
157 | .pi A B, ρ, v =>
158 WellDenoted M φ ρ A ∧ IsTy (interp M φ ρ A) ∧
159 (∃ B' : V → V, v ∈ˢ piSet (interp M φ ρ A) B') ∧
160 (∃ x, x ∈ˢ interp M φ ρ A ∧ app v x ≠ pt) ∧
161 ∀ x, x ∈ˢ interp M φ ρ A →
162 app v x = pt ∨ IOMem M φ B (cons x ρ) (app v x) ∨ IOHered B (cons x ρ) (app v x)
163 | .bvar _, _, _ => False
164 | .sort _, _, _ => False
165 | .const _ _, _, _ => False
166 | .app _ _, _, _ => False
167 | .lam _ _, _, _ => False
168 | .letE _ _ _, _, _ => False
169
170/-- **The io claim**: the point, a member, or hereditarily a member. -/
171def IORel (T : Expr) (ρ : Nat → V) (v : V) : Prop :=
172 v = pt ∨ IOMem M φ T ρ v ∨ IOHered M φ T ρ v
: the value is the point, or a member of the type, or, when the type is a , a graph over its domain whose applications satisfy the claim at the ‘s body. It is proved for every rulewhitepaper/Fragment/IOGrade.lean:L368-L369368theorem inferIO_sound : ∀ {Γ : List Expr} {e T : Expr},
369 InferIO env Γ e T → InferIOSem m φ Γ e T
of infer_only.
The claim serves two consumers. One is proof irrelevance with both types inferred by infer_only (the rule’s casewhitepaper/Fragment/IOGrade.lean:L425-L428425theorem proofIrrelIO_sound {Γ : List Expr} {a ta tta b : Expr} {u : Level}
426 (hta : InferIO env Γ a ta) (htta : InferIO env Γ ta tta) (hu : Red env Γ tta (.sort u))
427 (hu0 : LevelOracle.eq u .zero = true) :
428 DefEqSem m φ Γ a b := by
): a value whose type has sort Prop is the point, the hereditary case included, because the body of a of sort Prop has sort Prop. The other is against a term whose head is a variable or a constant, whose claim is never the hereditary onewhitepaper/Fragment/IOGrade.lean:L446-L449446theorem IORel_neutral : ∀ {Γ : List Expr} {e T : Expr},
447 InferIO env Γ e T → e.atomHead →
448 ∀ ρ, Sat m.M φ Γ ρ → WellDenoted m.M φ ρ e →
449 interp m.M φ ρ e = pt ∨ IOMem m.M φ T ρ (interp m.M φ ρ e)
(the rule’s casewhitepaper/Fragment/IOGrade.lean:L479-L482479theorem etaIO_sound {Γ : List Expr} {A₁ b₁ b tb A₂ B t₁ : Expr}
480 (htb : InferIO env Γ b tb) (hb : b.atomHead) (hr : Red env Γ tb (.pi A₂ B))
481 (hA : DefEqSem m φ Γ A₂ A₁)
482 (hbody : DefEqSem m φ (A₁ :: Γ) b₁ (.app (b.liftN 1) (.bvar 0)))
). The bodies’ types at lambda congruence and are not among them, as Section 2.8 showed, so the checker infers those by full inference and keeps the results of the two modes apart.
infer_only exactlyConLeche/Kernel/Core.lean:L1188-L11931188/-- **The io inference body** (tasks #161, #170, #346): official's1189
`infer_type_core` with `infer_only = true`. It differs from1190
`inferBody` at three clauses: an application's argument is not1191
checked against the domain, a λ's domain is not checked to be a type,1192
and a `let`'s type and value are not checked (the body is inferred at1193
the value). The computed types and the "function expected" rejection. Like it, the mode does not check a let-binding’s value against its type (full inference does, Section 2.3.3); the soundness proof needs no such check, since the value is substituted.
2.10. The argument in brief
Three points of this section run through the rest of the paper. The claims of reduction and equality are conditional: they say nothing about a term that denotes the point. The checks of the certification tax are exactly what a typing judgement would otherwise supply: a common type for the two sides of a comparison where nothing else gives one. And the proof uses no subject reduction, no confluence, no injectivity of Π-types and no unique typing; it needs only the laws of the set theory and the derivations of the checker’s three relations.
3. Adding definitions
§2 had no environment. This section adds one, together with the constants that refer to its entries, the reduction rule that unfolds a definition, and the model of the environment, from which the interpretation reads a constant. The environment grows one declaration at a time, and each step is checked by the relations of §2, extended by the rules below. We say what the checker checks at each step, what it stores, and how the model grows with the environment so that its laws keep holding. This section covers definitions; inductive types, the rule and the consistency corollary follow in Section 4.
The environment , fixed throughout like the valuation, is a list of the constantswhitepaper/Fragment/Env.lean:L109-L125109/-- **The environment**: the accepted constants, most recent first. A
110name is stored at most once; `find?` reads the first entry. -/
111structure Env where
112 /-- The stored constants. -/
113 consts : List (Name × ConstInfo)
114 deriving DecidableEq
115
116namespace Env
117
118/-- Look a name up. -/
119def find? (env : Env) (n : Name) : Option ConstInfo := env.consts.lookup n
120
121/-- The empty environment. -/
122def empty : Env := ⟨[]⟩
123
124/-- Store one more constant. -/
125def add (env : Env) (n : Name) (ci : ConstInfo) : Env := ⟨(n, ci) :: env.consts⟩
accepted so far, most recent firstConLeche/Kernel/Env.lean:L562562structure Env where
. A stored constantwhitepaper/Fragment/Env.lean:L63-L100 63/-- What kind of constant a name is stored as. -/
64inductive ConstKind where
65 /-- A definition: its value unfolds (δ). -/
66 | defn (value : Expr)
67 /-- An inductive type former with `numParams` parameters,
68 `numIndices` indices and these constructors — and the block's
69 specification it was generated from (`Spec.lean`), which a later
70 block nesting through this one reads back (con-leche stores the
71 block's shape with the former likewise, `IndCaps`). -/
72 | induct (numParams numIndices : Nat) (ctors : List Name) (spec : IndSpec)
73 /-- A constructor of the inductive `induct`, with `numParams`
74 parameters (the inductive's) and `nfields` fields. -/
75 | ctor (induct : Name) (numParams nfields : Nat)
76 /-- A recursor with the given telescope shape — `numParams`
77 parameters, `numMotives` motives, `numMinors` minor premises,
78 `numIndices` indices, then the major premise — and its rules. -/
79 | recursor (numParams numMotives numMinors numIndices : Nat) (rules : List RecRule)
80 deriving DecidableEq
81
82namespace ConstKind
83
84/-- The value a definition unfolds to. -/
85def value? : ConstKind → Option Expr
86 | defn v => some v
87 | _ => none
88
89end ConstKind
90
91/-- A stored constant: its level parameters, its type (closed, over
92those parameters) and its kind. -/
93structure ConstInfo where
94 /-- The level parameters. -/
95 lparams : List Name
96 /-- The declared type, a closed term over `lparams`. -/
97 type : Expr
98 /-- The kind. -/
99 kind : ConstKind
100 deriving DecidableEq
has its level parameters , its type (a closed term over ) and its kindConLeche/Kernel/Env.lean:L363-L377363inductive ConstantInfo where
364 | axiomInfo (val : ConstantVal)
365 | defnInfo (val : ConstantVal) (value : Expr) (hint : ReducibilityHint)
366 | thmInfo (val : ConstantVal)
367 /-- An inductive type former (whnf-stuck) with its capabilities. -/
368 | indInfo (val : ConstantVal) (caps : IndCaps)
369 /-- A basis constructor (whnf-stuck; the iota target). -/
370 | ctorInfo (val : ConstantVal) (numParams numFields : Nat)
371 /-- A basis recursor with its iota rules. Only the two sums the
372 firing path reads are stored: `majorIdx` (= numParams + numMotives +
373 numMinors + numIndices, the major premise's argument position) and
374 `rulePrefix` (= numParams + numMotives + numMinors, the length of the
375 argument prefix a rule's rhs is applied to). The individual counts
376 are consumed at install time only and are not stored. -/
377 | recInfo (val : ConstantVal) (majorIdx rulePrefix : Nat)
. We write for “ is stored with parameters and type ”, and when it is a definition with value . A definition carries a value. The other kinds (type former, constructor, recursor) are what an inductive block stores, and Section 4.1 introduces them. A name is stored at most once.
Constants. The grammar of Section 2.1 gains one form: a constant whitepaper/Fragment/Syntax.lean:L35-L3635 /-- A constant of the environment at a level instantiation. -/
36 | const (c : Name) (ls : List Level)
of the environment, used at a list of levels , one per level parameterConLeche/Kernel/Expr.lean:L315-L325315inductive Expr where
316 | bvar (i : Nat)
317 | fvar (idx : Nat) (type : Expr)
318 | sort (u : Level)
319 | const (n : Name) (us : List Level)
320 | app (f a : Expr)
321 | lam (type body : Expr)
322 | forallE (type body : Expr)
323 | letE (type value body : Expr)
324 | lit (l : Literal)
325 | proj (structName : Name) (idx : Nat) (e : Expr)
of its declaration.
Level instantiationwhitepaper/Fragment/Syntax.lean:L112-L121112/-- Instantiate the level parameters `ps` at the levels `ls`, in
113sorts and constants. -/
114def instL (ps : List Name) (ls : List Level) : Expr → Expr
115 | bvar i => bvar i
116 | sort u => sort (u.subst ps ls)
117 | const c us => const c (us.map (Level.subst ps ls))
118 | app f a => app (instL ps ls f) (instL ps ls a)
119 | lam A b => lam (instL ps ls A) (instL ps ls b)
120 | pi A B => pi (instL ps ls A) (instL ps ls B)
121 | letE T v b => letE (instL ps ls T) (instL ps ls v) (instL ps ls b)
, used when a constant declared with level parameters is taken at the levels , replaces the parameters in sorts and in the level lists of constants (as in con-lecheConLeche/Kernel/Level.lean:L227-L240227def Expr.instantiateLevelParams (ks : List Name) (us : List Level) : Expr → Expr
228 | .bvar i => .bvar i
229 | .fvar idx ty => .fvar idx (ty.instantiateLevelParams ks us)
230 | .sort u => .sort (Level.subst ks us u)
231 | .const n vs => .const n (vs.map (Level.subst ks us))
232 | .app f a => .app (f.instantiateLevelParams ks us) (a.instantiateLevelParams ks us)
233 | .lam ty body =>
234 .lam (ty.instantiateLevelParams ks us) (body.instantiateLevelParams ks us)
235 | .forallE ty body =>
236 .forallE (ty.instantiateLevelParams ks us) (body.instantiateLevelParams ks us)
237 | .letE ty val body => .letE (ty.instantiateLevelParams ks us)
238 (val.instantiateLevelParams ks us) (body.instantiateLevelParams ks us)
239 | .lit l => .lit l
240 | .proj s i e => .proj s i (e.instantiateLevelParams ks us)
).
The rules for constants. A constant has its declared typewhitepaper/Fragment/Rules.lean:L749-L753749 /-- A stored constant at the right number of levels has its
750 declared type, instantiated (`Infer.const`, `Rel.lean:559`). -/
751 | const {Γ : List Expr} {c : Name} {ls : List Level} {ci : ConstInfo} :
752 env.find? c = some ci → ls.length = ci.lparams.length →
753 Infer env Γ (const c ls) (ci.type.instL ci.lparams ls)
at the levels it is used at, and two constants of the same name are comparedwhitepaper/Fragment/Rules.lean:L563-L566563 /-- Two constants of the same name at oracle-equal levels
564 (`DefEq.const`, `Rel.lean:384`). -/
565 | const {Γ : List Expr} {c : Name} {ls ls' : List Level} :
566 Level.eqList ls ls' = true → DefEq env Γ (const c ls) (const c ls')
through the level oracle (in con-leche: inferenceConLeche/Rules/Rel.lean:L559-L565559 | const {g : Grade} {d : Nat} {n : Name} {us : List Level}
560 {ci : ConstantInfo} :
561 env.find? n = some ci → ci.isTowerEntry = false →
562 us.length = ci.toConstantVal.levelParams.length →
563 Infer env g d (.const n us)
564 (ci.toConstantVal.type.instantiateLevelParams
565 ci.toConstantVal.levelParams us)
and equalityConLeche/Rules/Rel.lean:L381-L386381 DefEq env d (.fvar i ty₁) (.fvar i ty₂)
382 /-- Two constants of the same name at equivalent levels
383 (`Kernel/Core.lean`). -/
384 | const {d : Nat} {n : Name} {us us' : List Level} :
385 Level.isEquivList us us' = some true →
386 DefEq env d (.const n us) (.const n us')
).
The rule. A definition unfolds to its valuewhitepaper/Fragment/Rules.lean:L88-L9488 /-- **δ** (`Red.delta`, `Rel.lean:140`): a definition unfolds to its
89 value, at the use's level instantiation. (With `appFn`, an applied
90 definition unfolds at its head.) -/
91 | delta {Γ : List Expr} {c : Name} {ls : List Level} {ci : ConstInfo} {v : Expr} :
92 env.find? c = some ci → ci.value? = some v →
93 ls.length = ci.lparams.length →
94 Red env Γ (const c ls) (v.instL ci.lparams ls)
at the levels it is used at (as in con-lecheConLeche/Rules/Rel.lean:L137-L141137 /-- **δ** (`whnfStep`, `Kernel/Core.lean`; also every unfolding of
138 `lazyDeltaStep`): one definition unfolded
139 at the head. A theorem never unfolds (`unfoldDefinition`). -/
140 | delta {d : Nat} {e e' : Expr} :
141 unfoldDefinition env e = some e' → Red env d e e'
). With the head rule of Section 2.3, an applied definition unfolds at its head.
What a definition must satisfy. Before the checker stores a definition with parameters , type and value , it checks four thingswhitepaper/Fragment/Decl.lean:L591-L605591/-- **A definition is accepted** (con-leche's `checkDefnVal`): the
592name is fresh, the declared type's type reduces to a sort, the
593value's inferred type is definitionally equal to the declared type,
594and both terms use only the declared level parameters. That is the
595one scope condition the typing derivations do not give (the sort rule
596accepts `Sort u` at any level `u`); closedness and stored constants
597they do give (`Infer.closedAt`, `Infer.consts`,
598`ScopeOfInfer.lean`), so the environment section recovers the full
599scope of both terms (`DefOk.type_scoped`, `DefOk.value_scoped`). -/
600def DefOk (env : Env) (c : Name) (ci : ConstInfo) : Prop :=
601 env.find? c = none ∧
602 (∃ s u, Infer env [] ci.type s ∧ Red env [] s (.sort u)) ∧
603 (∃ v T, ci.kind = .defn v ∧ Infer env [] v T ∧ DefEq env [] T ci.type ∧
604 v.lparamsIn ci.lparams = true) ∧
605 ci.type.lparamsIn ci.lparams = true
(in con-leche: the common checksConLeche/Kernel/CheckerBase.lean:L126-L144126def checkConstantVal (ops : CheckerOps m) (env : Env) (cv : ConstantVal) : m Unit := do
127 if (env.find? cv.name).isSome then
128 throw (.invalid s!"duplicate declaration {cv.name}")
129 if reservedBasisNames.contains cv.name then
130 throw (.invalid s!"reserved basis name {cv.name}")
131 if cv.name.isProjFnShape then
132 throw (.invalid s!"reserved projection name {cv.name}")
133 unless Name.nodup cv.levelParams do
134 throw (.invalid s!"duplicate universe parameters in {cv.name}")
135 unless cv.type.looseBVarsBounded 0 do
136 throw (.invalid s!"loose bound variable in type of {cv.name}")
137 if cv.type.hasFvar then
138 throw (.invalid s!"unexpected free variable in type of {cv.name}")
139 unless cv.type.allLevelParamsDefined cv.levelParams do
140 throw (.invalid s!"undeclared universe parameter in type of {cv.name}")
141 unless cv.type.constsResolve env do
142 throw (unresolvedConstsErrorL (strLitSupported env) s!"type of {cv.name}" cv.type)
143 let stype ← ops.inferType env 0 cv.type
144 let _u ← ops.ensureSort env 0 stype
and the value checkConLeche/Kernel/Checker.lean:L34-L4734def checkDefnVal (ops : CheckerOps m) (env : Env) (cv : ConstantVal)
35 (value : Expr) (hint : ReducibilityHint) : m Env := do
36 unless value.looseBVarsBounded 0 do
37 throw (.invalid s!"loose bound variable in value of {cv.name}")
38 if value.hasFvar then
39 throw (.invalid s!"unexpected free variable in value of {cv.name}")
40 unless value.allLevelParamsDefined cv.levelParams do
41 throw (.invalid s!"undeclared universe parameter in value of {cv.name}")
42 unless value.constsResolve env do
43 throw (unresolvedConstsErrorL (strLitSupported env) s!"value of {cv.name}" value)
44 let vtype ← ops.inferType env 0 value
45 unless ← ops.isDefEq env 0 vtype cv.type do
46 throw (.invalid s!"type mismatch in definition {cv.name}")
47 pure ⟨.defnInfo cv value hint :: env.consts⟩
):
- the name is fresh;
- the type has a sort: ;
- the value’s inferred type is definitionally equal to the declared type: and ;
- both terms use only the level parameterswhitepaper/Fragment/Decl.lean:L604-L605604
v.lparamsIn ci.lparams = true) ∧
605ci.type.lparamsIn ci.lparams = true
.
A stored term is thus closed, mentions only stored constants and uses only the declared level parameters. These three facts let the model read a stored term without looking at anything that is added later, because the interpretation of a term depends only on the constants and the free variables it mentions and the level parameters it uses (constantswhitepaper/Fragment/Hygiene.lean:L593-L595593theorem interp_consts {e : Expr} (h : ∀ c ∈ e.consts, ∀ ls, M c ls = M' c ls) {ρ : Nat → V} :
594 interp M φ ρ e = interp M' φ ρ e := by
595 induction e generalizing ρ with
, variableswhitepaper/Fragment/Hygiene.lean:L466-L468466theorem interp_closedAt {k : Nat} {e : Expr} (he : e.closedAt k = true) {ρ ρ' : Nat → V}
467 (h : ∀ i, i < k → ρ i = ρ' i) : interp M φ ρ e = interp M φ ρ' e := by
468 induction e generalizing k ρ ρ' with
, parameterswhitepaper/Fragment/Hygiene.lean:L656-L658656theorem interp_lparams {ps : List Name} {e : Expr} (he : e.lparamsIn ps = true)
657 (h : ∀ n ∈ ps, φ n = φ' n) {ρ : Nat → V} : interp M φ ρ e = interp M φ' ρ e := by
658 induction e generalizing ρ with
).
The interpretation, extended. The interpretation of Section 2.5 gains one parameter, an assignment of a set to every constant at every list of concrete levels, and one clause: a constant denotes what the assignment sayswhitepaper/Fragment/Interp.lean:L142142 | _, .const c ls => M c (ls.map (Level.eval φ))
,
with the list of the levels’ values. It stays term-directed. is a parameter like , consulted at a constant the way is at a variable. The environment itself, with its stored types and values, is never read, and no derivation and no type is needed. A fourth lemma joins those of Section 2.5, by the same induction: instantiating level parameterswhitepaper/Fragment/Interp.lean:L256-L258256theorem interp_instL (ps : List Name) (ls : List Level) :
257 ∀ (e : Expr) (ρ : Nat → V),
258 interp M φ ρ (e.instL ps ls) = interp M (Level.substVal φ ps ls) ρ e := by
is changing the valuation. And the semantic invariant gains the clause that a constant, like a variable or a sort, is always well-denoted.
The contract. What the soundness theorem, extended below, assumes of the environment is a modelwhitepaper/Fragment/EnvModel.lean:L298-L362298/-- **A model of an environment**: the assignment and the laws. -/
299structure EnvModel (V : Type u) [SetLib V] (env : Env) where
300 /-- The set of every constant at every list of concrete levels. -/
301 M : Name → List Nat → V
302 /-- Every stored constant's type, instantiated, is well-denoted and a
303 type, and the constant is a member of it — at every valuation and every
304 environment (the types are closed, so `ρ` is irrelevant, and stating
305 it for all `ρ` saves a closedness lemma). -/
306 type_ok : ∀ (c : Name) (ci : ConstInfo), env.find? c = some ci →
307 ∀ (φ : Name → Nat) (ρ : Nat → V) (ls : List Level), ls.length = ci.lparams.length →
308 WellDenoted M φ ρ (ci.type.instL ci.lparams ls) ∧
309 IsTy (interp M φ ρ (ci.type.instL ci.lparams ls)) ∧
310 M c (ls.map (Level.eval φ)) ∈ˢ interp M φ ρ (ci.type.instL ci.lparams ls)
311 /-- A definition's value, instantiated, is well-denoted and denotes
312 the constant. -/
313 unfold : ∀ (c : Name) (ci : ConstInfo) (v : Expr), env.find? c = some ci →
314 ci.value? = some v →
315 ∀ (φ : Name → Nat) (ρ : Nat → V) (ls : List Level), ls.length = ci.lparams.length →
316 WellDenoted M φ ρ (v.instL ci.lparams ls) ∧
317 interp M φ ρ (v.instL ci.lparams ls) = M c (ls.map (Level.eval φ))
318 /-- Every plain rule of every stored recursor satisfies its ι law. -/
319 rec_rules : ∀ (c : Name) (ci : ConstInfo) (numParams numMotives numMinors numIndices : Nat)
320 (rules : List RecRule),
321 env.find? c = some ci →
322 ci.kind = .recursor numParams numMotives numMinors numIndices rules →
323 ∀ rl ∈ rules, rl.inst = none → ∀ cij : ConstInfo, env.find? rl.ctor = some cij →
324 RecRuleLaw M c ci numParams (numParams + numMotives + numMinors)
325 (numParams + numMotives + numMinors + numIndices) rl cij
326 /-- Every nested rule of every stored recursor satisfies its ι law,
327 at its constructor's own parameter count. -/
328 rec_rules_nested : ∀ (c : Name) (ci : ConstInfo) (numParams numMotives numMinors numIndices : Nat)
329 (rules : List RecRule),
330 env.find? c = some ci →
331 ci.kind = .recursor numParams numMotives numMinors numIndices rules →
332 ∀ rl ∈ rules, ∀ (lvs : List Level) (pinst : List Expr), rl.inst = some (lvs, pinst) →
333 ∀ (cij : ConstInfo) (I : Name) (nPc nf : Nat), env.find? rl.ctor = some cij →
334 cij.kind = .ctor I nPc nf →
335 RecRuleLawN M c ci nPc (numParams + numMotives + numMinors)
336 (numParams + numMotives + numMinors + numIndices) rl cij
337 /-- Every stored inductive family's fibres are truth values where its
… 25 more lines: an assignment of a set to every constant and every list of natural numbers (the values of its level parameters), satisfying two laws (four more, for inductive types, join in Section 4). Throughout, ranges over level lists with , over valuations and over variable environments, and is the semantic invariant of Section 2.6.
Definition 4 (Model of an environment).
Typingwhitepaper/Fragment/EnvModel.lean:L302-L310302
/-- Every stored constant's type, instantiated, is well-denoted and a
303type, and the constant is a member of it — at every valuation and every
304environment (the types are closed, so `ρ` is irrelevant, and stating
305it for all `ρ` saves a closedness lemma). -/
306type_ok : ∀ (c : Name) (ci : ConstInfo), env.find? c = some ci →
307∀ (φ : Name → Nat) (ρ : Nat → V) (ls : List Level), ls.length = ci.lparams.length →
308WellDenoted M φ ρ (ci.type.instL ci.lparams ls) ∧
309IsTy (interp M φ ρ (ci.type.instL ci.lparams ls)) ∧
310M c (ls.map (Level.eval φ)) ∈ˢ interp M φ ρ (ci.type.instL ci.lparams ls)
: for every ,Unfoldingwhitepaper/Fragment/EnvModel.lean:L311-L317311
/-- A definition's value, instantiated, is well-denoted and denotes
312the constant. -/
313unfold : ∀ (c : Name) (ci : ConstInfo) (v : Expr), env.find? c = some ci →
314ci.value? = some v →
315∀ (φ : Name → Nat) (ρ : Nat → V) (ls : List Level), ls.length = ci.lparams.length →
316WellDenoted M φ ρ (v.instL ci.lparams ls) ∧
317interp M φ ρ (v.instL ci.lparams ls) = M c (ls.map (Level.eval φ))
: for every ,
In words: every stored constant is a member of its type, which is a type in the sense of Section 2.5, and a definition’s value denotes the constant, at every instantiation of the declared level parameters.
structure EnvModelM (μ : CheckMode) (env : Env) where230
/-- the environment carrier, contained -/231
base2 : EnvModel V env232
/-- every stored type reads, at every ground assignment -/233
type_reads : ∀ c ∈ env.consts, ∀ ψ : Name → Nat,234
∃ ta : Term,235
denote base2.acval env ψ 0 c.toConstantVal.type = some ta236
/-- the stored types' readings are graded types -/237
type_wellDenotedTy : ∀ c ∈ env.consts, ∀ (ψ : Name → Nat) (ta : Term),238
denote base2.acval env ψ 0 c.toConstantVal.type = some ta →239
∀ ρ : Nat → V, WellDenotedTy V ρ ta240
/-- stored constants inhabit their types' readings (a projection241
table's constant type is the closed dummy `Sort 1` and its leaf is242
`Sort 0`, so the row holds of tables too; that a table is not a term243
is `inferTypeCore`'s own rejection of a `.const` naming one) -/244
mem_type : ∀ c ∈ env.consts,245
∀ (ψ : Name → Nat) (ta : Term),246
denote base2.acval env ψ 0 c.toConstantVal.type = some ta →247
∀ ρ : Nat → V,248
interp V ρ (base2.acval c.name ψ) ∈ˢ interp V ρ ta249
/-- stored definition and theorem values read, to the constant's own250
leaf (existence) -/251
defn_reads : AcvalDefnInst base2252
/-- the two `Nat`-literal head facts, at every assignment -/).
The empty environment has a model triviallywhitepaper/Fragment/EnvModel.lean:L364-L373364/-- The empty environment has a model: any assignment, vacuous laws. -/
365def EnvModel.empty (M : Name → List Nat → V) : EnvModel V Env.empty where
366 M := M
367 type_ok := fun _ _ h => by simp at h
368 unfold := fun _ _ _ h => by simp at h
369 rec_rules := fun _ _ _ _ _ _ _ h => by simp at h
370 rec_rules_nested := fun _ _ _ _ _ _ _ h => by simp at h
371 ind_fib := fun _ _ _ _ _ _ h => by simp at h
372 rec_rules_k := fun _ _ _ _ _ _ _ h => by simp at h
373 rec_rules_slot := fun _ _ _ _ _ _ _ h => by simp at h
: any assignment, and laws with nothing to say.
Soundness, extended. Theorem 2 holds for the relations extended by the three rules above, with one more hypothesis: fix a model of the environment, a valuation , and let satisfy ; then the three claims hold as statedwhitepaper/Fragment/Sound.lean:L2322-L23262322theorem soundness [LevelOracle] (m : EnvModel V env) (φ : Name → Nat) :
2323 (∀ {Γ : List Expr} {e e' : Expr}, Red env Γ e e' → RedSem m φ Γ e e') ∧
2324 (∀ {Γ : List Expr} {a b : Expr}, DefEq env Γ a b → DefEqSem m φ Γ a b) ∧
2325 (∀ {Γ : List Expr} {e T : Expr}, Infer env Γ e T → InferSem m φ Γ e T) :=
2326 ⟨red_sound, defeq_sound, infer_sound⟩
, and Corollary 3 holds under every model. The induction of Section 2.7 gains three cases, one per rule.
Proof. Rule deltawhitepaper/Fragment/Sound.lean:L144-L150144theorem delta_sound {Γ : List Expr} {c : Name} {ls : List Level} {ci : ConstInfo} {v : Expr}
145 (hfind : env.find? c = some ci) (hv : ci.value? = some v)
146 (hls : ls.length = ci.lparams.length) :
147 RedSem m φ Γ (.const c ls) (v.instL ci.lparams ls) := by
148 intro ρ _ _ _
149 obtain ⟨hw, he⟩ := m.unfold c ci v hfind hv φ ρ ls hls
150 exact ⟨hw, by rw [interp_const, he]⟩
, for a definition with parameters and value , at levels:
Law 2 says the instantiated value is well-denoted and denotes , which is what the constant denotesConLeche/Model/Rules/RedSound.lean:L235-L236235theorem Red.delta_soundU (hin : RulesInputs V m φ) {d : Nat} {e e' : Expr}
236 (h : ConLeche.unfoldDefinition env e = some e') : RedSemU m φ d e e' := by
. The redex’s semantic invariant is not used. Theorem 5 shows the law holds when a definition is added.
Rule const, equalitywhitepaper/Fragment/Sound.lean:L177-L179177theorem const_sound [LevelOracle] {Γ : List Expr} {c : Name} {ls ls' : List Level}
178 (h : Level.eqList ls ls' = true) : DefEqSem m φ Γ (.const c ls) (.const c ls') :=
179 fun _ _ _ _ _ _ => by simp [(Level.eqList_iff ls ls').mp h φ]
, when pointwise:
The oracle answers yes only if the levels agree at every valuation (Section 2.2), so the two constants read the same entry of ConLeche/Model/Rules/DefEqSound.lean:L69-L7169theorem DefEq.const_soundU {d : Nat} {n : Name} {us us' : List Level}
70 (h : Level.isEquivList us us' = some true) :
71 DefEqSemU m φ d (.const n us) (.const n us') := by
.
Rule const, inferencewhitepaper/Fragment/Sound.lean:L276-L281276theorem const_sound {Γ : List Expr} {c : Name} {ls : List Level} {ci : ConstInfo}
277 (hfind : env.find? c = some ci) (hls : ls.length = ci.lparams.length) :
278 InferSem m φ Γ (.const c ls) (ci.type.instL ci.lparams ls) := by
279 intro ρ _
280 obtain ⟨hw, hT, hmem⟩ := m.type_ok c ci hfind φ ρ ls hls
281 exact ⟨trivial, hw, hT, hmem⟩
, :
The constant is well-denoted, and law 1 says its instantiated type is well-denoted, a type, and contains the constant’s setConLeche/Model/Rules/InferSound.lean:L210-L215210theorem Infer.const_sound (hin : RulesInputs V m φ) {g : Grade} {d : Nat}
211 {n : Name} {us : List Level} {ci : ConLeche.ConstantInfo}
212 (hf : env.find? n = some ci) (htower : ci.isTowerEntry = false)
213 (hus : us.length = ci.toConstantVal.levelParams.length) :
214 InferSem m φ g d (.const n us)
215 (ci.toConstantVal.type.instantiateLevelParams ci.toConstantVal.levelParams us) := by
. □
/-- **Installing a definition preserves having a model.** The new346
constant's set is its value's (`defVal`); every stored constant keeps347
its set. -/348
theorem install_def {env : Env} {c : Name} {ci : ConstInfo} (hs : Env.Scoped env)349
(m : EnvModel V env) (hok : DefOk env c ci) :350
∃ m' : EnvModel V (env.add c ci), ∀ n, (env.find? n).isSome → ∀ ls, m'.M n ls = m.M n ls := by(as in con-lecheConLeche/Model/Install.lean:L294-L297294
theorem declStep_preserves_of_cons (mp : EnvModelM V μ env)295
{c₀ : ConstantInfo} {A : (Name → Nat) → Term}296
(hfresh : env.find? c₀.name = none)297
(hh : ConsHead env c₀ A)).
159
/-- A model's laws survive changing the assignment away from the160
stored names: every stored term mentions only stored constants. -/161
def EnvModel.transport {env : Env} (m : EnvModel V env) (hs : Env.Scoped env). For the new constant we use Corollary 3 and the second claim of Theorem 2. From the value and are well-denoted and , a type; from the type is well-denoted and a type; two types are never the point, so the second claim applies to and gives . This holds at every valuation, and instantiating the level parameters is the same as changing the valuation (above), so it holds at every . Finally, and are closed and mention no constant but old ones, because the derivations are in the empty context, where no rule types a variablewhitepaper/Fragment/ScopeOfInfer.lean:L37-L3837
theorem Infer.closedAt : ∀ {Γ : List Expr} {e T : Expr}, Infer env Γ e T →38
e.closedAt Γ.length = trueand the constant rule requires the constant storedwhitepaper/Fragment/ScopeOfInfer.lean:L60-L6160
theorem Infer.consts : ∀ {Γ : List Expr} {e T : Expr}, Infer env Γ e T →61
∀ c ∈ e.consts, (env.find? c).isSome. So the two sets are the same under as under . That is law 1, and law 2 is the definition of at . □
The proof is short because §2 did the work. The checks a definition passes are the premises of the corollary, and the corollary’s conclusion is law 1.
4. Adding inductive types
The second kind of declaration is an inductive block: a type former with its constructors and its recursor. It is checked and installed by the same shape of argument as a definition (Section 3), and its reduction rule reads the environment as does. Two pieces of work are new. The constructed sets (the family, the constructors, the recursor) must be members of their generated types, which is law 1. And the law must be stated and proved: it is a third law of the contract of Section 3, and three more laws let most steps go without checking the constructor application. This section says what the checker checks for a block and what it stores, how the model grows by a least fixed point so that the laws keep holding, when needs a certificate, and then the consistency corollary.
4.1. What is checked
An inductive block declares an indexed family by its constructors: one type, or one proposition, per choice of indices. At a valuation the family is type-valued or proposition-valued, as its result sort evaluates to a positive number or to . These are the two interpretations of a binder from §2, decided for a whole family at once. The fragment takes a block with one type former, neither mutual nor nested; nested blocks are the subject of Section 5. The block is given by a specificationwhitepaper/Fragment/Spec.lean:L80-L103 80/-- **The data of a single inductive block** without its class
81(con-leche's `MemberShape`/`InductiveShape`, `BlockParts.lean:62`,
82`SumParts.lean:36`). -/
83structure IndBase where
84 /-- The type former's name. -/
85 name : Name
86 /-- The block's level parameters. -/
87 lparams : List Name
88 /-- The parameter telescope (a context, innermost first). -/
89 params : List Expr
90 /-- The index telescope (a context under the parameters). -/
91 indices : List Expr
92 /-- The result sort `Sort u`. -/
93 sort : Level
94 /-- The constructors, in order. -/
95 ctors : List CtorSpec
96 /-- The recursor's name. -/
97 recName : Name
98 /-- Large elimination: the recursor eliminates into `Sort ℓ` for a
99 fresh level parameter `ℓ`; otherwise into `Prop`. -/
100 large : Bool
101 /-- The fresh elimination level parameter (read only if `large`). -/
102 elim : Name
103 deriving DecidableEq
(as in con-lecheConLeche/Kernel/Inductives/BlockParts.lean:L104-L122104structure BlockShape where
105 /-- the members in block order -/
106 members : List MemberShape
107 /-- the block's recursors, in the order the stream exports them (any
108 number, assigned to their members by their majors) -/
109 recs : List RecShape
110 /-- the shared parameter count -/
111 nP : Nat
112 /-- the recursors' fresh elimination level parameter (`large` only;
113 `.anonymous` for a small eliminator) -/
114 elim : Name
115 /-- the result sort (member 0's; the others are `Level.isEquiv` to it) -/
116 resSort : Level
117 /-- large eliminator (a fresh elimination level parameter in front) -/
118 large : Bool
119 /-- the result sort is provably `Prop` -/
120 isProp : Bool
121 deriving Repr, Inhabited
122
):
- a name and level parameters ;
- a parameter telescope and, under it, an index telescope ; the parameters are the same for every constructor and every field, the indices vary;
a result sort : the family has type
- constructors , each with a list of fields and, under the parameters and the fields, the index expressions of its result .
A field is one of two kindswhitepaper/Fragment/Spec.lean:L28-L4228/-- A constructor field, as the positivity check classifies it
29(con-leche's `RecFieldKind`, `FieldTele.lean:29`). Its domain is an
30expression under the parameters and the earlier fields. -/
31inductive Field where
32 /-- An ordinary field: any domain that does not mention the block. -/
33 | ordinary (A : Expr)
34 /-- A reflexive field: `∀ tele, I params es` — a function space into
35 the family at the block's parameters and the index expressions `es`;
36 `tele` is a context (innermost first) under the parameters and the
37 earlier fields, `es` a spine (outermost first) under `tele` too.
38 With an empty telescope the field is simply **recursive**: the
39 family itself (`reflexive [] es`) — one kind covers both, as the
40 generated terms and the model treat them alike. (Con-leche keeps
41 them apart: `RecFieldKind.recursive`/`.reflexive`, `FieldTele.lean`.) -/
42 | reflexive (tele : List Expr) (es : List Expr)
(as in con-lecheConLeche/Kernel/Inductives/Positivity.lean:L598-L608598/-- The field's kind as the run found it: hole-free, a member hole
599(finitary or under `Π` binders), an instantiation IN PROGRESS (a `Y`
600hole — only inside a container's constructors), or an accepted
601instantiation (under `Π` binders when `refl`). -/
602inductive NestFieldKind where
603 | ordinary
604 | recursive (tgt : Nat)
605 | reflexive (tgt : Nat)
606 | inProgress
607 | nested (refl : Bool)
608 deriving DecidableEq, Inhabited
):
- ordinary: its domain does not mention ;
reflexive: its domain is
a function, under a telescope of binders whose domains do not mention , into the family being defined at the block’s own parameters and some index expressions. The telescope may be empty; then the domain is , a member of the family itself, and the field is simply recursive.
Two conditions of shape come with the kinds. No field’s domain mentions anywhere else; this is strict positivitywhitepaper/Fragment/Decl.lean:L628-L642628/-- The scope of a field's pieces: with `k` earlier fields, an
629ordinary domain is in scope under the parameters and them; a
630reflexive field's telescope entries under the earlier telescope
631entries as well, and its index expressions under the whole telescope
632(under the parameters and the earlier fields alone when the telescope
633is empty); a reflexive field's index expressions are as many as the
634indices. No piece
635mentions the block (it is not stored in `env`): that is **strict
636positivity** in the shape the fragment admits (con-leche's
637`nestPos`, `ConLeche/Kernel/Inductives/Positivity.lean:1453`). -/
638def fieldScoped (env : Env) (k : Nat) : Field → Prop
639 | .ordinary A => Expr.Scoped env S.lparams (S.nP + k) A
640 | .reflexive tele es =>
641 (∀ t T, tele[t]? = some T → Expr.Scoped env S.lparams (S.nP + k + (tele.length - 1 - t)) T) ∧
642 es.length = S.nI ∧ (∀ e ∈ es, Expr.Scoped env S.lparams (S.nP + k + tele.length) e)
. And nothing after a reflexive field may depend on its valuewhitepaper/Fragment/Decl.lean:L645-L650645/-- **No field reads an earlier recursive field**: an ordinary domain
646and a reflexive field's telescope entries and index expressions do not
647use the variable of any earlier reflexive field (con-leche's `structUsedLater`
648guard, `StructParts.lean:393`, run by `nestCtors`, `Positivity.lean:1247`:
649the model reads the domains at a frame with arbitrary recursive slots). -/
650def fieldNoRecDep (earlier : List Field) : Field → Prop
(as in con-lecheConLeche/Kernel/Inductives/Positivity.lean:L1246-L12491246 if (List.range nF).any (fun i => ks.getD i .ordinary != .ordinary &&
1247 structUsedLater (closeTelescope nds hi cur) 0 i) then
1248 throw (.invalid "nested positivity: non valid occurrence of the datatypes being \
1249 declared (a later field or the result depends on a recursive or nested field)")
and the official kernel), so that the model can read a constructor’s domains without knowing the values of its reflexive fields.
OVERVIEW.md); this section describes that check on a block of the fragment’s shape. Its positivity check classifies each field’s domain after weak head normal form, with at the block’s parameters replaced by a variable standing for the family being defined; Section 5 uses the same check (positivityConLeche/Kernel/Inductives/Positivity.lean:L1453-L14541453def nestPos (ops : CheckerOps m) (env : Env) (ctx : NestCtx) :1454
Nat → List NestHole → Nat → Nat → Expr → NestState → m (NestFieldKind × Expr × NestState)).
Reflexive fields with a non-empty telescope matter to the model. A tree type with a constructor has nodes with countably many children, so a node may have children of every finite depth, and the set of all trees is larger than the union of the sets of trees of depth . Iterating the constructors times, which reaches every member of , does not reach every tree, and a model must obtain the least fixed point some other way (Section 4.2).
4.1.1. The generated declarations
From the specification the checker generates the types of the type former, the constructors and the recursor, and the recursor’s rules. The type former’s type is the one displayed above; a constructor’s type is
(the two generatorswhitepaper/Fragment/Decl.lean:L209-L216209/-- **The type former's type** `∀ params indices, Sort u`; no binder is
210a proposition. -/
211def indType : Expr := mkPis (S.indices ++ S.params) (.sort S.sort)
212
213/-- **A constructor's type** `∀ params fields, I params idx`; every
214binder is a proposition exactly when the family is. -/
215def ctorType (c : CtorSpec) : Expr :=
216 mkPis (S.fieldCtx c.fields ++ S.params) (S.famAt c.fields.length c.idx)
). The recursor eliminates into , where the elimination level is either a fresh level parameter (large elimination) or (small elimination); the elimination rule below says which.
/-- Stage 2, one constructor's type: the ordinary constant check (the103
constructor is stored AS DECLARED), the result). The recursor it generates, as the official kernel does. From the stream’s recursor record it takes only the name, the level parameters and the type, which must be definitionally equal to the generated one, and the record’s two layout counts, which must equal the generated ones (the comparisonConLeche/Kernel/Inductives/GenRec.lean:L387-L401387
def classRecTyOk (ops : CheckerOps m) (fe : FEnv) (g : ClassGen) (k : Nat) (rc : RecShape)388
(cvRi : ConstantVal) (c : Nat) : m ConstantVal := do389
let ci := g.cls.getD c default390
unless rc.tgt == ci.member.getD k do391
throw (.invalid "generated recursor: the recursor record's member is not its class's")392
unless rc.rP == g.nP + g.slots.length && rc.mI == rc.rP + ci.nIdx do393
throw (.invalid "generated recursor: the recursor record's rule prefix or major index is \394
not the generated one")395
let gty ← unwrapOr (classGenRecTy g c) (.internal "generated recursor: recursor type")396
let cvG := { rc.cvR with type := gty }397
classConstOk ops fe cvG398
unless ← ops.isDefEq fe.env 0 cvRi.type cvG.type do399
throw (.invalid s!"generated recursor: the type of {rc.cvR.name} is not the generated one \400
(official: invalid recursor)")401
pure cvG). An unverified pre-pass reads off that type which type the recursor eliminates and how its motive and minor premises are laid out (the arguments named on in Example 6); a wrong reading can only make the comparison fail (the pre-passConLeche/Kernel/Inductives/ClassRead.lean:L24-L3224
This file READS that data off the recursor types, syntactically, and25
checks nothing. Everything it returns is re-established by the stage:26
every class is checked like a major (`targetMajorOf`), the inductive27
hypotheses read here must be exactly the positivity check's recursive28
fields, and every recursor TYPE is generated from the classes and the29
positivity check's recorded constructors and compared by `isDefEq` with30
the stream's. What the stage installs is the generated family, never31
anything read here: a wrong reading can only make the generated type32
differ from the stream's, i.e. reject.). The record’s rules are never read; the generated recursor and its rules are what is stored (the recursor stageConLeche/Kernel/Inductives/GenRec.lean:L539-L542539
def genRecCheck (so : ShadowOps m) (fe : FEnv) (p : BlockShape) (nestedBit : Bool)540
(params : List Expr) (tbl : List NestCtorNf) (rd : ClassRead) (Ms : List TargetMajor)541
(cvTas : List ConstantVal) (block : List ConstantInfo) :542
m (List (ConstantVal × TargetMajor × List Expr)) := do).
Here is the recursor of the natural numbers.
Example 6 (). The block has no parameters, no indices, sort , and two constructors: with no field and with one recursive field (reflexive with the empty telescope). The generated constructor types are and . The recursor eliminates into for a fresh level parameter (large elimination, see below), and its generated type is
is the motive; and are the minor premises, one per constructor, each taking the constructor’s fields and, for every recursive field, an inductive hypothesis , the motive at that field; is the major premise. The recursor has two rules, one per constructor: the major premise built by the constructor reduces to the minor premise applied to the fields and to the recursor’s own value at each recursive field,
What the checker stores as the second rule’s right-hand side is the closed term (the binders’ domains omitted)
the rule applies it to the recursor’s arguments and the constructor’s fields, and does the rest.
The general shape is the same with parameters, indices and telescopes added. The motive takes the indices and a member of the family:
A minor premise for takes the fields, then one inductive hypothesis per reflexive field. At a field
the hypothesis is
which at the empty telescope is the of Example 6. The minor premise ends in
The recursor takes the parameters, the motive, the minors, the indices and the major, and ends in . (Generated: the motivewhitepaper/Fragment/Decl.lean:L218-L220218/-- The motive's type, under the parameters:
219`∀ indices (t : I params indices), Sort ℓ`. -/
220def motiveTy : Expr := mkPis (S.famVars 0 :: S.indices) (.sort S.ℓ)
, an inductive hypothesiswhitepaper/Fragment/Decl.lean:L231-L243231/-- **An inductive hypothesis' type** for the field with `k` earlier
232fields, `l` earlier hypotheses, `o` binders between the parameters
233and the fields (the motive sits `nF + l + o - 1` binders above):
234`∀ tele, motive es (f tele)` at a reflexive field (`motive es f` when
235the telescope is empty). -/
236def ihTy (nF k l o : Nat) : Field → Expr
237 | .ordinary _ => .sort .zero
238 | .reflexive tele es =>
239 let m := tele.length
240 mkPis (liftCtx (fun t T => atCtx nF k l o t T) tele)
241 (mkAppN (.bvar (nF + l + o - 1 + m))
242 (es.map (atCtx nF k l o m) ++ [mkAppN (.bvar (nF - 1 - k + l + m)) (varsAt 0 m)]))
243 | .container => mkAppN (.bvar (nF + l + o - 2)) [.bvar (nF - 1 - k + l)]
, a minor premisewhitepaper/Fragment/Decl.lean:L256-L265256/-- **A minor premise** for constructor `j`, under the parameters, the
257motive and the `j` earlier minors:
258`∀ fields ihs, motive idx (C params fields)`. -/
259def minorTy (c : CtorSpec) (j : Nat) : Expr :=
260 let nF := c.fields.length
261 let nIh := c.recFields.length
262 mkPis (IndSpec.ihCtx c j ++ S.fieldCtxAt c (j + 1))
263 (mkAppN (.bvar (nIh + nF + j))
264 (c.idx.map (atCtx nF nF nIh (j + 1) 0) ++
265 [mkAppN (.const c.name S.lvls) (varsAt (nIh + nF + j + 1) S.nP ++ varsAt nIh nF)]))
, the recursor’s typewhitepaper/Fragment/Decl.lean:L272-L277272/-- **The recursor's type**
273`∀ params (motive : motiveTy) minors indices (t : I params indices), motive indices t`
274(con-leche's `classGenRecTy`, `GenRec.lean:181`). -/
275def recType : Expr :=
276 mkPis (S.famVars (S.n + 1) :: S.indicesAt (S.n + 1) ++ S.minorsCtx ++ [S.motiveTy] ++ S.params)
277 (mkAppN (.bvar (1 + S.nI + S.n)) (varsAt 1 S.nI ++ [.bvar 0]))
and a rule’s right-hand sidewhitepaper/Fragment/Decl.lean:L314-L321314/-- **A rule's right-hand side** for constructor `j`:
315`fun params motive minors fields => minor_j fields ihs`
316(con-leche's `classGenRule`, `GenRec.lean:193`). -/
317def ruleRhs (c : CtorSpec) (j : Nat) : Expr :=
318 let nF := c.fields.length
319 mkLams (S.ruleCtx c)
320 (mkAppN (.bvar (nF + S.n - 1 - j))
321 (varsAt 0 nF ++ c.recFields.map fun kf => S.ihVal nF kf.1 kf.2))
; in con-leche: the typeConLeche/Kernel/Inductives/GenRec.lean:L175-L182175/-- **The generated recursor type** at class `c`. -/
176def classGenRecTy (g : ClassGen) (c : Nat) : Option Expr := do
177 let pre := g.pre
178 let rP := pre.length
179 let (ifs, maj) ← g.major c rP
180 let t : Expr := .fvar (rP + ifs.length) maj
181 pure (closeTelescope (pre ++ ifs.map g.binder ++ [maj]) 0
182 (Expr.mkAppN (g.motVar c) (ifs ++ [t])))
, a ruleConLeche/Kernel/Inductives/GenRec.lean:L188-L207188def classGenRule (g : ClassGen) (recOf : Nat → Option Name) (rlvls : List Level) (c : Nat)
189 (x : ClassCtor) : Option Expr := do
190 let pre := g.pre
191 let rP := pre.length
192 let (s, _) ← (List.range g.slots.length).zip g.slots |>.find? fun (_, sl) =>
193 match sl with | .minor c' C _ => c' == c && C == x.cv.name | _ => false
194 let (fvs, _) ← openPisAtFvars x.nF x.tyD rP
195 let ws ← targetPiDomsWith fvs x.tyN
196 let pvars := (List.range rP).map fun i => if i < g.nP then g.params.getD i default else
197 g.slotVar (i - g.nP)
198 let ihs ← (List.range x.nF).filterMapM fun i =>
199 match x.kinds.getD i .ordinary with
200 | .ordinary => some none
201 | .recursive t tele => do
202 let f := fvs.getD i default
203 let (xs, idx) ← g.ihParts t tele (ws.getD i default) (rP + x.nF)
204 let r ← recOf t
205 pure (some (closeLams (xs.map g.binder) (rP + x.nF)
206 (Expr.mkAppN (.const r rlvls) (pvars ++ idx ++ [Expr.mkAppN f xs]))))
207 pure (closeLams (pre ++ fvs.map g.binder) 0 (Expr.mkAppN (g.slotVar s) (fvs ++ ihs)))
.)
4.1.2. The elimination rule
For a type-valued family, in with never zero, the recursor eliminates into any , a fresh level parameter: large elimination. For a proposition-valued family the rule is stricter. In the model every proof is the point, so a recursor applied to a proof cannot see which constructor built it or with which fields, and a recursor with a type-valued motive would have to return one value for all of them. The official kernel allows large elimination out of a proposition only under the subsingleton criterion: the block has at most one constructor, and every field of that constructor is either itself a proposition or occurs among the constructor’s result indices. Then a proof of determines every field: the propositional ones are all the same proof, and the others can be read off the indices. Otherwise the motive lands in , (small elimination). In the fragment the criterion is a condition per fieldwhitepaper/Fragment/Decl.lean:L710-L717710/-- **The subsingleton criterion** for a field of sort `v` at
711position `i` (innermost first) of a constructor with index
712expressions `idx`: the field is a proposition, or it is one of the
713result's indices (official's `elim_only_at_universe_zero` at one
714constructor; con-leche's `checkStructFieldSortsI`). Required only of a
715large eliminator on a block whose sort may be `Prop`. -/
716def SubsingletonField (v : Level) (i : Nat) (idx : List Expr) : Prop :=
717 S.large = true → ¬ S.NeverProp → (LevelOracle.eq v .zero = true ∨ Expr.bvar i ∈ idx)
plus the constructor countwhitepaper/Fragment/Decl.lean:L763763 (S.large = true → ¬ S.NeverProp → S.ctors.length ≤ 1) ∧
, required of a large eliminator on a family whose sort may be zero (never zero: at every valuationwhitepaper/Fragment/Decl.lean:L706-L708706/-- "The result sort is never `Prop`": `1 ≤ u` at every valuation
707(con-leche's `Level.isNeverZero`). -/
708def NeverProp : Prop := LevelOracle.le (.succ .zero) S.sort = true
).
def blockLargeElimAllowed (p : BlockShape) (nested : Bool) : Bool :=83
p.resSort.isNeverZero ||84
(p.large && p.k == 1 && !nested && (p.numCtors == 0 || p.numCtors == 1)), appliedConLeche/Kernel/Inductives/GenRec.lean:L550-L552550
if p.large && !blockLargeElimAllowed p (nestedBit || Ms.any (·.member.isNone)) then551
throw (.invalid "generated recursor: large eliminator on a block whose sort may be Prop \552
(official: elim_only_at_universe_zero)")) but the per-field conditionConLeche/Kernel/Inductives/SumInstall.lean:L85-L9985
def checkStructFieldSortsI (ops : CheckerOps m) (env : Env) (isProp large : Bool)86
(s : Level) (nP : Nat) (fvs idxArgs : List Expr) : Nat → m (List Level)87
| 0 => pure []88
| j + 1 => do89
let fv ← unwrapOr fvs[j]? (.internal "direct sum: field index")90
let ty ← ops.inferType env (nP + j) fv.fvarTypeD91
let u ← ops.ensureSort env (nP + j) ty92
if !isProp then93
unless ← liftFueled "level comparison" (Level.leq u s) do94
throw (.invalid "direct sum: field universe too large")95
else if large then96
unless Level.isEquiv u .zero == some true || idxArgs.contains fv do97
throw (.invalid "direct sum: large eliminator with a non-propositional \98
field outside the indices")99
let rest ← checkStructFieldSortsI ops env isProp large s nP fvs idxArgs jonly of a family whose sort is provably zero, which is sound because the universe bound (the third check below) makes every field a proposition at any valuation that sends the result sort to zero.
Three of the checks listed below ask whether a sort is , and the checker answers each with the level oracle:
- a field’s sort is a proposition: (the subsingleton criterion);
- the result sort is never a proposition: (large elimination);
- a field’s sort does not exceed the result sort (the universe bound): , unless , in which case there is no boundwhitepaper/Fragment/Decl.lean:L699-L704699
/-- The universe bound on a field of sort `v`: the family is a
700proposition, or `v ≤ u` (official's "universe level of `type_of(arg)`
701is too big" check, `checkStructFieldSortsI`,
702`ConLeche/Kernel/Inductives/SumInstall.lean:124`). -/
703def FieldBound (v : Level) : Prop :=
704LevelOracle.eq S.sort .zero = true ∨ LevelOracle.le v S.sort = true
(Lean’s impredicativity of ).
Example 7 (an indexed proposition with large elimination). Let
have one constructor
It is a proposition-valued family, indexed by a number, with one ordinary field that is not a proposition and occurs in the result’s index. The subsingleton criterion holds, so the recursor may eliminate into :
with one rule,
The index occurs twice: is the recursor’s index argument, the constructor’s field. Section 4.2.7 uses this example.
4.1.3. When a block is accepted
A block is acceptedwhitepaper/Fragment/Decl.lean:L719-L764719/-- **The block is accepted** (con-leche's `checkBlock`,
720`ConLeche/Kernel/Inductives/BlockTail.lean:143`):
721
722* the block's names are distinct and fresh;
723* the specification is in scope (closed, stored constants, declared
724 level parameters — positivity included);
725* the generated type former's type has a sort in the current
726 environment;
727* each generated constructor's type has a sort in the environment
728 holding the former, every field's domain has a sort respecting the
729 universe bound and, where a large eliminator asks it, the
730 subsingleton criterion, and so does every binder of a reflexive
731 field's telescope (the domain's sort implies it; the fragment lists
732 it, as the checker infers it);
733* each rule's type (`ruleType`, the recursor's binder prefix with the
734 constructor's fields in place of the indices and the major) has a
735 sort in the environment holding the former and the constructors;
736* a large eliminator on a block whose sort may be `Prop` has at most
737 one constructor (official's `elim_only_at_universe_zero`);
738* the generated recursor's type has a sort in the environment holding
739 the former and the constructors.
740
741The recursor's rules are generated and stored, not checked: they
742mention the recursor. Con-leche does type its generated rules, in
743the environment holding the recursors without their rules
744(`classRuleOk`, `GenRec.lean:427`); the fragment stores them as is. -/
745def Ok (env : Env) : Prop :=
746 (S.name :: S.recName :: S.ctors.map (·.name)).Nodup ∧
747 (∀ m ∈ S.name :: S.recName :: S.ctors.map (·.name), env.find? m = none) ∧
748 S.Scoped env ∧
749 (∃ T, Infer env [] S.indType T) ∧
750 (∀ c ∈ S.ctors,
751 (∃ T u, Infer (S.envInd env) [] (S.ctorType c) T ∧ Red (S.envInd env) [] T (.sort u)) ∧
752 (∀ i A, (S.fieldCtx c.fields)[i]? = some A →
753 ∃ s v, Infer (S.envInd env) ((S.fieldCtx c.fields).drop (i + 1) ++ S.params) A s ∧
754 Red (S.envInd env) ((S.fieldCtx c.fields).drop (i + 1) ++ S.params) s (.sort v) ∧
755 S.FieldBound v ∧ S.SubsingletonField v i c.idx) ∧
756 (∀ i tele es, c.fields[i]? = some (.reflexive tele es) → ∀ t T, tele[t]? = some T →
757 ∃ s w, Infer (S.envInd env)
758 (tele.drop (t + 1) ++ (S.fieldCtx c.fields).drop (i + 1) ++ S.params) T s ∧
… 6 more lines when (as in con-lecheConLeche/Kernel/Inductives/BlockTail.lean:L141-L146141def checkBlock (ops : CheckerOps m) (env : Env) (block : List ConstantInfo) (p₀ : BlockParts) :
142 m Env := do
143 unless (p₀.allCtors.map (·.1.name)).Nodup ∧ p₀.memberNames.Nodup do
144 throw (.invalid "direct rec: duplicate constructor")
145 let q ← checkBlockPass ops env p₀ (blockRawRec p₀)
146 checkBlockTail ops block q
):
- its names are distinct and fresh;
- the specification is in scope, positivity included;
- the generated type former’s type has a type in the current environment;
- each generated constructor’s type has a sort in the environment holding the type former, and every field’s domain has a sort that respects the universe bound and, where a large eliminator asks it, the subsingleton criterion (the binders of a reflexive field’s own telescope respect the universe bound too);
- the constructor count respects the elimination rule;
- in the environment holding the type former and the constructors, the generated recursor’s type has a type, and so has each rule’s typewhitepaper/Fragment/Decl.lean:L307-L312307
/-- **A rule's type**: `∀ params motive minors fields, motive idx (C params fields)`
308— the recursor's binder prefix with the constructor's fields in place
309of the indices and the major (con-leche generates the rule and
310type-checks it, `classRuleOk`, `GenRec.lean:427`; the stream's rules
311are never read; the fragment infers the rule's type likewise). -/
312def ruleType (c : CtorSpec) : Expr := mkPis (S.ruleCtx c) (S.ruleBodyTy c)
, the recursor’s binder prefix with the constructor’s fields in place of the indices and the major.
Each “has a type” is an inference of §2, in the empty context, and each “has a sort” one whose result reduces to a sort. So the generated types are checked like a definition’s. The model needs only the inferences: the third claim of Theorem 2 makes every generated type a well-denoted type.
/-- **One generated rule, installed** (as generated): resolved and typed415
at the rule-less recursors' environment `feR` (its level parameters the416
recursor's), its λ-telescope over the prefix and the fields `n` long,417
its λ-domains resolving at the constructors' environment `feT`.418
Returns the rule (stored). -/419
def classRuleOk (ops : CheckerOps m) (w : StructWalkers) (feT feR : FEnv) (cvR : ConstantVal)420
(n : Nat) (gen : Expr) : m Expr := do421
unless gen.looseBVarsBounded 0 && !gen.hasFvar do422
throw (.internal s!"generated recursor: a rule of {cvR.name} is not closed")423
let genA := gen424
unless genA.allLevelParamsDefined cvR.levelParams do425
throw (.internal s!"generated recursor: a rule of {cvR.name} names an undeclared universe \426
parameter")427
unless w.resolve feR genA do428
throw (unresolvedConstsError s!"generated rule of {cvR.name}" genA)429
let _ ← ops.inferType feR.env 0 genA430
let (rbs, _) ← unwrapOr (genA.stripLams n)431
(.internal s!"generated recursor: a rule of {cvR.name} is not a λ-telescope over the \432
recursor's prefix and the constructor's fields")433
unless rbs.all (fun b => w.resolve feT b) do434
throw (unresolvedConstsError s!"the domains of a generated rule of {cvR.name}" genA)435
pure genA).
4.1.4. What is stored
The block adds three kinds of constantwhitepaper/Fragment/Decl.lean:L531-L572531/-! ### What is stored -/
532
533/-- The type former's constant. -/
534def indInfo : ConstInfo := ⟨S.lparams, S.indType, .induct S.nP S.nI (S.ctors.map (·.name)) S⟩
535
536/-- A constructor's constant. -/
537def ctorInfo (c : CtorSpec) : ConstInfo :=
538 ⟨S.lparams, S.ctorType c, .ctor S.name S.nP c.fields.length⟩
539
540/-- The recursor's constant: `nP` parameters, one motive, `n` minors,
541`nI` indices, the generated rules. -/
542def recInfo : ConstInfo := ⟨S.recLparams, S.recType, .recursor S.nP 1 S.n S.nI S.rules⟩
543
544/-- The environment with the type former added. -/
545def envInd (env : Env) : Env := env.add S.name S.indInfo
546
547/-- The environment with the type former and the constructors added,
548in order. -/
549def envCtors (env : Env) : Env :=
550 S.ctors.foldl (fun e c => e.add c.name (S.ctorInfo c)) (S.envInd env)
551
552/-- `T.rec`'s constant in a nested block: `nP` parameters, two
553motives, `n + nK` minors, `nI` indices. -/
554def recInfoN (N : NestInfo) : ConstInfo :=
555 ⟨S.recLparams, S.recTypeN N, .recursor S.nP 2 (S.n + N.nK) S.nI (S.rulesN N)⟩
556
557/-- `T.rec_1`'s constant: the same prefix, no indices. -/
558def rec1Info (N : NestInfo) : ConstInfo :=
559 ⟨S.recLparams, S.rec1Type N, .recursor S.nP 2 (S.n + N.nK) 0 (S.rules1 N)⟩
560
561/-- **The installed nested block**: former, constructors, the two
562recursors. -/
563def installN (N : NestInfo) (env : Env) : Env :=
564 ((S.envCtors env).add S.recName (S.recInfoN N)).add N.aux (S.rec1Info N)
565
566/-- **The installed block**: former, constructors, recursor — and the
567auxiliary recursor for a nested block. -/
568def install (env : Env) : Env :=
569 match S.nest with
570 | none => (S.envCtors env).add S.recName S.recInfo
… 2 more lines to the environment, the ones Section 3 left to this section (the kindswhitepaper/Fragment/Env.lean:L63-L8063/-- What kind of constant a name is stored as. -/
64inductive ConstKind where
65 /-- A definition: its value unfolds (δ). -/
66 | defn (value : Expr)
67 /-- An inductive type former with `numParams` parameters,
68 `numIndices` indices and these constructors — and the block's
69 specification it was generated from (`Spec.lean`), which a later
70 block nesting through this one reads back (con-leche stores the
71 block's shape with the former likewise, `IndCaps`). -/
72 | induct (numParams numIndices : Nat) (ctors : List Name) (spec : IndSpec)
73 /-- A constructor of the inductive `induct`, with `numParams`
74 parameters (the inductive's) and `nfields` fields. -/
75 | ctor (induct : Name) (numParams nfields : Nat)
76 /-- A recursor with the given telescope shape — `numParams`
77 parameters, `numMotives` motives, `numMinors` minor premises,
78 `numIndices` indices, then the major premise — and its rules. -/
79 | recursor (numParams numMotives numMinors numIndices : Nat) (rules : List RecRule)
80 deriving DecidableEq
, as in con-lecheConLeche/Kernel/Env.lean:L165-L182165structure RecRule where
166 ctor : Name
167 nfields : Nat
168 /-- The constructor's parameter count (install-computed; parse
169 placeholder `0`). -/
170 ctorParams : Nat
171 /-- The firing mode (install-computed, parse placeholder `.plain`):
172 `.plain` at a member major, `.nested` at an outside one. -/
173 fire : RecRuleFire
174 rhs : Expr
175 /-- **The K bit** (install-computed, parse placeholder `false`;
176 official `recursor_val::is_k`): this rule is its recursor's only
177 one, its constructor has no fields, and that constructor's
178 inductive is stored with the K capability — the standing condition
179 of `majorToCtor`'s K rescue, decided once at the block's install
180 instead of at every recursor application. -/
181 k : Bool := false
182 /-- **The η-rescue bit** (install-computed, parse placeholder
). Written with the notation of Section 3, a stored declaration is one of
- , a definition (Section 3);
- , a type former: parameters, indices, constructors ;
- , a constructor of : parameters, fields;
- , a recursor: parameters, one motive, minors, indices, the major; rules , each flagged Kwhitepaper/Fragment/Env.lean:L53-L6053
/-- **The K flag** (Lean's `RecursorVal.k`): the rule
54belongs to a K-like family — one family (not mutual, not nested),
55always a proposition, with one constructor and no fields (`Eq`,
56`HEq`, `True`). Such a rule fires on any major whose type is the
57family, with no constructor in sight (`Red.iotaK`): the reduct is
58the right-hand side at the recursor's parameters, motives and minors
59alone. -/
60k : Bool := false
when the block is K-like (Section 4.2.8).
The counts are the sizes of the argument groups (Example 6 shows them on ). A rule is a closed right-hand side over the recursor’s level parameters, one per constructor. The rules are generated and stored without an inferred type: they mention the recursor itself, and the official kernel infers no rule either.
4.2. The model
The model of a block has three parts: the set the type former denotes at each choice of indices, the sets the constructors denote, and the set the recursor denotes. With the recursor, the contract of Section 3 gains its third lawwhitepaper/Fragment/EnvModel.lean:L318-L325318 /-- Every plain rule of every stored recursor satisfies its ι law. -/
319 rec_rules : ∀ (c : Name) (ci : ConstInfo) (numParams numMotives numMinors numIndices : Nat)
320 (rules : List RecRule),
321 env.find? c = some ci →
322 ci.kind = .recursor numParams numMotives numMinors numIndices rules →
323 ∀ rl ∈ rules, rl.inst = none → ∀ cij : ConstInfo, env.find? rl.ctor = some cij →
324 RecRuleLaw M c ci numParams (numParams + numMotives + numMinors)
325 (numParams + numMotives + numMinors + numIndices) rl cij
: every rule of every stored recursor satisfies its law (Definition 9), which this subsection proves. Section 4.2.8 adds three more laws, facts about the model that let most steps go without a certificate. All of it is stated against an extension of §2′s axioms.
4.2.1. The axioms, extended
Beyond the laws of Section 2.4 this section assumes that the positive universes , , are Grothendieck universeswhitepaper/Fragment/Univ.lean:L43-L8843/-- **The positive universes are Grothendieck universes.** `SetLib`
44with pairing, union, power set and replacement (the image of a set
45under any function of the ambient logic), the numerals and `ω`, and
46the laws: every `univ n` with `n ≠ 0` is transitive and closed under
47the four operations, and contains `ω`. -/
48class UnivLib (V : Type u) extends SetLib V where
49 /-- The unordered pair. -/
50 upair : V → V → V
51 /-- The members of a pair. -/
52 mem_upair : ∀ {z a b : V}, Mem z (upair a b) ↔ z = a ∨ z = b
53 /-- The union of the members. -/
54 sUnion : V → V
55 /-- The members of a union. -/
56 mem_sUnion : ∀ {z x : V}, Mem z (sUnion x) ↔ ∃ y, Mem y x ∧ Mem z y
57 /-- The power set. -/
58 power : V → V
59 /-- The members of a power set: the subsets. -/
60 mem_power : ∀ {z x : V}, Mem z (power x) ↔ ∀ w, Mem w z → Mem w x
61 /-- Replacement: the image of a set under a function. -/
62 image : (V → V) → V → V
63 /-- The members of an image. -/
64 mem_image : ∀ {f : V → V} {A z : V}, Mem z (image f A) ↔ ∃ w, Mem w A ∧ z = f w
65 /-- The numerals. -/
66 nat : Nat → V
67 /-- Distinct numbers have distinct numerals. -/
68 nat_inj : ∀ {k l : Nat}, nat k = nat l → k = l
69 /-- `ω`: the set of numerals. -/
70 omega : V
71 /-- The members of `ω`. -/
72 mem_omega : ∀ {z : V}, Mem z omega ↔ ∃ k, z = nat k
73 /-- A positive universe is transitive: a member of a member is a
74 member. -/
75 univ_trans : ∀ {n : Nat} {A x : V}, n ≠ 0 → Mem A (univ n) → Mem x A → Mem x (univ n)
76 /-- A positive universe is closed under pairing. -/
77 upair_mem_univ : ∀ {n : Nat} {a b : V}, n ≠ 0 →
78 Mem a (univ n) → Mem b (univ n) → Mem (upair a b) (univ n)
79 /-- A positive universe is closed under union. -/
80 sUnion_mem_univ : ∀ {n : Nat} {x : V}, n ≠ 0 → Mem x (univ n) → Mem (sUnion x) (univ n)
81 /-- A positive universe is closed under power set. -/
82 power_mem_univ : ∀ {n : Nat} {x : V}, n ≠ 0 → Mem x (univ n) → Mem (power x) (univ n)
… 6 more lines, and that there are tagged tupleswhitepaper/Fragment/IndLib.lean:L47-L6447/-- **The library for inductive types**: the universes with tuples and
48tags. -/
49class IndLib (V : Type u) extends UnivLib V where
50 /-- The n-ary tuple of a list of sets. -/
51 tuple : List V → V
52 /-- Tuples are injective. -/
53 tuple_inj : ∀ {xs ys : List V}, tuple xs = tuple ys → xs = ys
54 /-- A tuple of members of a positive universe is a member of it. -/
55 tuple_mem_univ : ∀ {n : Nat} {xs : List V}, n ≠ 0 →
56 (∀ x ∈ xs, Mem x (univ n)) → Mem (tuple xs) (univ n)
57 /-- A value tagged with a number. -/
58 tag : Nat → V → V
59 /-- Tags are injective. -/
60 tag_inj : ∀ {i j : Nat} {x y : V}, tag i x = tag j y → i = j ∧ x = y
61 /-- A tagged member of a positive universe is a member of it. -/
62 tag_mem_univ : ∀ {n i : Nat} {x : V}, n ≠ 0 → Mem x (univ n) → Mem (tag i x) (univ n)
63 /-- A tagged value is never the point. -/
64 tag_ne_pt : ∀ {i : Nat} {x : V}, tag i x ≠ pt
:
Pairing, union, power set, replacementwhitepaper/Fragment/Univ.lean:L49-L6449
/-- The unordered pair. -/
50upair : V → V → V
51/-- The members of a pair. -/
52mem_upair : ∀ {z a b : V}, Mem z (upair a b) ↔ z = a ∨ z = b
53/-- The union of the members. -/
54sUnion : V → V
55/-- The members of a union. -/
56mem_sUnion : ∀ {z x : V}, Mem z (sUnion x) ↔ ∃ y, Mem y x ∧ Mem z y
57/-- The power set. -/
58power : V → V
59/-- The members of a power set: the subsets. -/
60mem_power : ∀ {z x : V}, Mem z (power x) ↔ ∀ w, Mem w z → Mem w x
61/-- Replacement: the image of a set under a function. -/
62image : (V → V) → V → V
63/-- The members of an image. -/
64mem_image : ∀ {f : V → V} {A z : V}, Mem z (image f A) ↔ ∃ w, Mem w A ∧ z = f w
, on all sets: for sets and a function (of the meta-theory) from sets to sets, the sets , , and with- The numerals and whitepaper/Fragment/Univ.lean:L65-L7265
/-- The numerals. -/
66nat : Nat → V
67/-- Distinct numbers have distinct numerals. -/
68nat_inj : ∀ {k l : Nat}, nat k = nat l → k = l
69/-- `ω`: the set of numerals. -/
70omega : V
71/-- The members of `ω`. -/
72mem_omega : ∀ {z : V}, Mem z omega ↔ ∃ k, z = nat k
: a set for every natural number , distinct for distinct numbers, and the set of all numerals. Grothendieck universeswhitepaper/Fragment/Univ.lean:L73-L8873
/-- A positive universe is transitive: a member of a member is a
74member. -/
75univ_trans : ∀ {n : Nat} {A x : V}, n ≠ 0 → Mem A (univ n) → Mem x A → Mem x (univ n)
76/-- A positive universe is closed under pairing. -/
77upair_mem_univ : ∀ {n : Nat} {a b : V}, n ≠ 0 →
78Mem a (univ n) → Mem b (univ n) → Mem (upair a b) (univ n)
79/-- A positive universe is closed under union. -/
80sUnion_mem_univ : ∀ {n : Nat} {x : V}, n ≠ 0 → Mem x (univ n) → Mem (sUnion x) (univ n)
81/-- A positive universe is closed under power set. -/
82power_mem_univ : ∀ {n : Nat} {x : V}, n ≠ 0 → Mem x (univ n) → Mem (power x) (univ n)
83/-- A positive universe is closed under replacement: the image of a
84member under a function into the universe is a member. -/
85image_mem_univ : ∀ {n : Nat} {f : V → V} {A : V}, n ≠ 0 → Mem A (univ n) →
86(∀ a, Mem a A → Mem (f a) (univ n)) → Mem (image f A) (univ n)
87/-- A positive universe contains `ω`. -/
88omega_mem_univ : ∀ {n : Nat}, n ≠ 0 → Mem omega (univ n)
: for , is transitive (if and then ), closed under the four operations,and contains .
- Tagged tupleswhitepaper/Fragment/IndLib.lean:L50-L6450
/-- The n-ary tuple of a list of sets. -/
51tuple : List V → V
52/-- Tuples are injective. -/
53tuple_inj : ∀ {xs ys : List V}, tuple xs = tuple ys → xs = ys
54/-- A tuple of members of a positive universe is a member of it. -/
55tuple_mem_univ : ∀ {n : Nat} {xs : List V}, n ≠ 0 →
56(∀ x ∈ xs, Mem x (univ n)) → Mem (tuple xs) (univ n)
57/-- A value tagged with a number. -/
58tag : Nat → V → V
59/-- Tags are injective. -/
60tag_inj : ∀ {i j : Nat} {x y : V}, tag i x = tag j y → i = j ∧ x = y
61/-- A tagged member of a positive universe is a member of it. -/
62tag_mem_univ : ∀ {n i : Nat} {x : V}, n ≠ 0 → Mem x (univ n) → Mem (tag i x) (univ n)
63/-- A tagged value is never the point. -/
64tag_ne_pt : ∀ {i : Nat} {x : V}, tag i x ≠ pt
, a list of sets with a number attached: injective (equal tagged tuples have equal numbers and equal components), in when every component is, never , and never a member of whitepaper/Fragment/IndLib.lean:L65-L6865/-- No tagged value is a member of the point. (With `pt := {{∅}}`
66and Kuratowski tags: `{∅}` is not a pair `{{i}, {i, x}}`.) Together
67with `tag_ne_pt` it makes every set of tagged values strict. -/
68tag_not_mem_pt : ∀ {i : Nat} {x : V}, ¬ Mem (tag i x) pt
, so that a set of tagged tuples is strict.
Nothing else is assumed. There is no law about least fixed points and none about the size of an inductive family; both are theorems below. What the construction uses of the universes follows at once. A subset of a member is a memberwhitepaper/Fragment/Univ.lean:L115-L120115/-- A subset of a member of a universe is a member: at a positive
116universe it is a member of the power set, which is a member; at
117`univ 0` it is a subset of `{pt}`. Con-leche:
118`univ_mem_of_subset_mem`, `ConLeche/SetTheory/Derive/Universe.lean`. -/
119theorem mem_univ_of_sub {n : Nat} {A B : V} (hA : A ∈ˢ univ n) (h : B ⊆ˢ A) :
120 B ∈ˢ (univ n : V) := by
(it is in the power set, and the universe is transitive), so a separation from a member is a member; the union of a family indexed by a memberwhitepaper/Fragment/Univ.lean:L177-L194177/-- **The union of an indexed family** `⋃ a ∈ A, F a`: replacement,
178then union. Con-leche: `IsTGUniverse.famUnion_mem`,
179`ConLeche/SetTheory/Derive/Universe.lean`. -/
180def famUnion (A : V) (F : V → V) : V := sUnion (image F A)
181
182theorem mem_famUnion {A z : V} {F : V → V} : z ∈ˢ famUnion A F ↔ ∃ a, a ∈ˢ A ∧ z ∈ˢ F a := by
183 unfold famUnion
184 rw [mem_sUnion]
185 constructor
186 · rintro ⟨y, hy, hz⟩
187 obtain ⟨a, ha, rfl⟩ := mem_image.mp hy
188 exact ⟨a, ha, hz⟩
189 · rintro ⟨a, ha, hz⟩
190 exact ⟨F a, mem_image.mpr ⟨a, ha, rfl⟩, hz⟩
191
192theorem famUnion_mem_univ {n : Nat} {A : V} {F : V → V} (hn : n ≠ 0) (hA : A ∈ˢ univ n)
193 (hF : ∀ a, a ∈ˢ A → F a ∈ˢ univ n) : famUnion A F ∈ˢ (univ n : V) :=
194 sUnion_mem_univ hn (image_mem_univ hn hA hF)
is a member, by replacement then union; and so is the countable union whitepaper/Fragment/Univ.lean:L212-L231212/-- **The countable union** `F 0 ∪ F 1 ∪ …`: the union over `ω`, each
213numeral read back as its number. Con-leche: `Tower.natUnion`,
214`ConLeche/SetModel/Iter.lean`. -/
215noncomputable def natUnion (F : Nat → V) : V := famUnion omega fun z => F (idx z)
216
217theorem mem_natUnion {F : Nat → V} {z : V} : z ∈ˢ natUnion F ↔ ∃ k, z ∈ˢ F k := by
218 unfold natUnion
219 rw [mem_famUnion]
220 constructor
221 · rintro ⟨w, -, hz⟩
222 exact ⟨idx w, hz⟩
223 · rintro ⟨k, hk⟩
224 exact ⟨nat k, mem_omega.mpr ⟨k, rfl⟩, by rw [idx_nat]; exact hk⟩
225
226/-- A countable union of members of a positive universe is a member —
227the one use of `ω`. Con-leche: `Tower.natUnion_mem_univ_pos`,
228`ConLeche/SetModel/Iter.lean`. -/
229theorem natUnion_mem_univ {n : Nat} {F : Nat → V} (hn : n ≠ 0) (h : ∀ k, F k ∈ˢ univ n) :
230 natUnion F ∈ˢ (univ n : V) :=
231 famUnion_mem_univ hn (omega_mem_univ hn) fun _ _ => h _
, the union over . This is the one use of whitepaper/Fragment/Univ.lean:L29-L3529**Infinity is not a convenience.** `V_ω`, the hereditarily finite
30sets, is transitive and closed under pairing, union, power set and
31replacement, and the least fixed point of `X ↦ {∅} ∪ {x ∪ {x} | x ∈ X}`
32— accessible with a one-element bound — is `ω ∉ V_ω`. So the theorem
33`closed_of_acc` (`Access.lean`) needs `ω ∈ univ n`, and uses it
34exactly once: the set of finite paths over the bound is a countable
35union (`natUnion`).
, and it is needed: the hereditarily finite sets satisfy every other law in this list.
def IsTGUniverse {V : Type u} (mem : V → V → Prop) (u : V) : Prop :=90
(∀ y z, mem y u → mem z y → mem z u) ∧91
(∀ y z, mem y u → (∀ w, mem w z → mem w y) → mem z u) ∧92
(∀ y, mem y u → ∃ p, mem p u ∧ ∀ z, (∀ w, mem w z → mem w y) → mem z p) ∧93
(∀ y, (∀ w, mem w y → mem w u) → Equinumerous mem y u ∨ mem y u), the classConLeche/SetTheory/Core.lean:L95-L100 95
/-- A model of set theory of exactly the strength the checker needs:96
membership, the six ZF⁻ axioms (extensionality, pairing, union, power97
set, regularity, Lean-level replacement), and an ω-chain of98
Grothendieck universes. Infinity is derivable; choice is inherited99
from the meta-logic (`Classical.choice`); see the module docstring. -/100
class SetTheory (V : Type u) where). The closure laws of the list, and infinity, are derived from it; the fragment takes the derived laws as its interface, as it does for the rest of §2′s laws.
4.2.2. The operator
Fix a block as in Section 4.1, a valuation , and values for the parameters. A family is a function from index values to sets, its fibres. The block’s operator sends a family to a family: its fibre at is the set of constructor values whose fields fit a constructor relative to and whose index expressions read as ,
where are the index expressions of ’s result (Section 4.1), and their denotations with the parameters at and the fields at . Values fit a constructor’s fields relative to when each is a member of its field’s set at the earlier values: the set of an ordinary field is its domain’s denotation, read without ; the set of a reflexive field is the function space, read as in §2, over its telescope into fibres of at the field’s own index expressions ,
and the fibre itself when the telescope is empty (a field’s setwhitepaper/Fragment/IndSem.lean:L282-L303282/-- **The set a field ranges over**, relative to a family `W` (index
283values to sets), at parameters `ps` and the earlier field values `fs`
284(innermost first): an ordinary field's domain; for a reflexive field
285the function space over its telescope into the fibres of `W` at the field's
286index expressions read under the telescope's values — the fibre
287itself when the telescope is empty; for a container field the class
288at the fibre of `W` at the nested occurrence's index expressions
289(`classSet`: the container's family, read at the class's arguments
290with that fibre in the nested position; a plain block has no
291container field, and the clause is empty). Con-leche: a field's
292reading with holes, `LfpDatum.fields`, read at the hole frame of the
293tuple (`HFits`, `BlockLfp.lean`); the container clause `contApp`
294through the container's lfp clause (`BlockLfpMono.lean`). -/
295noncomputable def fieldSet (W : List V → V) (ps fs : List V) : Field → V
296 | .ordinary A => interp M (S.ψ ls) (consList fs (envP ps)) A
297 | .reflexive tele es =>
298 piCtx M (S.ψ ls) (consList fs (envP ps)) tele fun ρ' => S.reflBody ls tele (W (S.idxVals M ls ρ' es))
299 | .container =>
300 match S.nest with
301 | some N => S.classSet M ls N ps (W (S.memberIdx M ls N ps))
302 | none => empty
303
, a fitting listwhitepaper/Fragment/IndSem.lean:L315-L320315first) relative to a family: each value a member of its field's set
316at the earlier values. Con-leche: `SpineFit` at the hole frame. -/
317def FitsFields (W : List V → V) (ps : List V) : List Field → List V → Prop
318 | [], [] => True
319 | f :: fs, v :: vs => FitsFields W ps fs vs ∧ v ∈ˢ S.fieldSet M ls W ps vs f
320 | _, _ => False
). Here whitepaper/Fragment/IndCommon.lean:L188-L195188/-- **The admissible part of a set**: at a proposition the set itself;
189above it the members that are neither the point nor a member of it —
190a strict set (`adm_mem_typeU`), so a member of the nested parameter's
191domain `Sort u`, a set of types, and the set itself on a
192set of tagged values (`adm_eq_self`), such as a fibre of the family.
193The class reads the container's set at admissible sets only: there it
194is a graph applied inside its domain. -/
195def adm (X : V) : V := if S.z ls then X else sep X fun x => x ≠ pt ∧ ¬ x ∈ˢ pt
, the admissible part of , is itself when the family is proposition-valued; when it is type-valued, it is the members of that neither are the point nor contain it. On the family the construction ends with, changes nothing, since there a type-valued fibre is a set of tagged tuples. It is needed for the arbitrary families the operator is also applied to. A fibre would make the field’s function space a truth value, and then the field’s set would not grow with the family (the function space’s bodywhitepaper/Fragment/IndCommon.lean:L278-L286278/-- **The body of a reflexive field's function space**: the family's fibre —
279its admissible part under at least one binder. The
280function space is read by values (`SetLib.piV`), and over an arbitrary family
281in the universe a fibre `{pt}` would put a Type-sorted field's function space
282in the propositional regime; the admissible part, a strict set above a
283proposition, keeps it a function space wherever its domain is
284inhabited, so the field's set grows with the family (`piCtx_retype`).
285On the block's own family the fibres are admissible already. -/
286def reflBody (tele : List Expr) (X : V) : V := if tele = [] then X else S.adm ls X
). The fibre is a set, built by replacement, union and separation from the fields’ sets (the constructionwhitepaper/Fragment/IndSem.lean:L501-L550501replacement and union along the fields (innermost first).
502Con-leche builds the fibre the same way, by replacement over the
503spines (`BlockLfp.lean`, `fibre`). -/
504noncomputable def fitsSet (W : List V → V) (ps : List V) : List Field → V
505 | [] => sing (tuple [])
506 | f :: fs => famUnion (fitsSet W ps fs) fun t =>
507 famUnion (S.fieldSet M ls W ps (untuple t) f) fun v => sing (tuple (v :: untuple t))
508
509theorem mem_fitsSet {W : List V → V} {ps : List V} :
510 ∀ {fields : List Field} {x : V},
511 x ∈ˢ S.fitsSet M ls W ps fields ↔ ∃ fs, S.FitsFields M ls W ps fields fs ∧ x = tuple fs
512 | [], x => by
513 simp only [fitsSet, mem_sing]
514 constructor
515 · rintro rfl; exact ⟨[], trivial, rfl⟩
516 · rintro ⟨fs, hfs, rfl⟩
517 cases fs with
518 | nil => rfl
519 | cons _ _ => exact hfs.elim
520 | f :: fields, x => by
521 simp only [fitsSet, mem_famUnion, mem_sing]
522 constructor
523 · rintro ⟨t, ht, v, hv, rfl⟩
524 obtain ⟨fs, hfs, rfl⟩ := mem_fitsSet.mp ht
525 rw [untuple_tuple] at hv ⊢
526 exact ⟨v :: fs, ⟨hfs, hv⟩, rfl⟩
527 · rintro ⟨fs, hfs, rfl⟩
528 cases fs with
529 | nil => exact hfs.elim
530 | cons v fs =>
531 refine ⟨tuple fs, mem_fitsSet.mpr ⟨fs, hfs.1, rfl⟩, v, ?_, ?_⟩
532 · rw [untuple_tuple]; exact hfs.2
533 · rw [untuple_tuple]
534
535/-- **The fibre of constructor `j`** at the family `W` and the index
536values `is`: the constructor values of the fitting lists whose index
537expressions read as `is`. -/
538noncomputable def ctorFibre (W : List V → V) (ps is : List V) (j : Nat) (c : CtorSpec) : V :=
539 open Classical in
540 image (fun t => S.ctorVal ls j (untuple t))
… 10 more lines, its members are as displayedwhitepaper/Fragment/IndSem.lean:L561-L565561constructor instances over the input family at `is`. Con-leche:
562`LfpClause.fibre`. -/
563theorem mem_famOp {ps : List V} {W : List V → V} {is : List V} {x : V} :
564 x ∈ˢ S.famOp M ls ps W is ↔ S.Inst M ls W ps is x := by
565 unfold famOp Inst
). When the family is proposition-valued ( evaluates to at ) a constructor value is the pointwhitepaper/Fragment/IndCommon.lean:L336-L338336/-- **A constructor's value** at its fields: the point at a
337proposition, the tagged tuple above. -/
338def ctorVal (j : Nat) (fs : List V) : V := if S.z ls then pt else tag (S.tagOf j) (tuple fs.reverse)
instead of the tagged tuple, so every fibre of is a subset of : the truth value “some constructor reaches these indices”.
4.2.3. Accessibility
Theorem 8 below, which supplies the closed family the least fixed point needs, asks for accessibility. An occurrence in a family is a pair of index values and a member of the fibre there; write . Call accessible with the bound whitepaper/Fragment/Access.lean:L68-L7868/-- **Accessibility with the bound `A`** of an operator from families
69over `ι` to families over `κ`: every element `x` the operator
70produces at `j` from a family `W` in the universe has a support — a
71subset `B` of `A` and occurrences `g a ∈ W` for `a ∈ B` — such that
72`x` is produced at `j` from every family `W'` in the universe
73containing the support. Con-leche: `AccTuple`,
74`ConLeche/SetModel/Access.lean`. -/
75def AccFam (n : Nat) (Φ : (ι → V) → κ → V) (A : V) : Prop :=
76 ∀ W, InUniv n W → ∀ j x, x ∈ˢ Φ W j →
77 ∃ (B : V) (g : V → ι × V), B ⊆ˢ A ∧ (∀ a, a ∈ˢ B → InFam W (g a)) ∧
78 ∀ W', InUniv n W' → (∀ a, a ∈ˢ B → InFam W' (g a)) → x ∈ˢ Φ W' j
, a set, when every element of a fibre of has a support: a subset together with a map that names, for each , an occurrence in , such that is produced from every family in the universe that contains those occurrences:
(: every fibre of is a member of ). Whether an element is produced depends on at most -many elements of the input and on nothing else, for the one set . The fixed-point equation below also needs a monotone operator; accessibility implies it, on families in the universe: the support in lies in any larger whitepaper/Fragment/Access.lean:L80-L8580/-- **Accessible operators are monotone**: the support in `W` is in
81the larger `W'`. Con-leche: `AccTuple.monoTuple`. -/
82theorem AccFam.mono {n : Nat} {Φ : (ι → V) → ι → V} {A : V} (h : AccFam n Φ A) : MonoFam n Φ := by
83 intro W W' hW hW' hle i x hx
84 obtain ⟨B, g, -, hg, hs⟩ := h W hW i x hx
85 exact hs W' hW' fun a ha => hle _ _ (hg a ha)
.
The block’s operator is accessible. Take as an example the tree type with a constructor and an element of : the field is a function from the naturals into a fibre of . Three things have to be shown.
- The support. The value depends on only through the children , so its support is those childrenwhitepaper/Fragment/IndSem.lean:L1158-L11831158
own supports `Bk` (by position). -/
1159noncomputable def instSupp (ps : List V) (c : CtorSpec) (fs : List V) (Bk : Nat → V) : V :=
1160listUnion ((List.range c.fields.length).map fun k =>
1161match c.fields[c.fields.length - 1 - k]? with
1162| some (.reflexive tele _) =>
1163image (pcons (nat k)) (ctxSet M (S.ψ ls) (consList (earlier fs k) (envP ps)) tele)
1164| some .container => image (pcons (nat k)) (Bk k)
1165| _ => empty)
1166
1167/-- The occurrence a code stands for: at a reflexive field, the field
1168at the code's position applied to the code's argument tuple, at the
1169field's index expressions there; at a container field, the container
1170value's own occurrence `gk` at the code, at the nested occurrence's
1171index expressions. -/
1172noncomputable def instOcc (ps : List V) (c : CtorSpec) (fs : List V) (gk : Nat → V → V) (b : V) :
1173List V × V :=
1174match c.fields[c.fields.length - 1 - idx (pfst b)]? with
1175| some (.reflexive _ es) =>
1176(S.idxVals M ls (consList (untuple (psnd b)) (consList (earlier fs (idx (pfst b))) (envP ps))) es,
1177appList (fieldVal fs (idx (pfst b))) (untuple (psnd b)).reverse)
1178| some .container =>
1179match S.nest with
1180| some N => (S.memberIdx M ls N ps, gk (idx (pfst b)) (psnd b))
1181| none => ([], pt)
1182| _ => ([], pt)
1183
: one occurrence for each argument of the field, , where are the field’s index expressions. In general a reflexive field with telescope contributes one occurrence per fitting the telescope (a recursive field, with the empty telescope, exactly one), and an ordinary field contributes nothing. Each occurrence is named by the field’s position and the tuple . - A family holding the support produces . If every child lies in the matching fibre of , then , a function whose applications all lie in fibres of , is a member of the function spacewhitepaper/Fragment/IndSem.lean:L1237-L12501237
/-- **A fitting list fits every family holding its support** — the
1238re-typing of each reflexive field's value, and the container's clause
1239at each container value. -/
1240theorem FitsFields_retype {ps : List V} (hco : S.ContOk M ls ps) {W W' : List V → V}
1241(hW : InUniv (S.u₀ ls) W) (hW' : InUniv (S.u₀ ls) W') :
1242∀ {fields : List Field} {fs : List V}, S.FitsFields M ls W ps fields fs →
1243(∀ k tele es, fields[fields.length - 1 - k]? = some (.reflexive tele es) → k < fields.length →
1244∀ ys, FitsVals M (S.ψ ls) (consList (earlier fs k) (envP ps)) tele ys →
1245appList (fieldVal fs k) ys.reverse ∈ˢ
1246W' (S.idxVals M ls (consList ys (consList (earlier fs k) (envP ps))) es)) →
1247(∀ k, fields[fields.length - 1 - k]? = some .container → k < fields.length →
1248∀ N, S.nest = some N →
1249fieldVal fs k ∈ˢ S.classSet M ls N ps (W' (S.memberIdx M ls N ps))) →
1250S.FitsFields M ls W' ps fields fs
into those fibres (by η), and ordinary fields do not mention the family; so . - One bound for every family. The names of the occurrences (field position and argument tuple) must come from one set , the same whatever is. For that is the naturals. In general collects, per constructor and reflexive field, the tuples fitting the field’s telescopewhitepaper/Fragment/IndSem.lean:L1080-L10971080
/-- **A constructor's support bound**: for each reflexive field, the
1081tuples of its telescope at every list fitting the fields before it
1082relative to the one-fibre family, each coded by the field's position
1083(a numeral in a path step, `pcons`); for each container field, the
1084class's bound, coded likewise. An ordinary field contributes
1085nothing; a recursive field, whose telescope is empty, contributes the
1086one tuple `tuple []` per prefix. Con-leche: `piBound` glued along the
1087telescope (`HoleAcc.lean`), made uniform along the fields
1088(`TeleAcc.lean`); the container's bound `accPaths` of
1089`ContAccFrame.lean`. -/
1090noncomputable def ctorBound (ps : List V) (c : CtorSpec) : V :=
1091listUnion ((List.range c.fields.length).map fun k =>
1092match c.fields[c.fields.length - 1 - k]? with
1093| some (.reflexive tele _) =>
1094famUnion (S.fitsSet M ls (S.oneFam ls) ps (c.fields.drop (c.fields.length - k))) fun t =>
1095image (pcons (nat k)) (ctxSet M (S.ψ ls) (consList (untuple t) (envP ps)) tele)
1096| some .container => image (pcons (nat k)) S.classBound
1097| _ => empty)
. A telescope may mention earlier fields, but by the second shape condition of Section 4.1 never a reflexive field’s value, so the telescopes do not depend on ; the bound reads them at the one-fibre family, every fibre the same one-element typewhitepaper/Fragment/IndSem.lean:L819-L822819/-- **The one-fibre set**: `{pt}` at a proposition; above it a
820singleton of a tagged value — a type of the result universe either
821way, with an admissible member (`oneVal`). -/
822noncomputable def oneSet : V := if S.z ls then one else sing (tag 0 empty)
: when the family is proposition-valued, the singleton of a tagged tuple when it is type-valued (the replacementwhitepaper/Fragment/IndSem.lean:L885-L896885/-- An instance's values with every recursive value replaced by the
886constant family `Y`'s canonical member of its field's set (the value
887`y` abstracted over the telescope), and every container value by its
888stand-in. (`toFam one pt` is the one-fibre family's.) -/
889noncomputable def toFam (Y y : V) (ps : List V) : List Field → List V → List V
890| f :: fields, v :: vs =>
891(match f with
892| .reflexive tele _ =>
893lamCtx M (S.ψ ls) (consList (toFam Y y ps fields vs) (envP ps)) tele fun _ => y
894| .container => S.contAt M ls Y ps
895| _ => v) :: toFam Y y ps fields vs
896| _, vs => vs
, the telescope reads alikewhitepaper/Fragment/IndSem.lean:L1007-L10121007/-- A reflexive field's telescope has the same tuples at the values
1008and at the replaced values. -/
1009theorem ctxSet_toFam (Y y : V) (ps : List V) {L : List Field} {tele es : List Expr} {fs : List V}
1010(hnr : fieldNoRecDep L (.reflexive tele es)) :
1011ctxSet M (S.ψ ls) (consList fs (envP ps)) tele
1012= ctxSet M (S.ψ ls) (consList (S.toFam M ls Y y ps L fs) (envP ps)) tele := by
).
Hence the operator is accessible with this boundwhitepaper/Fragment/IndSem.lean:L1304-L13111304every constructor value's support is coded inside the bound, lies in
1305the input family, and carries the value to every family of the
1306universe holding it; at a container field by the container's clause.
1307Con-leche: `blockAcc_of_run`, the `AccTuple` `closed_of_acc`
1308consumes. -/
1309theorem famOp_acc {ps : List V} (hnr : S.NoRecDep) (hco : S.ContOk M ls ps) :
1310 AccFam (S.u₀ ls) (S.famOp M ls ps) (S.bound M ls ps) := by
1311 intro W hW is x hx
, and so monotonewhitepaper/Fragment/IndSem.lean:L1418-L14231418accessibility's support carries over (`AccFam.mono`). Con-leche:
1419`LfpClause.functor`'s first conjunct; `AccTuple.monoTuple`. -/
1420theorem famOp_mono {ps : List V} (hnr : S.NoRecDep) (hco : S.ContOk M ls ps) :
1421 MonoFam (S.u₀ ls) (S.famOp M ls ps) :=
1422 (S.famOp_acc M ls hnr hco).mono
1423
.
For a type-valued family, that the bound is a member of , and that maps families in to families in , follows from the semantic form of the checker’s universe bound, the universe bound on the fieldswhitepaper/Fragment/IndSem.lean:L601-L622601/-- **The universe bound on the fields, at every family in the
602universe**: above a proposition, along any list fitting the fields
603before a position relative to a family of members of the result
604universe, an ordinary field's domain is a member of the result
605universe, and so is every entry of a reflexive field's telescope at
606every fitting prefix of the telescope. Con-leche: the constructors'
607fields are small at every hole frame of the tuple space
608(`LfpClause.fieldsOk`, `ConLeche/Model/Env/BlockLfp.lean`, the
609install's `FieldsOkB`); the installation derives it from the checker's
610field-sort check read in a model in which the former denotes the
611one-fibre family (`domsBounded_of`, `InstallInd.lean`), whence every
612family by `domsBounded_of_fam`. -/
613def DomsBounded (ps : List V) : Prop :=
614 S.z ls = false → ∀ W, InUniv (S.u₀ ls) W → ∀ c ∈ S.ctors, ∀ k f,
615 c.fields[c.fields.length - 1 - k]? = some f → k < c.fields.length →
616 ∀ fs, S.FitsFields M ls W ps (c.fields.drop (c.fields.length - k)) fs →
617 match f with
618 | .ordinary A => interp M (S.ψ ls) (consList fs (envP ps)) A ∈ˢ (univ (S.u₀ ls) : V)
619 | .reflexive tele _ => ∀ t T, tele[t]? = some T → ∀ ys,
620 FitsVals M (S.ψ ls) (consList fs (envP ps)) (tele.drop (t + 1)) ys →
621 interp M (S.ψ ls) (consList ys (consList fs (envP ps))) T ∈ˢ (univ (S.u₀ ls) : V)
622 | .container => True
: along any instance of any family in the universe, every ordinary domain and every entry of a reflexive field’s telescope is a member of (the bound is a memberwhitepaper/Fragment/IndSem.lean:L1105-L11091105proposition), by the universe bound on the fields at the one-fibre
1106family. -/
1107theorem bound_mem_univ {ps : List V} (hz : S.z ls = false) (hb : S.DomsBounded M ls ps)
1108 (hco : S.ContOk M ls ps) : S.bound M ls ps ∈ˢ (univ (S.u₀ ls) : V) := by
1109 have hn := S.u₀_ne_zero ls hz
, the operator maps the universe to itselfwhitepaper/Fragment/IndSem.lean:L768-L775768universe**: at a proposition its fibres are truth values; above one
769they are sets of tagged tuples of members, built by replacement and
770union from sets of the universe (the class's membership in the
771universe from the container's clause). Con-leche: `LfpClause.functor`'s
772second conjunct, from `fieldsOk`. -/
773theorem famOp_maps {ps : List V} (hb : S.DomsBounded M ls ps) (hco : S.ContOk M ls ps) :
774 MapsFam (S.u₀ ls) (S.famOp M ls ps) := by
775 intro W hW is
). It comes from the checker’s field-sort check, , read in a model in which the type former denotes the one-fibre family: the type former’s type
is inhabited by the graph of any admissible family of types of the universe, so the environment holding the type former has a model for each onewhitepaper/Fragment/InstallInd.lean:L243-L247243/-- **The environment with the former has a model for every family of
244the universe**, the former its graph. -/
245noncomputable def mIndF (F : List Nat → List V → List V → V)
246 (hF : ∀ ls' ps is, F ls' ps is ∈ˢ (typeU (S.u₀ ls') : V)) (hFa : ∀ ls' ps is, S.adm ls' (F ls' ps is) = F ls' ps is) :
247 EnvModel V (S.envInd env) where
, and Theorem 2 read in the one for the one-fibre family puts every field’s domain in , and so, since and the universes are cumulative, in . No field reads an earlier reflexive field’s value, so the domains met along an instance of any family are those met along an instance of the one-fibre family (the universe bound, at every familywhitepaper/Fragment/InstallInd.lean:L429-L439429/-- **The universe bound on the fields, at every family of the result
430universe**: the one-fibre family (`oneSet`, a family of types) is the
431former's denotation in the model `mIndF`; the checker's sort facts on
432every field and every binder of a reflexive field's telescope, read
433there at the values fitting the earlier fields, bound the field's set
434at that family, hence at every family (`domsBounded_of_fam`: no field
435reads an earlier recursive field). Con-leche:
436`FieldsOkB` at every hole frame, from the holes-in-context check. -/
437theorem domsBounded_of (φ : Name → Nat) (ρ : Nat → V) {ps : List V} (hps : ps.length = S.nP)
438 (hp : FitsVals m.M (S.ψ (S.lparams.map φ)) base S.params ps) :
439 S.DomsBounded m.M (S.lparams.map φ) ps := by
).
For a proposition-valued family none of this is needed. Every constructor value is the point, so every fibre of is a subset of , and the constant family is closedwhitepaper/Fragment/LfpSet.lean:L170-L174170/-- **At a proposition no bound is needed**: every member of `univ 0`
171is a subset of `{pt}`, so the constant family `{pt}` is closed under
172any operator into `univ 0`. Con-leche: `closedTuple_zero`,
173`ConLeche/SetModel/Access.lean`. -/
174theorem closedFam_zero {Φ : (ι → V) → ι → V} (hmaps : MapsFam 0 Φ) : ∃ L, IsClosedFam 0 Φ L := by
under and lies in . This closed family needs no bound in the universe and no universe bound on the fields. Accessibility, which holds in both cases, is used here only for monotonicity.
/-- **The hole operator**: the block operator at the hole chains. -/105
@[expose] noncomputable def holeOp (ψ : Name → Nat) (ρp : Nat → V) : (Nat → V) → Nat → V :=106
blockPhiG D.N (D.w ψ) ρp (fun c => D.u c ψ) (fun c => D.ids c ψ) (D.holeChains ψ)), and monotonicity and accessibility are read off the positivity check’s run, case by case. A reading that mentions no hole is constantConLeche/Semantics/Inductives/HoleMono.lean:L71-L7371
/-- **Hole-free**: the reading is the same at related frames. -/72
def ConstOn (R : FrameRel V) (a : Term) : Prop :=73
∀ ρ ρ', R ρ ρ' → interp V ρ a = interp V ρ' a(the ordinary field); a function space over a hole-free domain, its fibres types, is positive when its body isConLeche/Semantics/Inductives/HoleMono.lean:L130-L139130
/-- **`pi`**: a Π-type with a hole-free domain and a positive codomain131
is positive, as soon as its fibres are types at both frames (the132
grading of the walked field: "not pt" source (1)) — `piV` is monotone in133
fibres that are types (`piV_subset_mono`: a truth value included in a134
type is a truth value, and a type with a fibre that is not one is135
strict there). -/136
theorem MonoOn.pi {R : FrameRel V} {A B : Term} (hA : ConstOn R A)137
(hB : MonoOn (R.under A) B)138
(hT : ∀ ρ ρ', R ρ ρ' → (∀ x, x ∈ˢ interp V ρ A → IsTy (interp V (cons x ρ) B)) ∧139
(∀ x, x ∈ˢ interp V ρ' A → IsTy (interp V (cons x ρ') B))) :and accessible with its body’s bound glued over the domainConLeche/Semantics/Inductives/HoleAcc.lean:L233-L243233
/-- **Π over a hole-free domain**: the support of a member over a small234
domain is the glued supports of its values, the body's items one binder235
down (the bound variable is no admissible position); a member over a236
domain that is not small (`w ≠ 0`) is the point, whose membership needs237
no item (`PtOn`). At the target frame the member lies in the Π by238
`mem_piV_of_app`, the fibres at both frames being types (`hT`, the239
grading: "not pt" source (1)). -/240
theorem AccOn.pi {w : Nat} {Q : Nat → Nat → Prop} {R : FrameRel V} {D B : Term}241
(hD : ConstOn R D)242
{Bb : (Nat → V) → V} (hB : AccOn w (shiftQ Q) (R.underBoth D) Bb B)243
(hT : FibTyOn R D B) (hP : w ≠ 0 → PtOn (R.underBoth D) B) :(the reflexive field); a hole applied to hole-free arguments is positiveConLeche/Semantics/Inductives/HoleMono.lean:L158-L162158
/-- **`holeApp`**: a hole applied to hole-free arguments, at its full159
arity, is positive. -/160
theorem MonoOn.holeApp {R : FrameRel V} {h : Nat} {es : List Term}161
(hh : HoleOn R h es.length) (hes : ∀ e ∈ es, ConstOn R e) :162
MonoOn R (Term.mkAppN (.bvar h) es) := byand accessible with the bound ConLeche/Semantics/Inductives/HoleAcc.lean:L321-L324321
/-- **A member hole at its full arity, at hole-free arguments.** -/322
theorem AccOn.holeApp {w : Nat} {Q : Nat → Nat → Prop} {R : FrameRel V} {h : Nat}323
{es : List Term} (hQ : Q h es.length) (hes : ∀ e ∈ es, ConstOn R e) :324
AccOn w Q R (fun _ => unitSet) (Term.mkAppN (.bvar h) es) := by(the recursive field). Assembled along the fields, that is the fit’s accessibility, for both kinds of family, with a bound in the universe for a type-valued oneConLeche/Model/Inductives/BlockAccRunCont.lean:L310-L315310
/-- **The hole fit of a uniform block is accessible at every parameter311
frame**, with a bound that is a set of the level at a `Type`-valued one312
— whence the operator's accessibility, (W) and monotonicity: the block theorem with the313
container rules read at the walk's carrier (coverage) and the walk314
context's sort (the block's level). -/315
theorem blockAcc_of_run {μ : ConLeche.CheckMode} (hμ : μ.isVerified = true) {F : Nat}(the notion aboveConLeche/SetModel/Access.lean:L56-L6356
/-- **`A`-accessibility** of an operator from `kI`-tuples over `IsI` to57
`kO`-tuples over `IsO`. -/58
def AccTuple (w kI : Nat) (IsI : Nat → V) (kO : Nat) (IsO : Nat → V)59
(Φ : (Nat → V) → Nat → V) (A : V) : Prop :=60
∀ X, InTupleSpace w kI IsI X → ∀ m, m < kO → ∀ i, i ∈ˢ IsO m → ∀ x, x ∈ˢ app (Φ X m) i →61
∃ (B : V) (g : V → Nat × V × V), B ⊆ˢ A ∧ (∀ a, a ∈ˢ B → InTup kI IsI X (g a)) ∧62
∀ X', InTupleSpace w kI IsI X' → (∀ a, a ∈ˢ B → InTup kI IsI X' (g a)) →63
x ∈ˢ app (Φ X' m) i), and the operator’s monotonicityConLeche/Model/Env/BlockLfp.lean:L511-L515511
/-- **The operator is monotone**, from its accessibility (`AccW.mono`). -/512
theorem mono (h : LfpClause acval D) {ψ : Name → Nat} {ρp : Nat → V}513
(hsat : Sat V (D.params ψ).reverse ρp) :514
MonoTuple (D.w ψ) D.N (D.idx ψ ρp) (D.Φ ψ ρp) :=515
(h.acc ψ ρp hsat).monofollows from it, as in the fragment. The fragment’s simplification is to take the field kinds from the specification instead of the run. The universe bound on the fields the real proof records from a check made for the proof alone, the constructor type-checked with the holes in context (the recorded boundConLeche/Model/Env/BlockLfp.lean:L450-L456450
/-- **the constructors' fields are small** at a `Type`-valued block, at451
every hole frame of the tuple space (the frame walk of a container reads its constructors' fields along the452
accessibility relation, which supports small elements only — the453
install's own `FieldsOkB`, recorded) -/454
fieldsOk : ∀ (ψ : Name → Nat) (ρp : Nat → V), Sat V (D.params ψ).reverse ρp → D.w ψ ≠ 0 →455
∀ X, InTupleSpace (D.w ψ) D.N (D.idx ψ ρp) X → ∀ c, c < D.N → ∀ j, j < D.nctors c →456
FieldsOkB (D.w ψ) (D.frame ψ ρp X) (D.fields ψ c j)); the fragment reads it off the constructor’s ordinary typing.
4.2.4. The least fixed point
A family is closed under whitepaper/Fragment/LfpSet.lean:L64-L6764/-- A **closed family** of `Φ`: in the universe, and a pre-fixed point.
65Con-leche: `IsClosedTuple`. -/
66def IsClosedFam (n : Nat) (Φ : (ι → V) → ι → V) (W : ι → V) : Prop :=
67 InUniv n W ∧ FamLe (Φ W) W
, in , when every fibre of is a member of and fibrewise.
/-- **A closed family from accessibility.** At a positive universe,293
an operator that maps families in the universe to families in the294
universe and is accessible with a bound `A` in the universe has a295
closed family in the universe: the limit family. An element `Φ`296
produces from it has a support, each occurrence in some stage; the297
node whose subtree at `a` is a tree of the occurrence `g a`'s stage298
has that support in the union of its subtrees' stages, so its stage299
produces the element, which is therefore in the limit. No300
monotonicity is used. Con-leche: `closed_of_acc`,301
`ConLeche/SetModel/Access.lean`. -/302
theorem closed_of_acc (hn : n ≠ 0) (hA : A ∈ˢ (univ n : V)) (hmaps : MapsFam n Φ)303
(hacc : AccFam n Φ A) : ∃ L, IsClosedFam n Φ L := byin (as in con-lecheConLeche/SetModel/Access.lean:L240-L244240
/-- **(W) from accessibility.** At a positive level, an operator that241
maps the tuple space into itself and is `A`-accessible for a set `A` of242
the level has a closed tuple. No monotonicity is used. -/243
theorem closed_of_acc (hw : w ≠ 0) (hA : A ∈ˢ (tg w : V)) (hmaps : MapsTuple w k Is Φ)244
(hacc : AccTuple w k Is k Is Φ A) : ∃ L, IsClosedTuple w k Is Φ L := by).
/-- Lean-level well-founded trees branching over the sets: a leaf, or164
a node with a subtree for every set. Con-leche: `BT`. -/165
inductive BT (V : Type u) : Type u166
| leaf : BT V167
| node : (V → BT V) → BT V168
169
namespace AccIter170
171
variable (Φ : (ι → V) → ι → V) (A : V)172
173
/-- The fibrewise union of an `A`-indexed family of families.174
Con-leche: `unionTup`. -/175
noncomputable def unionFam (F : V → ι → V) : ι → V := fun i => famUnion A fun a => F a i176
177
/-- **The stage at a tree**: the empty family at a leaf, `Φ` of the178
union of the subtrees' stages at a node. Con-leche: `stage`. -/179
noncomputable def stage : BT V → ι → V180
| .leaf => fun _ => empty181
| .node f => Φ (unionFam A fun a => stage (f a))182
183
/-- **A tree's code**: its set of paths over `A` — the empty path at a184
node, and a step `a` onto every path of the subtree at `a`.185
Con-leche: `code`. -/186
noncomputable def code : BT V → V187
| .leaf => empty188
| .node f => binUnion one (famUnion A fun a => image (pcons a) (code (f a)))189
190
open Classical in191
/-- A tree with the given code, if any. Con-leche: `pick`. -/192
noncomputable def pick (c : V) : BT V :=193
if h : ∃ T, code A T = c then Classical.choose h else .leaf194
195
/-- **The limit family**: the union of the stages over all codes.196
Con-leche: `limTup`. -/197
noncomputable def limFam : ι → V :=198
fun i => famUnion (power (accPaths A)) fun c => stage Φ A (pick A c) ibranching over all sets (a tree is a leaf, or a node with a subtree for every set). The stage at a leaf is the empty family, the stage at a node is of the union over of its subtrees’ stages, and the closed family is the union of the stages over all trees. It is closed by accessibilitywhitepaper/Fragment/Access.lean:L304-L320304
refine ⟨limFam Φ A, limFam_mem hn hA hmaps, ?_⟩305
intro i x hx306
obtain ⟨B, g, hBA, hgL, hsupp⟩ := hacc _ (limFam_mem hn hA hmaps) i x hx307
-- every occurrence of the support lies in some stage: choose its tree308
have hT : ∀ a, a ∈ˢ B → ∃ T : BT V, (g a).2 ∈ˢ stage Φ A T (g a).1 :=309
fun a ha => mem_limFam.mp (hgL a ha)310
classical311
let f : V → BT V := fun a => if h : a ∈ˢ B then Classical.choose (hT a h) else .leaf312
have hstage : x ∈ˢ stage Φ A (.node f) i := by313
simp only [stage]314
refine hsupp _ (fun i' => famUnion_mem_univ hn hA fun a _ => stage_mem hn hA hmaps (f a) i') ?_315
intro a ha316
refine mem_unionFam.mpr ⟨a, hBA a ha, ?_⟩317
have : f a = Classical.choose (hT a ha) := dite_eq_left ha318
rw [this]319
exact Classical.choose_spec (hT a ha)320
exact mem_limFam.mpr ⟨_, hstage⟩: an element produces from it has a support of at most -many occurrences, each in some stage, and the node whose subtree at is a tree of the occurrence ‘s stage produces the element at its own stage. It is in the universe because every stage is ( preserves the universe, which is closed under -indexed unions), and because the union over all trees is a union over a set. A stage depends on its tree only through the tree’s code, its set of finite paths over , and the codes are members of the power set of all finite paths over , itself a countable union of members of the universe (the codewhitepaper/Fragment/Access.lean:L183-L188183
/-- **A tree's code**: its set of paths over `A` — the empty path at a184
node, and a step `a` onto every path of the subtree at `a`.185
Con-leche: `code`. -/186
noncomputable def code : BT V → V187
| .leaf => empty188
| .node f => binUnion one (famUnion A fun a => image (pcons a) (code (f a))), the stage depends on the code onlywhitepaper/Fragment/Access.lean:L221-L223221
/-- **The stage depends on the tree only through its code.**222
Con-leche: `stage_eq_of_code`. -/223
theorem stage_eq_of_code : ∀ T T' : BT V, code A T = code A T' → stage Φ A T = stage Φ A T', the limit is in the universewhitepaper/Fragment/Access.lean:L284-L290284
/-- The limit family is in the universe: a union over the codes, a285
member of the universe as the power set of the paths. Con-leche:286
`limTup_mem`. -/287
theorem limFam_mem (hn : n ≠ 0) (hA : A ∈ˢ (univ n : V)) (hmaps : MapsFam n Φ) :288
InUniv n (limFam Φ A) := fun i =>289
famUnion_mem_univ hn (power_mem_univ hn (accPaths_mem_univ hn hA)) fun _ _ =>290
stage_mem hn hA hmaps _ i). □
With a closed family in hand, the least fixed point is a set construction, inside the set theory: fibrewise the intersection of the closed familieswhitepaper/Fragment/LfpSet.lean:L82-L8982/-- **The least fixed point of `Φ` inside the set theory**: fibrewise
83the intersection of the closed families when there is one, separated
84from a chosen closed family `L₀`; the empty family otherwise.
85Con-leche: `lfpTuple`, `ConLeche/SetTheory/Derive/LfpTuple.lean`. -/
86noncomputable def lfpFamSet (n : Nat) (Φ : (ι → V) → ι → V) : ι → V :=
87 if h : ∃ L, IsClosedFam n Φ L then
88 fun i => sep (Classical.choose h i) fun x => ∀ X, IsClosedFam n Φ X → x ∈ˢ X i
89 else fun _ => empty
, separated from ,
Its fibres are members of whitepaper/Fragment/LfpSet.lean:L111-L113111/-- **Formation, unconditional**: the fibres of the least fixed point
112are members of the universe. Con-leche: `lfpTuple_mem`. -/
113theorem lfpFamSet_mem (n : Nat) (Φ : (ι → V) → ι → V) : InUniv n (lfpFamSet n Φ) := by
, as separations from members; it lies below every closed familywhitepaper/Fragment/LfpSet.lean:L106-L108106/-- **Leastness**: the least fixed point lies below every closed
107family. Con-leche: `lfpTuple_le`. -/
108theorem lfpFamSet_least {X : ι → V} (hX : IsClosedFam n Φ X) : FamLe (lfpFamSet n Φ) X :=
; and when is monotone and maps to itself it is a fixed pointwhitepaper/Fragment/LfpSet.lean:L137-L141137/-- **The fixed-point equation**, fibrewise. Con-leche:
138`lfpTuple_eq`. -/
139theorem lfpFamSet_eq (h : ∃ L, IsClosedFam n Φ L) (hmono : MonoFam n Φ) (hmaps : MapsFam n Φ)
140 (i : ι) : Φ (lfpFamSet n Φ) i = lfpFamSet n Φ i :=
141 Sub.antisymm (lfpFamSet_closed h hmono i) (lfpFamSet_fixed h hmono hmaps i)
, , and supports inductionwhitepaper/Fragment/LfpSet.lean:L154-L160154/-- **Induction**: a property closed under `Φ` on the family's
155separation by it holds on the whole family — the separation is a
156closed family, so the least fixed point lies below it. Con-leche:
157`lfpTuple_induction`. -/
158theorem lfpFamSet_induction (h : ∃ L, IsClosedFam n Φ L) (hmono : MonoFam n Φ) (P : ι → V → Prop)
159 (hP : ∀ i x, x ∈ˢ Φ (sepFam n Φ P) i → P i x) :
160 ∀ i x, x ∈ˢ lfpFamSet n Φ i → P i x := by
: a property that holds of every element produces from the family’s separation by it holds on the whole family, because the separation is a closed family and the least one lies below it.
4.2.5. The family and the constructors
The block’s operator has a closed family in : for a type-valued family by Theorem 8, for a proposition-valued one the constant family (the closed familywhitepaper/Fragment/IndSem.lean:L1443-L14491443/-- **A closed family in the universe**: at a proposition the family
1444of truth values `{pt}` (`closedFam_zero`); above one, from
1445accessibility (`closed_of_acc`). Con-leche: `LfpClause.functor`'s
1446third conjunct, `closedTuple_zero` and `closed_of_acc`. -/
1447theorem closedFam {ps : List V} (hnr : S.NoRecDep) (hb : S.DomsBounded M ls ps)
1448 (hco : S.ContOk M ls ps) : ∃ L, IsClosedFam (S.u₀ ls) (S.famOp M ls ps) L := by
1449 cases hz : S.z ls with
). The family of the block is its least fixed point,
(the familywhitepaper/Fragment/IndSem.lean:L1458-L14611458/-- **The family**: the fibre at the index values `is` is the least
1459fixed point of the block's operator inside the set theory.
1460Con-leche: `LfpDatum.carrier`, `lfpTuple` of the hole operator. -/
1461noncomputable def Fam (ps is : List V) : V := lfpFamSet (S.u₀ ls) (S.famOp M ls ps) is
, the type former’s setwhitepaper/Fragment/IndSem.lean:L1641-L16431641/-- **The type former's set** at `ls`: the graph over the index and
1642parameter contexts whose value is the fibre. -/
1643noncomputable def famSet : V := S.famSetF M ls (S.Fam M)
, its graph over the parameters and indices). Its fibres are typeswhitepaper/Fragment/IndSem.lean:L1561-L15651561/-- **The fibre is a type of the result sort** (`typeU`): at a
1562proposition a truth value; above it a set of tagged values, which is
1563strict (no tagged value is the point or a member of it). -/
1564theorem Fam_mem_typeU {ps : List V} (hnr : S.NoRecDep) (hb : S.DomsBounded M ls ps)
1565 (hco : S.ContOk M ls ps) (is : List V) : S.Fam M ls ps is ∈ˢ (typeU (S.u₀ ls) : V) := by
of (above), which is law 1 for the type formerwhitepaper/Fragment/InstallInd.lean:L555-L562555/-- **The former's type law**: its type is a well-denoted type and its
556set is a member — the fibres are types (`Fam_mem_typeU`). -/
557theorem type_ok_ind (φ : Name → Nat) (ρ : Nat → V) {ls : List Level}
558 (hls : ls.length = S.indInfo.lparams.length) :
559 WellDenoted (S.M₃ m.M) φ ρ (S.indInfo.type.instL S.indInfo.lparams ls) ∧
560 IsTy (interp (S.M₃ m.M) φ ρ (S.indInfo.type.instL S.indInfo.lparams ls)) ∧
561 S.M₃ m.M S.name (ls.map (Level.eval φ)) ∈ˢ
562 interp (S.M₃ m.M) φ ρ (S.indInfo.type.instL S.indInfo.lparams ls) := by
. A type-valued fibre is a set of tagged tuples, hence strict, and a proposition-valued fibre is a truth value, so either way the fibre is a type of the result sortwhitepaper/Fragment/IndSem.lean:L1561-L15641561/-- **The fibre is a type of the result sort** (`typeU`): at a
1562proposition a truth value; above it a set of tagged values, which is
1563strict (no tagged value is the point or a member of it). -/
1564theorem Fam_mem_typeU {ps : List V} (hnr : S.NoRecDep) (hb : S.DomsBounded M ls ps)
. A member of a type-valued fibre is a tagged tuple and carries its constructor and its fields; a member of a proposition-valued fibre is the point and carries nothing.
The rest of this section rests on the fixed-point equationwhitepaper/Fragment/IndSem.lean:L1471-L14771471/-- **The fixed-point equation**: the operator at the family is the
1472family. Con-leche: `LfpClause.carrier_eq`. -/
1473theorem famOp_Fam {ps : List V} (hnr : S.NoRecDep) (hb : S.DomsBounded M ls ps)
1474 (hco : S.ContOk M ls ps) (is : List V) :
1475 S.famOp M ls ps (S.Fam M ls ps) is = S.Fam M ls ps is :=
1476 lfpFamSet_eq (S.closedFam M ls hnr hb hco) (S.famOp_mono M ls hnr hco) (S.famOp_maps M ls hb hco) is
1477
, read in both directions. A member of the fibre at is a constructor valuewhitepaper/Fragment/IndSem.lean:L1478-L14861478/-- **The family's case analysis**: a member of the fibre at `is` is
1479a constructor instance over the family at `is`, and conversely.
1480Con-leche: `LfpClause.carrier_case`. -/
1481theorem mem_Fam {ps : List V} (hnr : S.NoRecDep) (hb : S.DomsBounded M ls ps)
1482 (hco : S.ContOk M ls ps) {is : List V} {t : V} :
1483 t ∈ˢ S.Fam M ls ps is ↔ S.Inst M ls (S.Fam M ls ps) ps is t := by
1484 rw [← S.famOp_Fam M ls hnr hb hco]
1485 exact S.mem_famOp M ls
1486
whose fields fit the family and whose index expressions read as , and conversely. The converse is law 1 for the constructors: denotes the function that takes the parameters and the fields and returns (a constructor’s setwhitepaper/Fragment/IndSem.lean:L1656-L16611656/-- **A constructor's set**: the abstraction over the parameters and
1657fields (read in the model with the former) of the constructor's
1658value. -/
1659noncomputable def ctorSet (j : Nat) (c : CtorSpec) : V :=
1660 lamCtx (S.M₁ M) (S.ψ ls) base (S.fieldCtx c.fields ++ S.params) fun ρ' =>
1661 S.ctorVal ls j (readEnv c.fields.length ρ')
), a value in the fibrewhitepaper/Fragment/IndSem.lean:L1487-L14961487/-- **A constructor's value is in the fibre** at its index expressions
1488when its fields fit the family. Con-leche: `LfpClause.ctor` with the
1489carrier's closure. -/
1490theorem ctorVal_mem_Fam {ps : List V} (hnr : S.NoRecDep) (hb : S.DomsBounded M ls ps)
1491 (hco : S.ContOk M ls ps) {j : Nat}
1492 {c : CtorSpec} (hc : S.ctors[j]? = some c) {fs : List V}
1493 (hfit : S.FitsFields M ls (S.Fam M ls ps) ps c.fields fs) :
1494 S.ctorVal ls j fs ∈ˢ S.Fam M ls ps (S.idxVals M ls (consList fs (envP ps)) c.idx) :=
1495 (S.mem_Fam M ls hnr hb hco).mpr ⟨j, c, fs, hc, hfit, rfl, rfl⟩
1496
at its index expressions whenever its fields fit (the lawwhitepaper/Fragment/InstallInd.lean:L779-L785779/-- **A constructor's type law.** -/
780theorem type_ok_ctor {j : Nat} {c : CtorSpec} (hc : S.ctors[j]? = some c) (φ : Name → Nat)
781 (ρ : Nat → V) {ls : List Level} (hls : ls.length = (S.ctorInfo c).lparams.length) :
782 WellDenoted (S.M₃ m.M) φ ρ ((S.ctorInfo c).type.instL (S.ctorInfo c).lparams ls) ∧
783 IsTy (interp (S.M₃ m.M) φ ρ ((S.ctorInfo c).type.instL (S.ctorInfo c).lparams ls)) ∧
784 S.M₃ m.M c.name (ls.map (Level.eval φ)) ∈ˢ
785 interp (S.M₃ m.M) φ ρ ((S.ctorInfo c).type.instL (S.ctorInfo c).lparams ls) := by
). The forward direction is the inversion the law needs. The least fixed point’s induction principle is induction over the familywhitepaper/Fragment/IndSem.lean:L1497-L15071497/-- **Induction over the family**: a property that holds of every
1498constructor instance over the family's separation by it holds on the
1499family. Con-leche: `lfpTuple_induction`, the block's induction the
1500recursor's graph is built with. -/
1501theorem Fam_induction {ps : List V} (hnr : S.NoRecDep) (hb : S.DomsBounded M ls ps)
1502 (hco : S.ContOk M ls ps) (P : List V → V → Prop)
1503 (h : ∀ is x, S.Inst M ls (fun is' => sep (S.Fam M ls ps is') (P is')) ps is x → P is x) :
1504 ∀ is x, x ∈ˢ S.Fam M ls ps is → P is x :=
1505 lfpFamSet_induction (S.closedFam M ls hnr hb hco) (S.famOp_mono M ls hnr hco) P fun is x hx =>
1506 h is x ((S.mem_famOp M ls).mp hx)
1507
, on which the recursor is built. For a proposition-valued family every constructor value is the point, so the fibre is the truth valuewhitepaper/Fragment/IndSem.lean:L1539-L15431539/-- At a proposition a member of the fibre is the point and some
1540decoding exists. -/
1541theorem mem_Fam_true {ps : List V} (hnr : S.NoRecDep) (hb : S.DomsBounded M ls ps)
1542 (hco : S.ContOk M ls ps) (hz : S.z ls = true) {is : List V} {t : V} :
1543 t ∈ˢ S.Fam M ls ps is ↔ t = pt ∧ ∃ x, S.Mem M ls ps is x := by
of “some constructor reaches these indices”, and every constructor denotes the point.
/-- **The least pre-fixed tuple of `Φ`** — componentwise, fibrewise the79
intersection of the closed tuples when there is one (separated from a80
chosen closed tuple), dropped to `∅` if it left `strictU w`; the empty81
tuple otherwise. -/82
noncomputable def lfpTuple (w k : Nat) (Is : Nat → V) (Φ : (Nat → V) → Nat → V) : Nat → V :=83
fun m =>84
if h : ∃ L, IsClosedTuple w k Is Φ L then85
famOf (fun i => dropTo (strictU w) (sep (app (Classical.choose h m) i)86
(fun x => ∀ X, IsClosedTuple w k Is Φ X → x ∈ˢ app (X m) i))) (Is m)87
else famOf (fun _ => empty) (Is m)over a tuple of families, one per type former of the block, each a graph over an index set. What the model records of an installed block is one clause: the operator maps the universe to itself and its fit is accessible, for both kinds of family (the operatorConLeche/Model/Env/BlockLfp.lean:L388-L395388
/-- **`Φ` maps the tuple space into itself** -/389
maps : ∀ (ψ : Name → Nat) (ρp : Nat → V), Sat V (D.params ψ).reverse ρp →390
MapsTuple (D.w ψ) D.N (D.idx ψ ρp) (D.Φ ψ ρp)391
/-- **the hole fit is accessible**, with a bound of the level at a392
`Type`-valued block (task #327): the operator's accessibility, (W) and393
monotonicity, and the fit's growth follow at every level -/394
fitAcc : ∀ (ψ : Name → Nat) (ρp : Nat → V), Sat V (D.params ψ).reverse ρp →395
∃ A, (D.w ψ ≠ 0 → A ∈ˢ (tg (D.w ψ) : V)) ∧ D.FitAcc ψ ρp A). So it has a closed family (the constant family at a proposition, and from accessibility for a type-valued familyConLeche/SetModel/Access.lean:L298-L303298
theorem AccW.closed {w k : Nat} {Is : Nat → V} {Φ : (Nat → V) → Nat → V}299
(h : AccW w k Is Φ) (hmaps : MapsTuple w k Is Φ) : ∃ L, IsClosedTuple w k Is Φ L := by300
by_cases hw : w = 0301
· subst hw; exact closedTuple_zero hmaps302
· obtain ⟨A, hA, hacc⟩ := h303
exact closed_of_acc hw (hA hw) hmaps hacc) and is monotoneConLeche/Model/Env/BlockLfp.lean:L511-L515511
/-- **The operator is monotone**, from its accessibility (`AccW.mono`). -/512
theorem mono (h : LfpClause acval D) {ψ : Name → Nat} {ρp : Nat → V}513
(hsat : Sat V (D.params ψ).reverse ρp) :514
MonoTuple (D.w ψ) D.N (D.idx ψ ρp) (D.Φ ψ ρp) :=515
(h.acc ψ ρp hsat).mono; its fibres are the tagged tuples of fitting fields (the fibresConLeche/Model/Env/BlockLfp.lean:L396-L401396
/-- **what `Φ` is**: component `c`'s fibre at `(X, t)` is the set of397
injections of the spines fitting one of `c`'s constructors -/398
fibre : ∀ (ψ : Name → Nat) (ρp : Nat → V), Sat V (D.params ψ).reverse ρp →399
∀ X, InTupleSpace (D.w ψ) D.N (D.idx ψ ρp) X → ∀ c, c < D.N →400
∀ t, t ∈ˢ D.idx ψ ρp c → ∀ x,401
x ∈ˢ app (D.Φ ψ ρp X c) t ↔ ∃ j fs, D.HFits ψ ρp X t c j fs ∧ x = D.inj ψ c j fs), and the type former denotes the least fixed pointConLeche/Model/Env/BlockLfp.lean:L181-L183181
/-- **The carrier**: the least pre-fixed tuple of the operator. -/182
@[expose] noncomputable def carrier (ψ : Name → Nat) (ρp : Nat → V) : Nat → V :=183
lfpTuple (D.w ψ) D.N (D.idx ψ ρp) (D.Φ ψ ρp)(the fixed-point equationConLeche/Model/Env/BlockLfp.lean:L517-L520517
/-- **The carrier is a fixed point**, componentwise. -/518
theorem carrier_eq (h : LfpClause acval D) {ψ : Name → Nat} {ρp : Nat → V}519
(hsat : Sat V (D.params ψ).reverse ρp) {c : Nat} (hc : c < D.N) :520
D.Φ ψ ρp (D.carrier ψ ρp) c = D.carrier ψ ρp c :=). Its constructors are tagged pairsConLeche/SetModel/TaggedSum.lean:L6565
noncomputable def inj (i : Nat) (a : V) : V := spair (vnat i) aof nested pairsConLeche/SetModel/TupleTower.lean:L9393
noncomputable def mkTower : List V → V, the two kinds of family in one carrierConLeche/SetModel/TaggedSum.lean:L61-L6261
noncomputable def sumSet (w : Nat) (f : Nat → V) : V :=62
sigmaSet w omega (natFibre f).
4.2.6. The recursor
Fix values for the motive and for the minor premises. The recursor’s value on a member of the family is determined by the rules. On it must be applied to and to the inductive hypotheses; at each reflexive field the hypothesis is the function sending to the recursor’s own value at , and at the empty telescope it is just the recursor’s value at . That this equation has exactly one solution is the recursion theorem. In the fragment it is proved by the same device as the family. The recursor’s graph, the relation “the value at is ”, is the least fixed pointwhitepaper/Fragment/IndRec.lean:L89-L9289/-- **The recursor's graph**: the least fixed point of `rstep`.
90Con-leche: `gGraph`. -/
91noncomputable def RecGraph (ps : List V) (m : V) (mins is : List V) (t v : V) : Prop :=
92 Lfp (S.rstepT M ls) (ps, m, mins, is, t, v)
of the operator that reads the equation as a stepwhitepaper/Fragment/IndRec.lean:L73-L8173/-- **The operator** whose least fixed point is the recursor's graph:
74at the constructor value of fitting fields, the value is the
75constructor's minor at the fields and the inductive hypotheses.
76Con-leche: the graph functor `gStep` (`GraphRec.lean`). -/
77noncomputable def rstep (R : RecP V) (ps : List V) (m : V) (mins is : List V) (t v : V) : Prop :=
78 ∃ j c fs ihs, S.ctors[j]? = some c ∧ S.FitsFields M ls (S.Fam M ls ps) ps c.fields fs ∧
79 is = S.idxVals M ls (consList fs (envP ps)) c.idx ∧ t = S.ctorVal ls j fs ∧
80 ListRel (S.IhOk M ls R ps m mins fs) c.recFields ihs ∧
81 v = appList (S.minorAt mins j) (fs.reverse ++ ihs)
, a least fixed point on predicates this time, which the ambient logic’s impredicative provides outrightwhitepaper/Fragment/IndLib.lean:L101-L104101/-- **The least fixed point** of an operator on predicates: the
102intersection of every predicate closed under it. -/
103def Lfp {α : Sort _} (Φ : (α → Prop) → (α → Prop)) : α → Prop :=
104 fun a => ∀ P : α → Prop, (∀ b, Φ P b → P b) → P a
. It is single-valuedwhitepaper/Fragment/IndRec.lean:L155-L159155/-- **Single-valuedness of the graph**: two values at one major come
156from one decoding, and their inductive hypotheses agree by induction
157over the graph. Con-leche: the uniqueness half of `GraphRecKit.exu`. -/
158theorem RecGraph_fun (hu : S.Uniq M ls) {ps : List V} (hp : FitsVals M (S.ψ ls) base S.params ps)
159 {m : V} {mins is : List V} {t v v' : V}
by induction over the graph, using that tagged tuples are injective, and totalwhitepaper/Fragment/IndRec.lean:L213-L219213/-- **Totality of the graph on the family** (with single-valuedness,
214the recursion theorem): by induction over the family — at a
215constructor instance over the family's separation by "the graph has a
216value", every application of a reflexive field has one, and the
217inductive hypotheses' semantic values build the instance's.
218Con-leche: the existence half of `GraphRecKit.exu`, by `ind`. -/
219theorem RecGraph_total {ps : List V} (hpl : S.nest = none) (hnr : S.NoRecDep)
by induction over the family.
When the family is proposition-valued and the motive is not, the major is the point and carries no fields. A major decodes as a constructor with fields when it is the value of applied to fields that fit and whose index expressions read as the indices ; the pair is a decoding of the major. The point may have several decodings, and the equation has one instance per decoding. The graph is single-valued only because any two decodings agreewhitepaper/Fragment/IndRec.lean:L131-L141131/-- **Two decodings of one value agree**: above a proposition the
132value is the tagged tuple of either (tags and tuples are injective);
133at a proposition by uniqueness of decodings. -/
134theorem decode_unique (hu : S.Uniq M ls) {ps : List V} (hp : FitsVals M (S.ψ ls) base S.params ps)
135 {is : List V} {j j' : Nat} {c c' : CtorSpec} {fs fs' : List V}
136 (hc : S.ctors[j]? = some c) (hc' : S.ctors[j']? = some c')
137 (hfit : S.FitsFields M ls (S.Fam M ls ps) ps c.fields fs)
138 (hfit' : S.FitsFields M ls (S.Fam M ls ps) ps c'.fields fs')
139 (his : is = S.idxVals M ls (consList fs (envP ps)) c.idx)
140 (his' : is = S.idxVals M ls (consList fs' (envP ps)) c'.idx)
141 (ht : S.ctorVal ls j fs = S.ctorVal ls j' fs') : j = j' ∧ fs = fs' := by
: under a large eliminator the subsingleton criterion as the checker states itwhitepaper/Fragment/InstallInd.lean:L733-L738733/-- **Uniqueness of decodings** under the subsingleton
734criterion: at a proposition with a large eliminator the checker admits
735at most one constructor, every field of which is a proposition or one
736of the indices. -/
737theorem uniq_of (φ : Name → Nat) (hz : S.z (S.lparams.map φ) = true) (hlarge : S.large = true) :
738 S.Uniq m.M (S.lparams.map φ) := by
reads semanticallywhitepaper/Fragment/Uniq.lean:L40-L5040/-- **The subsingleton criterion, semantically**: at a proposition,
41at most one constructor, and every field of a fitting instance is
42the point (its domain is a proposition) or is read off the
43constructor's index expressions (the field's variable is one of
44them). -/
45def Subsingleton : Prop :=
46 S.ctors.length ≤ 1 ∧
47 ∀ c ∈ S.ctors, ∀ ps : List V, FitsVals M (S.ψ ls) base S.params ps → ∀ fs : List V,
48 S.FitsFields M ls (S.Fam M ls ps) ps c.fields fs →
49 ∀ k, k < c.fields.length →
50 fieldVal fs k = pt ∨ Expr.bvar (c.fields.length - 1 - k) ∈ c.idx
as uniqueness of decodingswhitepaper/Fragment/Uniq.lean:L60-L6460/-- **Uniqueness from the subsingleton criterion**: two decodings
61at one index are equal — each is the tagged tuple of
62fields every one of which is the point or one of the (shared) index
63values. -/
64theorem uniq_of_subsingleton (hsub : S.Subsingleton M ls) : S.Uniq M ls := by
, as Theorem 10′s proof shows.
The recursor denotes the graph of the resulting functionwhitepaper/Fragment/IndRec.lean:L394-L408394/-- **The recursor's set** at its own levels `lsr` (the elimination
395level in front, if large): the abstraction over its context — the
396parameters, the motive, the minors, the indices, the major — of the
397recursor's semantic value, the family read at the block's levels.
398Into `Prop` (`qv`) the body is the point outright: the motive's fibres
399are truth values, and the abstraction is read by its values,
400so a body that is not the point would make it a graph. -/
401noncomputable def recSet (lsr : List Nat) : V :=
402 let ψr := valOf S.recLparams lsr
403 let ls := S.lparams.map ψr
404 lamCtx (S.M₂ M) ψr base
405 (S.famVars (S.n + 1) :: S.indicesAt (S.n + 1) ++ S.minorsCtx ++ [S.motiveTy] ++ S.params)
406 fun ρ' => if S.qv ψr then pt else
407 S.recSem M ls (readEnv S.nP (shiftE (1 + S.nI + S.n + 1) 0 ρ')) (ρ' (1 + S.nI + S.n))
408 (readEnv S.n (shiftE (1 + S.nI) 0 ρ')) (readEnv S.nI (shiftE 1 0 ρ')) (ρ' 0)
(its valuewhitepaper/Fragment/IndRec.lean:L181-L185181/-- **The recursor's semantic value** at parameters, motive, minors,
182indices and the major: the one the graph relates, if any.
183Con-leche: `recSel`, the graph's selector. -/
184noncomputable def recSem (ps : List V) (m : V) (mins is : List V) (t : V) : V :=
185 open Classical in
), curried over the parameters, the motive, the minors, the indices and the major. It is a member of its generated typewhitepaper/Fragment/InstallInd.lean:L1054-L10571054theorem recSet_mem (φ : Name → Nat) (ρ : Nat → V) {lsr : List Level}
1055 (hlsr : lsr.length = S.recLparams.length) :
1056 S.recSet m.M (lsr.map (Level.eval φ)) ∈ˢ
1057 interp (S.M₃ m.M) (Level.substVal φ S.recLparams lsr) ρ S.recType := by
, which is law 1 for the recursor (the recursor’s typingwhitepaper/Fragment/IndRec.lean:L316-L325316/-- **The recursor's typing**: at a member of the fibre, the
317recursor's value lies in the motive at the indices and the member —
318by induction over the family, from the minors' typing. -/
319theorem recSem_mem {ps : List V} (hpl : S.nest = none) (hnr : S.NoRecDep) (hb : S.DomsBounded M ls ps)
320 (hu : S.Uniq M ls)
321 (hp : FitsVals M (S.ψ ls) base S.params ps) (m : V) (mins : List V)
322 (hmin : ∀ j c, S.ctors[j]? = some c → S.MinorOk M ls ps m mins j c)
323 (hmo : ∀ is t, t ∈ˢ S.Fam M ls ps is → IsTy (appList m (is.reverse ++ [t])))
324 {is : List V} {t : V} (ht : t ∈ˢ S.Fam M ls ps is) :
325 S.recSem M ls ps m mins is t ∈ˢ appList m (is.reverse ++ [t]) := by
, by induction over the family from the minors’). When the elimination level is zero at the valuation, the motive’s values are truth values and the recursor’s value is the point at every argument. Then so is the recursor’s set, since an abstraction is read by its values, and there is nothing to construct.
/-- **The recursor family's graph**: the least fixed point of the graph101
functor, over families of the axiomatic level `tg ℓ` — a relation,102
whose fibres are sets of values, not types (no drop ever fires: the103
level is closed under subsets). -/104
noncomputable def gGraph : V := lfpFamIn (tg ℓ) U (gStep ℓ U Dec pred B st)closed under the rules, each read over the decodings of a major. The graph has exactly one value at every majorConLeche/SetModel/GraphRec.lean:L232-L234232
/-- **The recursion theorem, from induction**: the graph has exactly233
one value at every major. -/234
theorem exu : ∀ u, u ∈ˢ U → Single K.G u := byby the family’s own induction, and the equation at any decoding is the lawConLeche/SetModel/GraphRec.lean:L270-L274270
/-- **The ι law, at ANY decoding**: the recursor at a major is the271
step at that decoding, the `ih`s being the recursor at the272
predecessors. -/273
theorem rec_eq {u : V} {d : R} (hu : u ∈ˢ U) (hd : K.Dec u d) :274
K.recAt u = K.st d (graph (fun j => K.recAt j) (K.pred d)) := by. The one fact about sorts it asks is a premise: at every major, two decodings are equal, or the motive’s value there has at most one memberConLeche/SetModel/GraphRec.lean:L190-L192190
/-- **the sort-dependent fact**: decodings are unique, or the bound is a subsingleton -/191
huniq : ∀ u, u ∈ˢ U → ∀ d d', Dec u d → Dec u d' →192
d = d' ∨ ∀ v v', v ∈ˢ B u → v' ∈ˢ B u → v = v'. This is the elimination rule of Section 4.1, read three ways (the three casesConLeche/Model/Inductives/ClassGenUniq.lean:L13-L1913
* **`huniq`** (`genUniq`): two decodings of a major are equal, or the14
motive's value at it is a subsingleton. At `ℓ = 0` the bound is a truth15
value (from `hconclTy`); at a nonzero width the class's recorded clause16
injection is injective (`LfpClause.mkInj`), given the class's fit is the17
clause's hole fit at its carrier and its injection the clause's; at a18
`Prop`-valued class under a large eliminator a per-major PREMISE (fits at19
one index tuple coincide — the elimination guard's criterion)., the theoremConLeche/Model/Inductives/ClassGenUniq.lean:L5757
theorem genUniq (D : Nat → LfpDatum V) (ψ : Nat → Name → Nat) (fr : Nat → Nat → V)). At the motive’s values are truth values; at a type-valued family, tagged tuples are injective; at a proposition-valued family with a large eliminator, the subsingleton criterion makes the decoding a function of the index, which is the fragment’s uniqueness of decodings as one premise.
4.2.7. The rule
The rule fireswhitepaper/Fragment/Rules.lean:L95-L174 95 /-- **ι** (`Red.iota`, `Rel.lean:200`): a stored recursor applied to
96 exactly its telescope — `numParams` parameters, `numMotives` motives,
97 `numMinors` minor premises, `numIndices` indices and the major
98 premise — whose major reduces to a constructor application with a
99 rule, fires the rule: the rule's right-hand side, at the use's
100 levels, applied to the parameters, motives and minors, then to the
101 constructor's fields (its arguments after the parameters). The rule
102 is one of the block's own constructors' (no stored instantiation,
103 `RecRule.inst`); an auxiliary recursor's rule fires by
104 `Red.iotaNested`.
105
106 The **certificates** are the fragment's form of `Rel.lean`'s `CertsF`
107 and `piResidual` premises (`:227-236`). The recursor's spine before the
108 major needs none: a reduction claims nothing of a redex that is the
109 point, and a redex that is not the point fits the recursor's telescope
110 by the invariant's application slots (`teleFit_of_ne_pt`). The reduced
111 major's slot is certified — its type inferred and found definitionally
112 equal to the domain the recursor's telescope leaves after the spine
113 (`Expr.piResidual`) — and so is the constructor's argument spine against
114 its stored type's `Π`-telescope (`Expr.piDomains`; the stored types have
115 the spine's length of syntactic binders, `Expr.hasPis`). Both are
116 needed only where the major may denote the point (a family that may be a
117 proposition): where the major's domain is strict, `iota_core_strict`
118 (`Sound.lean`) needs neither; where the constructor's family is a
119 proposition at every valuation, `Red.iotaProp` needs no slot
120 certificate. They make the environment's ι law (`EnvModel.lean`,
121 `RecRuleLaw`) applicable: they put every argument in its domain.
122
123 The **three comparisons** after them are `Rel.lean`'s too (`:213-241`,
124 there in the certified case only): the constructor's levels are the
125 recursor's (its last `usj.length` ones — a large eliminator carries
126 one extra level in front), the constructor's parameters are
127 definitionally the recursor's, and the constructor's residual type
128 (`Expr.piResidual`: the family at the constructor's own parameters and
129 index expressions, with the fields substituted in) has index
130 expressions definitionally equal to the recursor's index arguments. In
131 the model they matter exactly when the family is a proposition: there
132 the major denotes the one point, the certificates say only that the
133 fibre is inhabited, and the fields the rule's right-hand side receives
134 can be related to the recursor's own parameters and indices through
… 40 more lines on where is a stored recursor, are its parameters, motive, minors and indices, and the major reduces to a constructor application for which has a rule with right-hand side . The reduct is applied to the parameters, motive and minors from and to the fields (as in con-lecheConLeche/Rules/Rel.lean:L186-L243186 /-- **ι** (`iotaRec`, `Kernel/Core.lean`): a stored recursor
187 applied to exactly its telescope, whose prepared major (the `Red`
188 premise: `prepareMajor`'s whnf / literal / rescue chain) is a
189 constructor application with a firing rule. The recursor
190 telescope's syntactic arity over the redex's own spine, and (case 3
191 only) the level and parameter comparisons with the rule's comparands,
192 the constructor's full-grade telescope certificate and the index
193 comparison are the checker's, in its order.
194
195 **The ι case split** (task #342 L10, S5c): `iotaCase3` at the comparand
196 levels is the checker's test. A K rule, and a rule whose
197 constructor's family is never `Prop` at the comparand levels (case 2),
198 carry no premise about the constructor application; case 3 carries
199 all four. -/
200 | iota {d : Nat} {e : Expr} {c : Name} {us : List Level} {cv : ConstantVal}
201 {mI rP : Nat} {rules : List RecRule} {major : Expr} {cj : Name}
202 {usj : List Level} {cvj : ConstantVal} {cnP cnF : Nat} {rl : RecRule}
203 {residual : Expr} :
204 e.getAppFn = .const c us →
205 env.find? c = some (.recInfo cv mI rP rules) →
206 e.getAppArgs.length = mI + 1 →
207 us.length = cv.levelParams.length →
208 Red env d (e.getAppArgs.getD mI (.bvar 0)) major →
209 major.getAppFn = .const cj usj →
210 env.find? cj = some (.ctorInfo cvj cnP cnF) →
211 rules.find? (fun r' => r'.ctor == cj) = some rl →
212 major.getAppArgs.length = rl.ctorParams + rl.nfields →
213 (iotaCase3 env.find? rl cvj
214 (recFireComparands rl cv.levelParams us cvj.levelParams
215 e.getAppArgs rP).1 = true →
216 Level.isEquivList usj
217 (recFireComparands rl cv.levelParams us cvj.levelParams
218 e.getAppArgs rP).1 = some true) →
219 (iotaCase3 env.find? rl cvj
220 (recFireComparands rl cv.levelParams us cvj.levelParams
221 e.getAppArgs rP).1 = true →
222 DefEqList env d (major.getAppArgs.take rl.ctorParams)
223 (recFireComparands rl cv.levelParams us cvj.levelParams
224 e.getAppArgs rP).2) →
225 (Expr.peelPis e.getAppArgs.length
… 18 more lines). Besides finding , and the rule in the environment, the fragment’s rule has three comparisons as premises:
- the constructor’s levels are oracle-equal to the last levels of (a large eliminator carries one extra level in front);
- the constructor’s parameters are definitionally equal to the recursor’s;
- the index expressions of the constructor’s result type at are definitionally equal to the recursor’s index arguments.
In one case it also has a certificate on the constructor application (Section 4.2.8). The official kernel makes none of these checks. The fragment’s rules make the comparisons in every case except K, and its proof of the law uses them. The checker makes them only in the certified case of Section 4.2.8, together with the certificate; there they belong to the certification tax (Section 2.8).
Nothing checks the recursor’s own arguments against its type, and nothing needs to. The reduction claim is asked only of a redex that is not the point, and such a redex fits the recursor’s telescopewhitepaper/Fragment/Sound.lean:L821-L835821/-- **A spine that is not the point fits its head's telescope, with no
822certificate** (the argument of the paper's ι rule, iterated). At each step
823the application is not the point, so neither is its head
824(`app_pt`); the invariant's slot then puts the head in a function space
825(`piR false`), and the head's membership in its type — a function space, not
826the point — makes it a graph of that type's domain, so the slot's
827domain is the binder's (`piR_dom_unique`) and the argument lies in it.
828This is why `Red.iota` carries no certificate on the recursor's
829telescope: the reduction claims nothing of a redex that is the point. -/
830theorem teleFit_of_ne_pt {ρ : Nat → V} :
831 ∀ {xs : List Expr} {f T : Expr},
832 WellDenoted m.M φ ρ T → interp m.M φ ρ f ∈ˢ interp m.M φ ρ T →
833 T.hasPis xs.length = true →
834 WellDenoted m.M φ ρ (Expr.mkAppN f xs) → interp m.M φ ρ (Expr.mkAppN f xs) ≠ pt →
835 TeleFit m.M φ ρ T xs
as it stands. Call the arguments of an application its spine. Each prefix of the redex’s spine is not the point either (), hence a graph. The condition the semantic invariant’s application rule puts on the next argument names a function space holding that graph, the prefix’s membership in its own type names another, and by domain uniqueness the two have one domain, which holds the argument.
/-- **The ι case split's environment facts, one rule**.522
`RecRuleLaw`'s main clause takes the levels, parameters and index523
comparisons as equations; under the conditional claims the ι step has524
them only where neither side is the point, and at a K rule or a family525
that is never `Prop` at the comparand levels it runs no check of the526
constructor application at all. What the step consumes instead, at the527
recursor's levels:528
529
* **K** (`RecRule.k`): the rule's law at the recursor's own530
parameters, for any major value in the major's domain (the531
fragment's `RecRuleLawK`: inversion of the family's least532
fixed point) — the reduct, the minor, does not mention the major;533
* **the constructor's arity**: its stored type binds the spine534
syntactically, so a graded application of it that is not the point535
fits its telescope;536
* **(S) case 2**: where the family is never `Prop` at the comparand537
levels (`iotaCtorNeverProp`, the checker's test), the recursor's major538
domain holds no point;539
* **(T) case 2, the constructor's levels**: there a fired application540
that is not the point, at the constructor's own levels and spine, is541
also an application of the constructor at the comparand levels, to a542
graded spine with the same fields that fits its telescope there —543
the major's membership in the family at the comparands ties the544
injection's fields (the same argument as for the parameters), so the545
main law applies at the comparand levels;546
* **(P) the slot at the point**: where the major domain holds the547
point, every fitting constructor application is the point (the548
family is `Prop` there; the fragment's slot law);549
* **(U) coherence**: at a fired spine where the constructor application550
is the point (case 3, where the step compares the levels and551
parameters with the rule's comparands and the indices), the552
conditional index comparison is an equation (read off the553
parameters' conditional comparison).554
555
(P) and (U) are read only at case 3, so they take the level equation. -/).
By Theorem 2 each comparison becomes an equality of sets wherever neither side is the point. So the lawwhitepaper/Fragment/EnvModel.lean:L119-L186119/-- **The ι law of one recursor rule** (con-leche's `RecRuleLaw`,
120`ConLeche/Model/Env/Laws.lean`), stated on **values**. For the
121recursor `c` (stored as `ci`, with `numParams` parameters,
122`numBefore = numParams + numMotives + numMinors` arguments before the
123indices, the major at `majorIdx`) and its rule `rl` for the
124constructor stored as `cij`: whenever values `xs` up to the major,
125together with the constructor's set applied to values `ys` as the
126major, fit the recursor's telescope, `ys` fit the constructor's
127telescope, and the three comparisons of `Red.iota` hold in their
128semantic form — the constructor's levels evaluate as the recursor's
129last `usj.length` ones, its parameters equal the recursor's and the
130index expressions of its residual type (`piBodyV`: the family at the
131constructor's parameters and index expressions, read under the fields)
132denote the recursor's index arguments, **each where neither side is
133the point** (as `DefEq` claims them) — and the recursor's application
134is **not the point** (as `Red` claims ι), then the recursor's set
135applied to the spine is the rule's right-hand side's set applied to
136the parameters, motives, minors and the fields; and the right-hand
137side is well-denoted, with a well-formed application chain along
138those values (which is what makes the reduct well-denoted,
139`WellDenoted_mkAppN_of_spineOk`).
140
141Values rather than terms so that the law mentions the model only at
142the STORED terms (the two types and the right-hand side): extending
143the environment by a fresh constant then leaves every old law intact
144(`Install.lean`), where a law over arbitrary argument terms would have
145to be re-proved for the terms that mention the new name. The ι rule's
146soundness converts its term-level certificates to this form
147(`Tele.lean`).
148
149**The installation's obligation, named.** The conditional comparisons
150become the equalities the recursion equation needs because the
151recursor's and the constructor's telescopes are generated from one
152parameter and index context: under equal earlier values the two sides
153of each comparison lie in one type, whose members are all the point or
154none (`IsTy.eq_of_ne_pt`). Where the family is a *type*, the law then
155follows from the **fixpoint's inversion**: the recursor's fit puts the
156major — a tagged tuple of the fields — in the family at the RECURSOR's
157parameters and indices, and a member of the family built by `rl.ctor`
158has its fields in the constructor's field telescope at THOSE
… 28 more lines is stated on values, with the fits and these conditional equalities as premises:
Definition 9 (the law of a rule). Let be a stored recursor and the right-hand side of its rule for the constructor . Take a valuation, levels and of the right lengths, values for the recursor’s arguments before the major (parameters , motive , minors , indices ), and values for the constructor’s arguments (parameters and fields ). Suppose
- followed by fit the recursor’s type at , and fit the constructor’s type at ;
- evaluates as the last levels of ;
- at every position where neither is the point;
- the constructor’s index expressions read at its arguments equal at every position where neither side is the point; and
- the recursor applied to all these values is not the point.
Then
and the right-hand side is well-denoted with a well-formed application chain along those values.
The second conjunct makes the reduct well-denoted, as the first claim of Theorem 2 demands; the first is the equation. The case of Theorem 2 is then a translationwhitepaper/Fragment/Sound.lean:L1057-L10671057/-- **ι, the semantic core** of `iota_core` and
1058`iota_core_strict`, at the constructor's parameter count `nP` and a
1059right-hand side `rhs`, given the environment's law in the form both ι
1060laws take once instantiated (`hlaw`), and — at every valuation where
1061the redex is well-denoted and not the point — the invariant of the
1062reduced major and the two telescope fits (`hcore`), however obtained:
1063from certificates (`iota_core`) or from the major's domain being strict
1064(`iota_core_strict`). The index comparisons are passed on in their
1065conditional form; the major's denotation is its reduct's with no β
1066certificate (`major_eq`). -/
1067theorem iota_core_sem {Γ : List Expr} {c : Name} {us : List Level} {ci : ConstInfo}
. The comparisons become conditional equalities; the redex’s own fit is as above; the constructor’s fit comes from its certificate or from the arguments of Section 4.2.8; the reduced major’s value replaces the argument’s; and the lawConLeche/Model/Rules/IotaSound.lean:L8888theorem Red.iota_sound (hin : RulesInputs V m φ) {d : Nat} {e : Expr} {c : Name}
gives the rest.
/-- **The ι law of a generated rule** (`RecRuleLaw`, `EnvModel.lean`)823
for the recursor's constant and the constructor's, at the recursor's824
`nP` parameters, `nP + 1 + n` arguments before the indices and the825
major at `nP + 1 + n + nI`. -/826
theorem rec_rule_law {j : Nat} {c : CtorSpec} (hc : S.ctors[j]? = some c) :827
RecRuleLaw (S.M₃ m.M) S.recName S.recInfo S.nP (S.nP + 1 + S.n) (S.nP + 1 + S.n + S.nI)828
⟨c.name, c.fields.length, S.ruleRhs c j, none, S.isK⟩ (S.ctorInfo c) := byin the model of Section 4.2. (Via the equation on the semantic recursorwhitepaper/Fragment/IndRec.lean:L280-L290280
/-- **The ι equation**: at a constructor value whose fields fit the281
family, the recursor is the minor at the fields and the inductive282
hypotheses' values. Con-leche: `GraphRecKit.rec_eq`. -/283
theorem recSem_eq {ps : List V} (hpl : S.nest = none) (hnr : S.NoRecDep) (hb : S.DomsBounded M ls ps)284
(hu : S.Uniq M ls)285
(hp : FitsVals M (S.ψ ls) base S.params ps) {j : Nat} {c : CtorSpec} {fs : List V}286
(hc : S.ctors[j]? = some c) (hfit : S.FitsFields M ls (S.Fam M ls ps) ps c.fields fs)287
(m : V) (mins : List V) :288
S.recSem M ls ps m mins (S.idxVals M ls (consList fs (envP ps)) c.idx) (S.ctorVal ls j fs)289
= appList (S.minorAt mins j)290
(fs.reverse ++ c.recFields.map (S.ihSem M ls ps m mins fs)) :=; in con-leche, the graph’s equationConLeche/Model/Inductives/BlockRecLaw.lean:L877-L883877
/-- **A new block recursor's rule law, at either firing**. `hpins` is878
`RecRuleLaw`'s outer `.nested` conjunct — the pins'879
open readings and their context-guarded grading — vacuous at a `.plain`880
rule; `hrhs` receives no parameter comparison at either firing: the881
major's membership in the family at the comparands identifies the fired882
spine with the rule's own (`BlockRuleDataAt`'s `mk` conjunct). -/883
theorem blockRecRuleLaw_gen {env : Env} {m₃ : EnvModel V env} {φ : Name → Nat}, lifted to the law of Definition 9.)
Proof. First the conditional comparisons become equalities. The recursor’s and the constructor’s telescopes are generated from one parameter context and one index context, and both spines fit them. Under equal earlier values, two values compared at one position lie in one type, whose members are all the point or none, so where the comparison says nothing they are both the point (a conditional comparison of fitting values is an equalitywhitepaper/Fragment/Ctx.lean:L161-L167161/-- **Fitting values compared where neither is the point are equal**:
162the two lists fit one context of types, so position by position, under
163equal earlier values, the two values lie in one type — whose members
164are all the point or none of them (`IsTy.eq_of_ne_pt`). -/
165theorem FitsVals_eq_of_cond : ∀ {ρ : Nat → V} {Γ : List Expr} {vs ws : List V},
166 CtxTy M φ ρ Γ → FitsVals M φ ρ Γ vs → FitsVals M φ ρ Γ ws →
167 (∀ p ∈ vs.zip ws, p.1 ≠ pt → p.2 ≠ pt → p.1 = p.2) → vs = ws
). So , and the constructor’s index values are .
The type-valued family. The recursor’s fit puts the major’s value in the family at the recursor’s parameters and indices , and by the constructor’s fit that value computes to . A member of the fibre is a constructor value of fitting fields (the fixed-point equation, read forwardswhitepaper/Fragment/IndSem.lean:L1478-L14861478/-- **The family's case analysis**: a member of the fibre at `is` is
1479a constructor instance over the family at `is`, and conversely.
1480Con-leche: `LfpClause.carrier_case`. -/
1481theorem mem_Fam {ps : List V} (hnr : S.NoRecDep) (hb : S.DomsBounded M ls ps)
1482 (hco : S.ContOk M ls ps) {is : List V} {t : V} :
1483 t ∈ˢ S.Fam M ls ps is ↔ S.Inst M ls (S.Fam M ls ps) ps is t := by
1484 rw [← S.famOp_Fam M ls hnr hb hco]
1485 exact S.mem_famOp M ls
1486
), and tagged tuples are injective, so the fields fit ’s field telescope at the recursor’s parameters, which is what the steps inside require. The recursion theorem’s equation at is the rule’s equation.
The proposition-valued family. Here the major’s value is the point, and the recursor’s fit says only that the fibre at the recursor’s indices is inhabited. The point carries no constructor, no fields, no parameters and no indices. When the elimination level is zero the recursor’s application is the point, and the law asks nothing. Otherwise the block passed the subsingleton criterion. It has one constructor, so every decoding of the point is by , and each field is a proposition or occurs among the constructor’s result indices. A propositional field is the point in both decodings, provided both read its domain at one valuation of the block’s level parameters; the level comparison secures that. The index equality identifies an index field’s value with the recursor’s index argument. So the two decodings of the point agree, and the recursion theorem’s equation is again the rule’s. □
Why the index comparison is needed. Take the recursor of Example 7 on the spine . Every is inhabited, by , so , and the recursor’s fit asks only that lie in it, which it does. The fit holds equally for . Without the index comparison the law would therefore have to give both
so for every , which is false. The recursor’s set is a function of its arguments and has one value at index . On values the types and are both , so no comparison of types could tell them apart; the index comparison, , is what the model can use. For a type-valued family the comparison is redundant, because the tagged tuple carries its indices. The official kernel never makes it: at an step it compares nothing and relies on the term being well-typed, which its typing judgement guarantees. Our proof has no typing judgement, so where the major may be the point the comparison has to be a premise of the rule.
4.2.8. When needs a certificate
The official kernel checks nothing at an step: the term is well-typed, so the constructor application it reduces to and that application’s fields are well-typed too. The semantic proof needs the reduct to be well-denoted and the constructor’s arguments to fit its type wherever the redex is not the point. What supplies that depends on the major’s family at the instance. The model has three more laws for it, each proved when the block is installed.
A family that is never a proposition. If the family’s sort is at least at every valuation (one oracle test on the levels at the use), then the step needs no certificatewhitepaper/Fragment/Rules.lean:L394-L445394 /-- **ι, case 2** (the checker's ι case test): `Red.iota`
395 with **no certificate at all** — no major slot, no constructor
396 telescope — where the major's domain (what the recursor's telescope
397 leaves after the spine, `Expr.piResidual`) is a stored inductive
398 family applied to its parameters and indices whose stored sort is
399 **never `Prop`** at the use: one level test, `1 ≤ u` at every
400 valuation (`LevelOracle.le`, con-leche's `sortZ` read as `never`).
401 The environment law `EnvModel.ind_fib` makes that domain strict,
402 so a redex that is not the point has a major that is not the point;
403 its reduction claim then applies, and the reduced constructor
404 application fits the constructor's telescope with no certificate
405 (`Sound.lean`, `iota_core_strict`). The three comparisons are
406 `Red.iota`'s, unchanged. -/
407 | iotaStrict {Γ : List Expr} {c : Name} {us : List Level} {ci : ConstInfo}
408 {numParams numMotives numMinors numIndices : Nat} {rules : List RecRule}
409 {args : List Expr} {major : Expr} {cj : Name} {usj : List Level}
410 {cij : ConstInfo} {margs : List Expr} {rl : RecRule}
411 {dM bM : Expr} {I₀ : Name} {ls₀ : List Level} {dargs : List Expr} {ci₀ : ConstInfo}
412 {nP₀ nI₀ : Nat} {cs₀ : List Name} {S₀ : IndSpec}
413 {residual : Expr} {I : Name} {lsI : List Level} {rps ridx : List Expr} :
414 env.find? c = some ci →
415 ci.kind = .recursor numParams numMotives numMinors numIndices rules →
416 us.length = ci.lparams.length →
417 args.length = numParams + numMotives + numMinors + numIndices + 1 →
418 ci.type.hasPis (numParams + numMotives + numMinors + numIndices + 1) = true →
419 Red env Γ (args.getD (numParams + numMotives + numMinors + numIndices) (bvar 0))
420 major →
421 major = mkAppN (const cj usj) margs →
422 rl ∈ rules → rl.ctor = cj → rl.inst = none →
423 env.find? cj = some cij →
424 usj.length = cij.lparams.length →
425 margs.length = numParams + rl.nfields →
426 cij.type.hasPis (numParams + rl.nfields) = true →
427 -- case 2: the major's domain is a stored family whose sort is never `Prop` at the use
428 piResidual (ci.type.instL ci.lparams us)
429 (args.take (numParams + numMotives + numMinors + numIndices)) = some (pi dM bM) →
430 dM = mkAppN (const I₀ ls₀) dargs →
431 env.find? I₀ = some ci₀ → ci₀.kind = .induct nP₀ nI₀ cs₀ S₀ →
432 ls₀.length = ci₀.lparams.length → dargs.length = nP₀ + nI₀ →
433 ci₀.type.hasPis (nP₀ + nI₀) = true →
… 12 more lines. The model’s fibre lawwhitepaper/Fragment/EnvModel.lean:L278-L296278/-- **The fibres' regime follows the stored sort** (used by
279the ι case test): for a stored inductive type former
280`I` (stored as `ci`, with `nP` parameters and `nI` indices and its
281block's specification `S`), the family applied to any values fitting
282its telescope is a **truth value** where the family's stored sort
283`S.sort`, instantiated at the use's levels, evaluates to zero, and a
284**strict** set — not the point, and without the point as a member —
285where it does not (`Fib`). Every `Sort (n+1)` holds truth values as
286well (`typeU`), so the universe does not say the second; the
287inductive model does: above a proposition a fibre is a set of tagged
288values (`IndSem.Fam_mem_typeU`), and a family read through a former's
289graph is admissible (`IndCommon.adm`), so strict
290(`IndCommon.strict_adm`). Stated on values, like `RecRuleLaw`. -/
291def IndFibLaw (M : Name → List Nat → V) (I : Name) (ci : ConstInfo) (nP nI : Nat) (S : IndSpec) :
292 Prop :=
293 ∀ (φ : Name → Nat) (ρ : Nat → V) (ls : List Level) (vs : List V),
294 ls.length = ci.lparams.length → vs.length = nP + nI →
295 TeleFitV M φ ρ (ci.type.instL ci.lparams ls) vs →
296 Fib (Level.eval φ (S.sort.subst ci.lparams ls) == 0) (appList (M I (ls.map (Level.eval φ))) vs)
says that a stored family’s fibres are truth values where its stored sort evaluates to zero and strict sets where it does not; the installation proves itwhitepaper/Fragment/InstallInd.lean:L150-L163150/-- **The fibre law of a former read by readers**: an
151assignment that reads the block at every valuation, with a family in
152the result universe (`Reader.adm` makes it admissible), satisfies
153`IndFibLaw` at the former — at the use's levels the reader's valuation
154evaluates the stored sort as the use does (`Level.eval_subst`), and the
155family applied has the fibres that sort says (`Reader.fib_app`).
156Every installation stage that stores the former reads it so (`mIndF`,
157`mInd`, and the nested stages). -/
158theorem indFib_of_readers {M M' : Name → List Nat → V} {F : List Nat → List V → List V → V}
159 (hS : S.Scoped env) (hR : ∀ φ, S.Reader (env := env) M φ F M' φ)
160 (hF : ∀ φ ps is, FitsVals M (S.ψ (S.lparams.map φ)) base S.params ps →
161 FitsVals M (S.ψ (S.lparams.map φ)) (envP ps) S.indices is →
162 F (S.lparams.map φ) ps is ∈ˢ (typeU (S.u₀ (S.lparams.map φ)) : V)) :
163 IndFibLaw M' S.name S.indInfo S.nP S.nI S := by
from the construction of Section 4.2. The redex is not the point, so the major lies in its domain, a strict fibre. So the major is not the point, its own reduction claim applies, and the constructor application it reduces to is not the point and lies in the same fibre. Hence it fits the constructor’s telescope by the argument of Section 4.2.7 (the case’s proofwhitepaper/Fragment/Sound.lean:L1309-L13231309/-- **ι with no telescope certificate at all, where the major's domain
1310is strict** (the case of a family that is never a proposition). If the
1311domain the major meets holds no point at the valuation — the case of an
1312inductive family whose sort is never `Prop` at the instance, whose
1313fibres are sets of tagged tuples — then a redex that is not the point
1314has a major that is not the point (the major lies in that domain,
1315`teleFit_of_ne_pt`), so the major's reduction claim applies; the
1316reduced major, a constructor application that is not the point, fits
1317the constructor's telescope by the same argument. No certificate is
1318needed for the recursor's spine, the major or the constructor's
1319spine. The strictness is a semantic hypothesis here (a sort above
1320`Prop` also holds truth values, so the universe does not give it);
1321`strict_dom` supplies it from the environment law
1322`EnvModel.ind_fib` and one level test (`Red.iotaStrict`). -/
1323theorem iota_core_strict {Γ : List Expr} {c : Name} {us : List Level} {ci : ConstInfo}
). This covers every redex on Nat, List, Bool and the like.
A family that may be a proposition. This is the certified case. The rulewhitepaper/Fragment/Rules.lean:L292-L347292 /-- **ι with no major-slot certificate and no level test** (the
293 checker's certified case of ι). `Red.iota`
294 without the reduced major's slot certificate, for any family: the
295 constructor's telescope certificate stays at the full grade (every
296 parameter and field), and so do the three comparisons; nothing
297 else is tested. Why the slot needs nothing: where the unreduced
298 major is not the point its reduction claim gives the reduct; where it
299 is, the environment's slot law (`EnvModel.rec_rules_slot`) makes
300 the constructor application the point too, given the redex is not
301 (`Sound.lean`, `slot_of_law`, `iota_core_slot`). On the generated
302 recursors that law holds because the major's domain is the
303 constructor's own family at the recursor's levels, a fibre holding the
304 point is a truth value there, and the constructor of a family in
305 `Prop` is the point (`IndSpec.rec_rule_slot`). No premise links the
306 major's domain to the constructor's family: the installation proves
307 the slot law of every generated rule (`InstallIota.lean`). This
308 is the rule for a family that is a proposition at some valuations
309 only (`Quot.{u}`, `PUnit.{u}`). -/
310 | iotaSlot {Γ : List Expr} {c : Name} {us : List Level} {ci : ConstInfo}
311 {numParams numMotives numMinors numIndices : Nat} {rules : List RecRule}
312 {args : List Expr} {major : Expr} {cj : Name} {usj : List Level}
313 {cij : ConstInfo} {margs : List Expr} {rl : RecRule}
314 {dM bM : Expr} {doms' tys' : List Expr}
315 {residual : Expr} {I : Name} {lsI : List Level} {rps ridx : List Expr} :
316 env.find? c = some ci →
317 ci.kind = .recursor numParams numMotives numMinors numIndices rules →
318 us.length = ci.lparams.length →
319 args.length = numParams + numMotives + numMinors + numIndices + 1 →
320 ci.type.hasPis (numParams + numMotives + numMinors + numIndices + 1) = true →
321 Red env Γ (args.getD (numParams + numMotives + numMinors + numIndices) (bvar 0))
322 major →
323 major = mkAppN (const cj usj) margs →
324 rl ∈ rules → rl.ctor = cj → rl.inst = none →
325 env.find? cj = some cij →
326 usj.length = cij.lparams.length →
327 margs.length = numParams + rl.nfields →
328 cij.type.hasPis (numParams + rl.nfields) = true →
329 -- the major's slot, located but not certified
330 piResidual (ci.type.instL ci.lparams us)
331 (args.take (numParams + numMotives + numMinors + numIndices)) = some (pi dM bM) →
… 16 more lines makes the three comparisons of Section 4.2.7 and checks the constructor application: the constructor’s levels, parameters and index expressions are compared with the recursor’s, and every parameter and field of the constructor application is inferred by full inference and compared with the constructor’s domain. At a valuation where the family is a proposition the constructor’s value is the point (a constructor of a family in Propwhitepaper/Fragment/Sound.lean:L1003-L10121003/-- **A constructor of a family whose sort evaluates to zero denotes the
1004point**. The
1005constructor applied to arguments that fit its stored type, whose
1006residual is the family `I` at some parameters and indices, at a level
1007where `I`'s sort evaluates to zero: the application lies in that
1008residual, a type of `Sort 0`, so it is the point
1009(`eq_pt_of_mem_sortZero`). No argument of the application is read
1010beyond its fit: at such a level the constructor's arguments are not
1011constrained by its value. -/
1012theorem ctorApp_eq_pt_of_sortZero {ρ : Nat → V} {cj : Name} {usj : List Level} {cij : ConstInfo}
), and an application whose head is the point constrains none of its arguments. So a field can lie outside its domain in a well-denoted major. The reduct applies the minor premise to that field, and is then not well-denoted although the redex is (a witnesswhitepaper/Fragment/EtaIota.lean:L681-L692681/-- **The field counterexample, on values, and the eta path's answer to
682it**. At a valuation where the structure is a
683proposition, the redex `rec minor (mk n)` with an ill-slotted field
684`n` is well-denoted and not the point. Plain ι's reduct `minor n` is
685not well-denoted, so plain ι without a certificate on the field
686breaks the reduction claim; the eta reduct `minor (p0 (mk n))` is
687well-denoted and denotes what the redex does, so the step through the
688projections satisfies it with no certificate. -/
689theorem plainIota_literal_witness (φ : Name → Nat) :
690 (∀ ρ : Nat → V, WellDenoted (witM (V := V)) φ ρ redex1 ∧ interp witM φ ρ redex1 ≠ pt) ∧
691 ¬ RedSem (EnvModel.empty (witM (V := V))) φ [] redex1 plain1 ∧
692 RedSem (EnvModel.empty (witM (V := V))) φ [] redex1 eta1 := by
, in which the minor is a graph over and the field is not the point). This is the second kind of check of the certification tax (Section 2.8). Other checkers skip it for the reason they skip the first: in a well-typed major the fields are well-typed. The major premise itself needs no check. By the model’s major-premise lawwhitepaper/Fragment/EnvModel.lean:L250-L276250/-- **The point in the major's slot** (the installer fact
251behind `Red.iotaSlot`/`Red.iotaNestedSlot`): for a recursor `c` and its
252rule `rl` firing on the constructor stored as `cij`, wherever the
253recursor's telescope holds the point as the major (after values `xs`
254fitting it), either the recursor's application there is the point or
255the rule's constructor, applied to anything, is — provided, for a rule
256of the block's own constructors (`rl.inst = none`), that the
257constructor's levels evaluate as the recursor's last ones (the level
258comparison ι runs). On generated recursors: the major's domain is the
259family at the recursor's own levels; where its sort evaluates to zero
260the constructor (of the same family, at the same levels) is the point,
261and where it does not the domain is strict and holds no point. On a
262nested block the family is `Prop` only where the eliminator is
263(`blockLargeElimAllowed`), so there the recursor's application is the
264point. This is what lets the ι rules with no slot certificate go
265without a premise linking the major's domain to the constructor's
266family. -/
267def RecRuleSlotLaw (M : Name → List Nat → V) (c : Name) (ci : ConstInfo) (majorIdx : Nat)
268 (rl : RecRule) (cij : ConstInfo) : Prop :=
269 ∀ (φ : Name → Nat) (ρ : Nat → V) (us usj : List Level) (xs ys : List V),
270 us.length = ci.lparams.length → usj.length = cij.lparams.length →
271 xs.length = majorIdx →
272 TeleFitV M φ ρ (ci.type.instL ci.lparams us) (xs ++ [pt]) →
273 (rl.inst = none →
274 usj.map (Level.eval φ) = (us.drop (us.length - usj.length)).map (Level.eval φ)) →
275 appList (M c (us.map (Level.eval φ))) (xs ++ [pt]) = pt ∨
276 appList (M rl.ctor (usj.map (Level.eval φ))) ys = pt
, wherever the recursor’s domain holds the point as the major, the redex or the constructor is the point (its proofwhitepaper/Fragment/InstallIota.lean:L1046-L10561046/-- **The slot law of a generated rule** (`RecRuleSlotLaw`):
1047where the recursor's telescope holds the point as the major, the
1048fibre it lies in — the family at the recursor's own parameters,
1049indices and levels (`fits_recCtx_iff`) — holds the point, so the
1050family's sort is zero there (above it the fibre is a set of tagged
1051values, `mem_Fam_false`); the constructor, at levels that evaluate as
1052the recursor's (the level comparison), is then the point
1053(`ctorSet` at a proposition). -/
1054theorem rec_rule_slot {j : Nat} {c : CtorSpec} (hc : S.ctors[j]? = some c) :
1055 RecRuleSlotLaw (S.M₃ m.M) S.recName S.recInfo (S.nP + 1 + S.n + S.nI)
1056 ⟨c.name, c.fields.length, S.ruleRhs c j, none, S.isK⟩ (S.ctorInfo c) := by
: a fibre holding the point is a truth value, so the family is a proposition there). In that case the reduced constructor application is the point, as the major is.
K. A block whose sort is Prop, with one constructor, which has no fields (Eq, HEq, True), is K-like, and its rule is flagged. Its stepwhitepaper/Fragment/Rules.lean:L493-L527493 /-- **ι by K** (Lean's
494 `to_cnstr_when_K`): a recursor whose rule is flagged K
495 (`RecRule.k`: one family, always a proposition, one constructor
496 with no fields — `Eq`, `HEq`, `True`) reduces on its major **as it
497 stands**, with no constructor in sight, to the rule's right-hand
498 side at the recursor's parameters, motives and minors. There is no
499 certificate, no level, parameter or index comparison, and the major
500 is not reduced. What stays is K's **type check**, as in Lean: the
501 major's inferred type reduces to a family application
502 `I.{lsI} targs`, the constructor fabricated at its parameters
503 `rl.ctor.{lsI} (targs.take numParams)` has a type `tF`, and
504 `tF ≡ I.{lsI} targs`. That check decides whether the step is taken
505 (a verdict test); no claim consumes it. Soundness is the
506 environment's K law (`EnvModel.rec_rules_k`, proved in the
507 installer by inversion of the least fixed point) with
508 `Red.iotaK_core_sem`: a redex that is not the point fits the
509 recursor's telescope, the major included, and the law needs nothing
510 more. -/
511 | iotaK {Γ : List Expr} {c : Name} {us : List Level} {ci : ConstInfo}
512 {numParams numMotives numMinors numIndices : Nat} {rules : List RecRule}
513 {args : List Expr} {rl : RecRule} {tM : Expr} {I : Name} {lsI : List Level}
514 {targs : List Expr} {tF : Expr} :
515 env.find? c = some ci →
516 ci.kind = .recursor numParams numMotives numMinors numIndices rules →
517 us.length = ci.lparams.length →
518 args.length = numParams + numMotives + numMinors + numIndices + 1 →
519 ci.type.hasPis (numParams + numMotives + numMinors + numIndices + 1) = true →
520 rl ∈ rules → rl.k = true →
521 -- K's type check on the major as it stands (a verdict test, consumed by no claim)
522 Infer env Γ (args.getD (numParams + numMotives + numMinors + numIndices) (bvar 0)) tM →
523 Red env Γ tM (mkAppN (const I lsI) targs) →
524 Infer env Γ (mkAppN (const rl.ctor lsI) (targs.take numParams)) tF →
525 DefEq env Γ tF (mkAppN (const I lsI) targs) →
526 Red env Γ (mkAppN (const c us) args)
527 (mkAppN (rl.rhs.instL ci.lparams us) (args.take (numParams + numMotives + numMinors)))
fires on any major whose type is right, with no constructor in sight. The reduct is the rule’s right-hand side at the recursor’s parameters, motive and minors alone. There is no certificate and no comparison, and the major is not reduced. What remains is Lean’s type check: the major’s inferred type reduces to the family at some arguments, and the constructor fabricated at their parameters has a type definitionally equal to it. That check decides whether the step is taken; no claim consumes it. The model’s K lawwhitepaper/Fragment/EnvModel.lean:L188-L214188/-- **The ι law of a K rule** (the `EnvModel` field
189`rec_rules_k`, proved by the installer as
190`IndSpec.rec_rule_lawK`).
191For a recursor `c` whose one rule `rl` fires on a constructor with no
192fields of a family of sort `Prop` (Lean's K: `Eq`, `HEq`, `True`): at
193**any** values `vs` fitting the recursor's whole telescope — the
194major's value included, whatever term produced it — an application
195that is not the point equals the rule's right-hand side applied to the
196parameters, motives and minors alone. In the inductive model this is
197the plain law at the recursor's own parameters plus **inversion of the
198least fixed point**: the major's value lies in the fibre at the
199recursor's indices, a fibre is inhabited only at the indices of the
200unique constructor applied to those parameters (the family "inhabited
201exactly there" is closed under the constructor, so it contains the
202least one), and the constructor's value there is the point, as the
203major's is. The constructor application the checker fabricates (or
204finds) is not mentioned: the reduct does not contain it. -/
205def RecRuleLawK (M : Name → List Nat → V) (c : Name) (ci : ConstInfo)
206 (numBefore majorIdx : Nat) (rl : RecRule) : Prop :=
207 ∀ (φ : Name → Nat) (ρ : Nat → V) (us : List Level) (vs : List V),
208 us.length = ci.lparams.length → vs.length = majorIdx + 1 →
209 TeleFitV M φ ρ (ci.type.instL ci.lparams us) vs →
210 appList (M c (us.map (Level.eval φ))) vs ≠ pt →
211 appList (M c (us.map (Level.eval φ))) vs
212 = appList (interp M φ ρ (rl.rhs.instL ci.lparams us)) (vs.take numBefore) ∧
213 WellDenoted M φ ρ (rl.rhs.instL ci.lparams us) ∧
214 SpineOk (interp M φ ρ (rl.rhs.instL ci.lparams us)) (vs.take numBefore)
says that at any values fitting the recursor’s whole telescope, the major’s included, an application that is not the point equals the right-hand side at the values before the indices. The installation proves itwhitepaper/Fragment/InstallIota.lean:L981-L1002 981/-- **The K law of a generated rule** (`RecRuleLawK`):
982for a block with one constructor `c`, which has no fields, at ANY
983values fitting the recursor's telescope — the major's value whatever
984term produced it — the recursor is the rule's right-hand side at the
985parameters, motive and minor alone. The proof is **inversion of the
986least fixed point**: the recursor's fit puts the major's value `t` in
987the fibre at the recursor's own parameters and indices
988(`fits_recCtx_iff`), and a member of a fibre is a constructor instance
989over the family (`IndSpec.mem_Fam`, the fixed-point equation); with
990one constructor and no fields that instance is `c` at the recursor's
991parameters, its index values are the indices, and `t` is its value
992(at a proposition the point, above it the empty tagged tuple). The
993plain law's second half (`rec_rule_core`) then applies at the
994recursor's own parameters. So the constructor's parameter telescope
995needs no comparison with the recursor's: the inversion is read off the
996recursor's fit, in the block's own parameter context. Neither the
997family's sort nor the elimination level is used: the law holds for
998every one-constructor, fieldless block (Lean's K adds "is `Prop`",
999`isK`, and so does the flag). -/
1000theorem rec_rule_lawK {c : CtorSpec} (hK : S.ctors = [c]) (hnf : c.fields = []) :
1001 RecRuleLawK (S.M₃ m.M) S.recName S.recInfo (S.nP + 1 + S.n) (S.nP + 1 + S.n + S.nI)
1002 ⟨c.name, c.fields.length, S.ruleRhs c 0, none, S.isK⟩ := by
by inversion of the least fixed point. The major’s value lies in the fibre at the recursor’s parameters and indices, a member of a fibre is a constructor value reaching those indices, and with one constructor and no fields that value and those indices are determined. The proof uses neither the family’s sort nor the elimination level, so the law holds for every one-constructor block without fields; the Prop condition is Lean’s.
For a structure, Section 6.2 shows a further way to fire without a certificate, through the projections of the major.
def iotaCtorNeverProp (find? : Name → Option ConstantInfo) (cvj : ConstantVal)86
(us : List Level) : Bool :=87
match (cvj.type.piResult).getAppFn with88
| .const T _ =>89
match find? T with90
| some (.indInfo cvT caps) =>91
cvT.levelParams.length == us.length && capsNeverZero cvT.levelParams us caps92
| _ => false93
| _ => falseat the levels the rule compares with, read off the sort recorded for the family when it was installed. A K rule and a family that is never a proposition fire with no check; every other ruleConLeche/Kernel/CoreDefs.lean:L102-L104102
def iotaCase3 (find? : Name → Option ConstantInfo) (rl : RecRule) (cvj : ConstantVal)103
(us : List Level) : Bool :=104
!rl.k && !iotaCtorNeverProp find? cvj usis the certified case, as in the fragment.
4.3. Consistency
An environment is acceptedwhitepaper/Fragment/Consistency.lean:L36-L4736/-- **The environments the checker accepts**: the empty environment,
37a definition the definition check passes, an inductive block the
38block check passes (con-leche's `addDecl`, `ConLeche/Kernel/Env.lean`,
39run to completion). -/
40inductive Accepted : Env → Prop
41 /-- Nothing is stored. -/
42 | empty : Accepted Env.empty
43 /-- A definition (`DefOk`, `Decl.lean`). -/
44 | defn {env : Env} {c : Name} {ci : ConstInfo} :
45 Accepted env → DefOk env c ci → Accepted (env.add c ci)
46 /-- An inductive block, plain or nested (`IndOk`, `Decl.lean`). -/
47 | ind {env : Env} (S : IndSpec) : Accepted env → IndOk env S → Accepted (S.install env)
when it is built from the empty environment by the two steps of §3 and §4: a definition that passes its checks, or an inductive block that passes its checks and is installed. An accepted environment is closed: every stored term mentions only stored constantswhitepaper/Fragment/Consistency.lean:L50-L5550/-- An accepted environment is closed: its stored terms mention only
51stored constants, at their own level parameters. -/
52theorem Accepted.scoped : ∀ {env : Env}, Accepted env → Env.Scoped env
53 | _, .empty => Env.Scoped.empty
54 | _, .defn h hok => h.scoped.add_def hok
55 | _, .ind S h hok => h.scoped.install_any hok
. This is what Theorem 5 and the construction of Section 4.2 assumed of the environment they extend.
/-- **Every accepted environment has a model** (in any `IndLib`) — a58
block model, which remembers its blocks for the nestings to come59
(`BlockModel.lean`). -/60
theorem accepted_model {env : Env} (h : Accepted env) : Nonempty (BlockModel V env) := by61
induction h with62
| empty => exact ⟨BlockModel.empty fun _ _ => pt⟩63
| defn h hok ih =>64
obtain ⟨m⟩ := ih65
obtain ⟨m', -⟩ := install_def' h.scoped m hok66
exact ⟨m'⟩67
| ind S h hok ih =>68
obtain ⟨m⟩ := ih69
obtain ⟨m', -⟩ := install_ind_any h.scoped m hok70
exact ⟨m'⟩(con-leche’s main theoremConLeche/MainTheorem.lean:L96-L9996
theorem model_exists (V : Type w) [SetTheory V]97
(pins : List NatOpPinSet) (ds : Array Declaration) (env : Env)98
(accepted : checkDecls .verified pins ds = .ok env) :99
Nonempty (Model V env) := by).
/-- **Installing a plain inductive block preserves having a model.**177
Every stored constant keeps its set. -/178
theorem install_ind {env : Env} {S : IndSpec} (hpl : S.nest = none) (hs : Env.Scoped env)179
(m : EnvModel V env) (hok : S.Ok env) :180
∃ m' : EnvModel V (S.install env), ∀ n, (env.find? n).isSome → ∀ ls, m'.M n ls = m.M n ls :=181
⟨S.mInstall hpl hs m hok, fun n hn ls => (S.agree_M₃ hok m.M n hn ls).symm⟩: law 1 is its membership claims, law 2 has no new instance, and law 3 is Theorem 10. □
theorem declBlock (hμ : μ.isVerified = true) {F : Nat} {env env₂ : Env}72
{block : List ConstantInfo} {nPd : Nat} {p₀ : BlockParts}73
(mp : EnvModelM V μ env)74
(hE : ConLeche.EtaFamiliesClosed env) (hdp : ConLeche.blockParts? nPd block = some p₀)75
(hrun : ConLeche.Semantics.DeclBlockRun μ F env block p₀ env₂) (hcov : LfpCover mp []) :76
∃ mp' : EnvModelM V μ env₂, LfpCover mp' [] := by, read off the checker’s run stage by stage.
/-- **No closed term inhabits an empty inductive type.** For a block90
without constructors, parameters, indices and level parameters whose91
recursor the environment stores, no closed term has the type former's92
type: in a model, the recursor's set lies in the function space over the93
motives and the fibre, and at the motive with the empty fibre a94
member of the type would be a member of the empty set. -/95
theorem no_empty_inductive_inhabitant {env : Env} (hacc : Accepted env) {S : IndSpec}96
(hctors : S.ctors = []) (hparams : S.params = []) (hidx : S.indices = [])97
(hlp : S.lparams = []) (hrec : env.find? S.recName = some S.recInfo) (e : Expr) :98
¬ Infer env [] e (.const S.name []) := by; in particular no stored constant has type (at the block
inductive False : Propwhitepaper/Fragment/Consistency.lean:L134-L138134theorem no_empty_prop_inhabitant {env : Env} (hacc : Accepted env) {name recName elim : Name}135
(hrec : env.find? recName = some (IndSpec.falseSpec name recName elim).recInfo) (e : Expr) :136
¬ Infer env [] e (.const name []) :=137
no_empty_inductive_inhabitant V hacc (S := IndSpec.falseSpec name recName elim) rfl rfl rfl rfl138
hrec e; as in con-lecheConLeche/Model/Fold.lean:L268-L275268
theorem no_proof_of_False_pure (V : Type w) [SetTheory V]269
{μ : CheckMode} (hμ : μ.isVerified = true) {F : Nat}270
{ds : List Declaration} {env' : Env}271
(h : checkDeclsPure μ (fueledOps μ F) pins ds = .ok env') :272
∀ c ∈ env'.consts,273
c.toConstantVal.type = .const falseName [] → False := by274
obtain ⟨mp⟩ := checkDeclsPure_sound_of (V := V) hμ h275
exact fun c hc hty => no_constant_of_False mp c hc hty).
Proof. Take a model of the environment, by Theorem 11, and read law 1 for the block’s recursor at the valuation that sends every level to . Its type is then
the elimination principle of an empty type, read as a proposition, and law 1 puts the recursor’s set in what it denotes. If had , Corollary 3 would put into . Take for the abstraction over that is constantly the empty truth value: it lies in the motive’s domain, so the recursor applied to and lies in , the empty truth value: a member of the empty set. The proof uses only that a model exists, not how it was built. (The construction of Section 4.2 also makes empty outright, as the least fixed point of an operator with no step.) □
False from the stream but installs it from a built-in copy and rejects a stream that declares it differently (likewise Eq, Nat and a few others, Section 7), so the main theorem can name False and say that it denotes the empty set and Eq set equality, with no hypothesis about the stream (§1 of OVERVIEW.md); and its acceptance is the run of a program, the declaration foldConLeche/Model/Fold.lean:L198-L200198theorem foldPM (hμ : μ.isVerified = true) {F : Nat} :199
∀ (ds : List Declaration) (env : Env) {env' : Env},200
EnvModelOk V μ env →, rather than a relation, so a bridge theorem turns each accepting run into the derivations §3 and §4 consume (Section 7). The argument in between is the one above.
5. Nested inductive types
This section changes tack. The previous sections present a fragment of the checker and prove it sound. This one describes in full con-leche’s check for nested blocks, which mention the type being defined inside another inductive type (mutual blocks aside), and then only sketches how the construction and the proofs of Section 4.2 extend to them. The details are in the linked con-leche proof. A restricted case, with one container instantiation at depth one and not under a binder, is verified in the paper’s Lean fragment: installing such a block keeps a model of the environmentwhitepaper/Fragment/NestInstall.lean:L258-L262258theorem install_nest {env : Env} {S : IndSpec} {N : NestInfo} (hs : Env.Scoped env)
259 (m : BlockModel V env) (hok : S.OkN N env) :
260 ∃ m' : BlockModel V (S.install env), ∀ n, (env.find? n).isSome → ∀ ls, m'.M n ls = m.M n ls := by
261 exact ⟨{ toEnvModel := IndSpec.mInstallN hs m hok, blocks := IndSpec.blocksN m hok },
262 fun n hn ls => (IndSpec.agree_M₃N hok m.M n hn ls).symm⟩
.
5.1. Nested blocks
A block of Section 4 mentions the type being defined only directly: a recursive field’s domain is the family itself, a reflexive field’s a function type into it. The standard example of such a nested block is a tree whose children form a list of treestests/e2e/src/nested_rec.lean:L17-L1817inductive Tree (α : Type) where
18 | node : α → List (Tree α) → Tree α
:
inductive Tree (α : Type) where
| node : α → List (Tree α) → Tree αThe domain of the field List (Tree α) is the block List, installed earlier and called the container, applied to the type being defined. The type Tree α there is the nested occurrence, and List (Tree α), the container at given levels and parameters, is an instantiation of the container.
The container may also sit under a binder, as intests/e2e/src/nested_p01.lean:L4-L54inductive P1 where
5 | mk : (Nat → List P1) → P1
inductive P where
| mk : (Nat → List P) → Pand nesting may go deeper. With lists, rose trees andtests/e2e/src/nest_rose_tree.lean:L15-L2415inductive L (α : Type u) : Type u where
16 | nil : L α
17 | cons : α → L α → L α
18
19inductive R (α : Type u) : Type u where
20 | node : α → L (R α) → R α
21
22inductive T : Type where
23 | leaf : T
24 | mk : R T → T
inductive L (α : Type) where
| nil : L α
| cons : α → L α → L α
inductive R (α : Type) where
| node : α → L (R α) → R α
inductive T where
| leaf : T
| mk : R T → Tthe block T is nested through R, a container that is itself nested: inside R T, the constructor R.node holds an L (R T), so T occurs at depth two. Containers with indices are allowed too, as intests/e2e/src/indexed_nested_aux.lean:L13-L1813inductive Vec (α : Type) : Nat → Type where
14 | nil : Vec α 0
15 | cons {n : Nat} : α → Vec α n → Vec α (n + 1)
16
17inductive TV (α : Type) where
18 | node : α → {n : Nat} → Vec (TV α) n → TV α
inductive TV (α : Type) where
| node : α → {n : Nat} → Vec (TV α) n → TV αwith Vec α n the vectors of length n.
5.2. The positivity check
Section 4.1 classifies each field by the shape of its domain as ordinary or reflexive. The checker does this with one walk over the constructorsConLeche/Kernel/Inductives/Positivity.lean:L1453-L14961453def nestPos (ops : CheckerOps m) (env : Env) (ctx : NestCtx) :
1454 Nat → List NestHole → Nat → Nat → Expr → NestState → m (NestFieldKind × Expr × NestState)
1455 | 0, _, _, _, _, _ => throw (.notImplemented "nested positivity: fuel")
1456 | fuel + 1, prog, dep, kb, e, st => do
1457 let hi := ctx.hiAt prog.length
1458 let w ← ops.whnf env dep e
1459 -- `const`: the reduct mentions no member and no hole; the normal
1460 -- form is the input when it mentions none either, else the reduct
1461 if !w.nestOcc ctx.names ctx.nP hi then
1462 return (.ordinary, (if e.nestOcc ctx.names ctx.nP hi then w else e), st)
1463 match w with
1464 | .forallE a b =>
1465 -- `pi`: the domain hole-free, the codomain positive
1466 if a.nestOcc ctx.names ctx.nP hi then
1467 throw (.invalid "nested positivity: non positive occurrence of the datatypes \
1468 being declared")
1469 let (k, nb, st) ←
1470 nestPos ops env ctx fuel prog (dep + 1) (kb + 1) (b.instantiate1 (.fvar dep a)) st
1471 pure (k, .forallE a (nb.abstract1 dep), st)
1472 | _ =>
1473 let args := w.getAppArgs
1474 match w.getAppFn with
1475 | .fvar i _ =>
1476 -- `holeApp`: a hole (`nestHoleAt`) — a member's, the ROOT frame's,
1477 -- or a container frame's, its instantiation in progress — standing
1478 -- for its entry's whole application to the parameters, applied to
1479 -- hole-free indices, at its full arity (official
1480 -- `is_valid_ind_app`, :338–341)
1481 match nestHoleAt ctx prog i with
1482 | some h =>
1483 if args.all (fun x => !x.nestOcc ctx.names ctx.nP hi) &&
1484 args.length + h.key.ds.length == nestArity ctx h.key.cname then
1485 return (if i < ctx.hiAt 0 then
1486 (if kb == 0 then .recursive (i - ctx.nP) else .reflexive (i - ctx.nP))
1487 else .inProgress, w, st)
1488 else throw nestNonValid
1489 | none => throw nestNonValid
1490 | .const n us =>
1491 -- a member constant left after the abstraction (other levels)
1492 if ctx.names.contains n then throw nestNonValid
… 4 more lines, the same for every block, which also looks through containers. On a block of §4′s shape only the first three cases below apply, and they give exactly §4′s classification.
Holes and frames. The walk works on variables in place of types. Before a constructor is examined, every application of the type being defined to the block’s parameters, , is replaced by a variable , a hole, standing for that whole application (a family over the indices). For this to catch every occurrence, the walk is preceded by a uniformity checkConLeche/Kernel/Inductives/Positivity.lean:L1554-L15681554def nestUniformOk (ctx : NestCtx) (cv : ConstantVal) : Bool :=
1555 !cv.type.piDomsOcc ctx.names ctx.nP (ctx.hiAt 0) ctx.nP &&
1556 match nestRootCanon ctx cv with
1557 | some crest => !crest.nestOcc ctx.names 0 0
1558 | none => false
1559
1560/-- `nestUniformOk` at every stored constructor of every member, before
1561the walk (official runs it before anything else): a REJECT with
1562official's wording. -/
1563def nestUniform (ctx : NestCtx) (ctorss : List (List (ConstantVal × Nat))) : m Unit :=
1564 match ctorss.findSome? (·.find? (!nestUniformOk ctx ·.1)) with
1565 | some c => throw (.invalid s!"invalid occurrence of a datatype being declared in the \
1566 type of {c.1.name}: it must be applied to the parameters and universe levels of the \
1567 mutual declaration")
1568 | none => pure ()
: the type being defined occurs in a constructor only applied to exactly the block’s parameters, at the block’s own levels, and in no parameter’s domain. A frame is one type whose constructors are being walked, at one instantiation. It is either the block itself (the root frame) or a container instantiation met in a field, such as at . A container’s frame has holes of its own: in ’s constructors the application of the container to its own parameter is replaced by a hole before is instantiatedConLeche/Kernel/Inductives/Positivity.lean:L1110-L11161110/-- **A stored constructor type at a key** (see the section header): its
1111canonical abstraction (`nestCanonCrest`, the group `names` at the key's
1112levels `us`), then the key's parameters `ds` and the frame's holes
1113`holes` (one per member of `names`, in order) put in. -/
1114def nestCrest (names : List Name) (us : List Level) (ds holes : List Expr) (cty : Expr) :
1115 Option Expr :=
1116 (nestCanonCrest names us ds.length cty).map (·.replaceFVars (nestKeyMap ds holes))
, so it is not confused with an application at another instantiation. Frames nest: walking one may meet another container, whose frame is walked inside it. At any point the types in progress are the type being defined and every instantiation whose frame is open, each present as its hole.
The cases. The walk first puts a field’s domain in weak head normal formConLeche/Kernel/Inductives/Positivity.lean:L14581458 let w ← ops.whnf env dep e
, and every case reads , never the domain’s syntax, so a container reached only by unfolding a definition counts as one. Inside the current frame, the cases are these.
- Ordinary. If mentions no type in progress, the field is ordinaryConLeche/Kernel/Inductives/Positivity.lean:L1461-L14621461
if !w.nestOcc ctx.names ctx.nP hi then
1462return (.ordinary, (if e.nestOcc ctx.names ctx.nP hi then w else e), st)
. - A . If is , the domain must not mention a type in progress, or the block is rejected as not positive, and the walk continues with ConLeche/Kernel/Inductives/Positivity.lean:L1464-L14711464
| .forallE a b =>
1465-- `pi`: the domain hole-free, the codomain positive
1466if a.nestOcc ctx.names ctx.nP hi then
1467throw (.invalid "nested positivity: non positive occurrence of the datatypes \
1468being declared")
1469let (k, nb, st) ←
1470nestPos ops env ctx fuel prog (dep + 1) (kb + 1) (b.instantiate1 (.fvar dep a)) st
1471pure (k, .forallE a (nb.abstract1 dep), st)
, a new variable. The binders passed form the field’s telescope. - A hole. If is a hole applied to arguments, the arguments (the indices of the type the hole stands for) must all be present and must not mention a type in progress, or the block is rejected. The root frame’s hole makes the field recursive (reflexive when a was passed). The hole of a container frame, this one or an enclosing one, stands for an instantiation in progress, such as the tail of a , and is acceptedConLeche/Kernel/Inductives/Positivity.lean:L1475-L14891475
| .fvar i _ =>
1476-- `holeApp`: a hole (`nestHoleAt`) — a member's, the ROOT frame's,
1477-- or a container frame's, its instantiation in progress — standing
1478-- for its entry's whole application to the parameters, applied to
1479-- hole-free indices, at its full arity (official
1480-- `is_valid_ind_app`, :338–341)
1481match nestHoleAt ctx prog i with
1482| some h =>
1483if args.all (fun x => !x.nestOcc ctx.names ctx.nP hi) &&
1484args.length + h.key.ds.length == nestArity ctx h.key.cname then
1485return (if i < ctx.hiAt 0 then
1486(if kb == 0 then .recursive (i - ctx.nP) else .reflexive (i - ctx.nP))
1487else .inProgress, w, st)
1488else throw nestNonValid
1489| none => throw nestNonValid
. A container. If is a stored inductive type at levels applied to arguments, where is neither one of the block’s types nor the quotient type, the first arguments (’s parameter count) are its parameters , the rest its indices. It is requiredConLeche/Kernel/Inductives/Positivity.lean:L1415-L14401415
def nestCont (ctx : NestCtx) (ops : CheckerOps m) (env : Env)
1416(rec : List NestHole → Nat → Nat → Expr → NestState → m (NestFieldKind × Expr × NestState))
1417(prog : List NestHole) (kb : Nat) (n : Name) (us : List Level) (args : List Expr)
1418(st : NestState) : m (NestFieldKind × NestState) := do
1419let q ← unwrapOr (nestContainer ctx n) nestNonValid
1420if args.length < q.1 ||
1421!(args.drop q.1).all (fun x => !x.nestOcc ctx.names ctx.nP (ctx.hiAt prog.length)) then
1422throw nestNonValid
1423-- `Quot` is stored as an `.indInfo` but is no inductive for
1424-- official (`is_nested_inductive_app` asks `is_inductive()`):
1425-- the one name read here. Every other basis type (`Eq`, `Nat`,
1426-- `Empty`, `False`, and `And`) is a container like any stored
1427-- inductive.
1428if n == quotName then throw nestNonValid
1429unless (args.take q.1).all (fun x => x.bvarB == 0 && x.fvarB ≤ ctx.hiAt prog.length) do
1430throw (.invalid "nested positivity: nested inductive datatypes parameters \
1431cannot contain local variables")
1432-- the instance is FULLY applied: the container case
1433-- compares the container's family at the index tuple, and a partial
1434-- application is a function, whose graph does not grow with its values.
1435-- A field's domain is a type, so a checked constructor never has one.
1436let ni ← nestInstType ctx (ctx.hiAt prog.length) ⟨n, us, args.take q.1⟩
1437unless args.length == q.1 + ni.1 do
1438throw (.invalid "nested positivity: type expected (a container instance that is not \
1439fully applied)")
1440nestContKey ctx ops env rec prog kb n us (args.take q.1) q.1 ni.2 st
that- the indices mention no type in progress (they may mention earlier fields, as the of does);
- the parameters mention no variable but the block’s parameters and the holes, so no field and no variable bound by a of the field;
- has ’s number of levels, and every index of is supplied;
- ’s index telescope at ConLeche/Kernel/Inductives/Positivity.lean:L1124-L11581124
def nestInstType (ctx : NestCtx) (hi : Nat) (key : NestKey) : m (Nat × Expr) := do
1125let cvC ← unwrapOr (match ctx.find? key.cname with
1126| some (.indInfo cv _) => some cv
1127| _ => none)
1128(.internal "nested positivity: container vanished")
1129-- the container is applied at its own level count (official:
1130-- `infer_constant`'s "incorrect number of universe levels", which every
1131-- constant of a checked constructor type — and of every
1132-- reduct of it — passed; the model reads the key's constants at it)
1133unless key.lvls.length = cvC.levelParams.length do
1134throw (.invalid "nested positivity: incorrect number of universe levels for a nested \
1135inductive datatype (official: incorrect number of universe levels)")
1136-- the container's parameters are a SYNTACTIC telescope (its canonical
1137-- instantiation exists, so the N2 check below reads
1138-- against the container's recorded index telescope; every stored
1139-- inductive's parameters are binders of its type, as official checks)
1140unless (cvC.type.stripPis key.ds.length).isSome do
1141throw (.invalid "nested positivity: invalid nested inductive datatype, its type does \
1142not bind its parameters (official: ill-formed inductive type)")
1143let ty ← unwrapOr (instPisWith key.ds
1144(cvC.type.instantiateLevelParams cvC.levelParams key.lvls))
1145(.invalid "nested positivity: invalid nested inductive datatype, its type does not bind \
1146its parameters (official: ill-formed inductive type)")
1147let s ← unwrapOr (match ty.piBinders.2 with
1148| .sort s => some s
1149| _ => none)
1150(.invalid "nested positivity: invalid nested inductive datatype, its type is not a \
1151telescope ending in a sort (official: type expected)")
1152if ty.piBinders.1.any (fun b => b.nestOcc ctx.names ctx.nP hi) then
1153throw (.invalid "nested positivity: a container's index telescope mentions the block \
1154(official: unknown constant)")
1155unless ← liftFueled "level comparison" (Level.isEquiv s ctx.sort) do
1156throw (.invalid "nested positivity: mutually inductive types must live in the \
1157same universe")
1158pure (ty.piBinders.1.length, ty)
mentions no type in progress, and ’s sort is equivalent to the block’s.
The parameters may mention the types in progress anywhere, as in ; the frame decides whether that is positive. Then the instantiation is looked upConLeche/Kernel/Inductives/Positivity.lean:L1395-L14041395
def nestContKey (ctx : NestCtx) (ops : CheckerOps m) (env : Env)
1396(rec : List NestHole → Nat → Nat → Expr → NestState → m (NestFieldKind × Expr × NestState))
1397(prog : List NestHole) (kb : Nat) (n : Name) (us : List Level) (ds : List Expr) (nPc : Nat)
1398(cty : Expr) (st : NestState) : m (NestFieldKind × NestState) :=
1399if st.active.contains ⟨n, us, ds⟩ then
1400throw (.invalid "nested positivity: non valid occurrence of the datatypes being \
1401declared (an instantiation in progress, reached through reduction)")
1402else if ds.all (fun x => x.fvarB ≤ ctx.hiAt 0) && st.keys.contains ⟨n, us, ds⟩ then
1403pure (.nested (kb != 0), st)
1404else nestContNew ctx ops env rec prog kb n us ds nPc cty st
. If it is in progress, it is rejected: its syntactic occurrences have become its hole, so meeting it as a type here means reduction produced it. Otherwise its frame is walked, as described next, unless the same instantiation was accepted before. The field is then nested, under the s it passed.- Anything else, such as the type being defined at other levels, a variable that is not a hole, or a quotient, is rejectedConLeche/Kernel/Inductives/Positivity.lean:L1488-L14961488
else throw nestNonValid
1489| none => throw nestNonValid
1490| .const n us =>
1491-- a member constant left after the abstraction (other levels)
1492if ctx.names.contains n then throw nestNonValid
1493-- `contApp`: a stored inductive at a concrete instantiation
1494let (k, st) ← nestCont ctx ops env (nestPos ops env ctx fuel) prog kb n us args st
1495pure (k, w, st)
1496| _ => throw nestNonValid
.
Walking a frame. At an instantiation , the walk checks that has a type, and introduces a hole for itConLeche/Kernel/Inductives/Positivity.lean:L1333-L13511333def nestFrame (ctx : NestCtx) (ops : CheckerOps m) (env : Env)
1334 (rec : List NestHole → Nat → Nat → Expr → NestState → m (NestFieldKind × Expr × NestState))
1335 (prog : List NestHole) (hi : Nat) (us : List Level) (ds : List Expr) (nPc : Nat)
1336 (grp : List (Name × Expr)) (st : NestState) : m NestState := do
1337 -- K.52: the instantiation `C.{us} ds` is TYPED at the
1338 -- frame's own depth (its holes typed by their containers' formers, as
1339 -- the constants they stand for). Official imposes it: after the nested
1340 -- elimination, `add_inductive` type-checks every replaced nested
1341 -- application `I Ds` (`tc.check(nested, …)` over `m_aux2nested`,
1342 -- `inductive.cpp` v4.32.2 :1186–1189, v4.34.0 :1320–1323), and a reduct
1343 -- of a typed term is typed. The model grades the key's parameters in
1344 -- the frame's stack context by it.
1345 let _ ← ops.inferType env hi (Expr.mkAppN (.const (grp.headD default).1 us) ds)
1346 let prog' := (grp.mapIdx fun _ (c, _) =>
1347 ({ key := ⟨c, us, ds⟩, base := hi } : NestHole)).reverse ++ prog
1348 let ctors ← nestGroupCtors ctx nPc (grp.map (·.1))
1349 let (_, st) ← nestCtors ctx ops env (fun _ => rec) prog' (hi + grp.length) us ds (grp.map (·.1))
1350 (grp.mapIdx fun i (_, ty) => Expr.fvar (hi + i) ty) ctors st
1351 pure st
: a variable standing for the container’s type former applied at this instantiation. It then takes each of ’s constructors in turnConLeche/Kernel/Inductives/Positivity.lean:L1213-L12621213def nestCtors (ctx : NestCtx) (ops : CheckerOps m) (env : Env)
1214 (rec : Expr → List NestHole → Nat → Nat → Expr → NestState →
1215 m (NestFieldKind × Expr × NestState))
1216 (prog : List NestHole) (hi : Nat) (us : List Level) (ds : List Expr) (names : List Name)
1217 (holes : List Expr) :
1218 List (ConstantVal × Nat) → NestState → m (List (List NestFieldKind × Expr) × NestState)
1219 | [], st => pure ([], st)
1220 | (cv, nF) :: cs, st => do
1221 -- the constructor's level parameters are distinct (the substitution law
1222 -- instantiates them as the recorded reading does;
1223 -- every stored constant passed `checkConstantVal`'s own check)
1224 unless Name.nodup cv.levelParams do
1225 throw (.invalid "nested positivity: invalid nested inductive datatype, its constructor \
1226 has a duplicate universe level parameter (official: duplicate universe level \
1227 parameter)")
1228 -- the container's constructor at the key, its group's WHOLE
1229 -- applications replaced by the holes BEFORE its parameters are
1230 -- instantiated (`nestCrest`), WITHOUT a β-step (official's
1231 -- `instantiate_pi_params`, `inductive.cpp` v4.34.0)
1232 let crest ← unwrapOr
1233 (nestCrest names us ds holes (cv.type.instantiateLevelParams cv.levelParams us))
1234 (.invalid "nested positivity: invalid nested inductive datatype, its constructor type \
1235 does not bind the parameters (official: ill-formed constructor)")
1236 let ty ← ops.inferType env hi crest
1237 let _ ← ops.ensureSort env hi ty
1238 let (ks, nds, cur, st) ← nestFields (rec crest) prog hi
1239 (.invalid "nested positivity: invalid nested inductive datatype, its constructor type \
1240 does not bind its fields (official: ill-formed constructor)") nF 0 crest st
1241 -- U4 on the instantiated constructor: no later field and not the result
1242 -- reads a field that is
1243 -- not ordinary (recursive, reflexive, nested or in progress) — on the
1244 -- walked (whnf'd) telescope. Official rejects every instance
1245 -- (charter item 9)
1246 if (List.range nF).any (fun i => ks.getD i .ordinary != .ordinary &&
1247 structUsedLater (closeTelescope nds hi cur) 0 i) then
1248 throw (.invalid "nested positivity: non valid occurrence of the datatypes being \
1249 declared (a later field or the result depends on a recursive or nested field)")
1250 -- official's "invalid return type" on the instantiated constructor; its
1251 -- result is headed by its hole, applied to its indices alone (never
1252 -- fires: a stored constructor's result is its inductive applied to
… 10 more lines:
- its stored type at , the frame’s applications replaced by the holes and the parameters instantiated at , must be a type;
- every field is classified by the cases above, inside this frame;
- no later field and no result index may depend on a field that is not ordinary (§4′s second shape condition, at the instantiation);
- the result’s indices mention no type in progress;
- the constructor’s walked type is recorded, every hole read back as the application it stands for, for the recursors below.
The root frameConLeche/Kernel/Inductives/Positivity.lean:L1591-L15991591def nestRoot (ops : CheckerOps m) (env : Env) (ctx : NestCtx) (holes : List Expr) :
1592 List (List (ConstantVal × Nat)) → NestState →
1593 m (List (List (List NestFieldKind × Expr)) × NestState)
1594 | [], st => pure ([], st)
1595 | cs :: css, st => do
1596 let (o, st) ← nestCtors ctx ops env (fun crest => nestPos ops env ctx (whnfWalkFuel crest)) []
1597 (ctx.hiAt 0) (ctx.lps.map .param) ctx.params ctx.names holes cs st
1598 let (os, st) ← nestRoot ops env ctx holes css st
1599 pure (o :: os, st)
is the same loop over the block’s own constructors.
On , the field of type meets the container case at with the parameter . In ’s frame, has no field, and has the fields , the root’s hole, which is recursive, and , the frame’s own hole, which is accepted. In the walk passes a over and meets at the same way. For the rose trees, ’s field meets at ; in ’s frame, with its hole , ’s fields are and , so ’s frame opens inside ’s, with its hole , and there ’s fields are and , both accepted. A container that uses its parameter to the left of an arrow, such as with a constructor of type , fails at the instantiation: in its frame that field’s domain is , a whose domain mentions the hole. Whether a container is positive in its parameter is thus decided at the instantiation, by walking it; nothing about it is recorded when the container is installed.
OVERVIEW.md), and records the instantiations it acceptedConLeche/Kernel/Inductives/Positivity.lean:L658-L664658structure NestState where659
keys : Array NestKey := #[]660
/-- the instantiations whose frames are being walked (every group661
member at the key), outermost last: an instantiation walked at the662
EMPTY stack (`nestWalkStack`) is still in progress for the cycle check663
(`nestContKey`) -/664
active : List NestKey := []and, where it can, does not walk an accepted one again.
5.3. Classes and their recursors
A recursion over a tree has to pass through the list of its children. So a nested block’s recursors eliminate several types, its classes. They are the type being defined and the container instantiations the recursion passes through: every instantiation at which a field of the block or of a walked container lands, read with its holes back as the types they stand for, is a class. For the classes are and ; for the rose trees, , and . Each class gets a motive and a recursor ( for the type being defined, for the others), and each constructor of each class a minor premise. All recursors share one prefix, made of the parameters, the motives and the minor premises. A minor premiseConLeche/Kernel/Inductives/GenRec.lean:L138-L158138/-- Constructor `x`'s minor premise type at depth `d`, of class `c`: its
139declared fields, then per recursive field `f` (walked type `w`) the
140inductive hypothesis `∀ a⃗, motive_t e⃗ (f a⃗)`, over the motive at the
141declared result indices and the constructor applied. -/
142def ClassGen.minorTy (g : ClassGen) (c : Nat) (x : ClassCtor) (d : Nat) : Option Expr := do
143 let ci := g.cls.getD c default
144 let (fvs, res) ← openPisAtFvars x.nF x.tyD d
145 let ws ← targetPiDomsWith fvs x.tyN
146 let recs := (List.range x.nF).filterMap fun i =>
147 match x.kinds.getD i .ordinary with
148 | .recursive t tele => some (i, t, tele)
149 | .ordinary => none
150 let ihs ← (List.range recs.length).mapM fun l => do
151 let (i, t, tele) := recs.getD l default
152 let e := d + x.nF + l
153 let (xs, idx) ← g.ihParts t tele (ws.getD i default) e
154 pure (closeTelescope (xs.map g.binder) e
155 (Expr.mkAppN (g.motVar t) (idx ++ [Expr.mkAppN (fvs.getD i default) xs])))
156 let concl := Expr.mkAppN (g.motVar c)
157 (res.getAppArgs.drop ci.nPc ++ [Expr.mkAppN (.const x.cv.name ci.lvls) (ci.ds ++ fvs)])
158 pure (closeTelescope (fvs.map g.binder ++ ihs) d concl)
is generated as in Section 4.1 from the constructor’s fields at the instantiation, with one inductive hypothesis per recursive or nested field: the motive of the class where the field lands, under the field’s s, read off the walk’s recorded type (a recursor’s typeConLeche/Kernel/Inductives/GenRec.lean:L175-L182175/-- **The generated recursor type** at class `c`. -/
176def classGenRecTy (g : ClassGen) (c : Nat) : Option Expr := do
177 let pre := g.pre
178 let rP := pre.length
179 let (ifs, maj) ← g.major c rP
180 let t : Expr := .fvar (rP + ifs.length) maj
181 pure (closeTelescope (pre ++ ifs.map g.binder ++ [maj]) 0
182 (Expr.mkAppN (g.motVar c) (ifs ++ [t])))
, a ruleConLeche/Kernel/Inductives/GenRec.lean:L184-L207184/-- **The generated rule** of a recursor at class `c` for its constructor
185`x`: `λ p⃗ (prefix) f⃗, minor f⃗ (λ a⃗, rec_t p⃗ (prefix) e⃗ (f a⃗))…`, the
186callee `rec_t` the family's recursor at the landing class `t`
187(`recOf`). -/
188def classGenRule (g : ClassGen) (recOf : Nat → Option Name) (rlvls : List Level) (c : Nat)
189 (x : ClassCtor) : Option Expr := do
190 let pre := g.pre
191 let rP := pre.length
192 let (s, _) ← (List.range g.slots.length).zip g.slots |>.find? fun (_, sl) =>
193 match sl with | .minor c' C _ => c' == c && C == x.cv.name | _ => false
194 let (fvs, _) ← openPisAtFvars x.nF x.tyD rP
195 let ws ← targetPiDomsWith fvs x.tyN
196 let pvars := (List.range rP).map fun i => if i < g.nP then g.params.getD i default else
197 g.slotVar (i - g.nP)
198 let ihs ← (List.range x.nF).filterMapM fun i =>
199 match x.kinds.getD i .ordinary with
200 | .ordinary => some none
201 | .recursive t tele => do
202 let f := fvs.getD i default
203 let (xs, idx) ← g.ihParts t tele (ws.getD i default) (rP + x.nF)
204 let r ← recOf t
205 pure (some (closeLams (xs.map g.binder) (rP + x.nF)
206 (Expr.mkAppN (.const r rlvls) (pvars ++ idx ++ [Expr.mkAppN f xs]))))
207 pure (closeLams (pre ++ fvs.map g.binder) 0 (Expr.mkAppN (g.slotVar s) (fvs ++ ihs)))
).
Example 13 (’s recursors). With the motive for and the motive for , the shared prefix is the parameter, the two motives, a minor premise for and one for each of ’s constructors at the instantiation:
(the is the same prefix; the constructors’ own parameter arguments are left implicit). The minor for has one inductive hypothesis, at the nested field; the minor for has two, at the field that held the hole and at the one that held . With for the prefix , the three rules are
A nested field under binders gets its hypothesis under the same binders: for the minor premise is
Where a rule of fires. A rule of Section 4.2 takes the constructor’s levels and parameters to be the recursor’s own. At a container class they are not: in the class has the parameter , not the recursor’s , and ’s level names, not ’s. So each rule of a recursor at a container class stores the instantiationConLeche/Kernel/Env.lean:L144-L150144* `.nested lvls pins` — an auxiliary recursor's rule at an outside
145 major (a nested block): the comparands are fixed instantiations,
146 the class's levels and parameters the generated recursor type's major
147 domain applies the container to (`auxRuleFire`) — `lvls` are levels
148 over the recursor's level parameters, `pins` expressions in the
149 recursor's rule-prefix context, instantiated at the redex's levels and
150 leading arguments. -/
, that is, the class’s levels and parameters as terms in the recursor’s level parameters and parameters (the stored rules at a container classConLeche/Kernel/Inductives/RecCheck.lean:L602-L614602/-- **One checked recursor's stored rules, at its major**: `sumRules` at the
603MAJOR's parameter count and constructors;
604at an OUTSIDE major (a nested block's auxiliary recursor) every rule
605fires `auxRuleFire` (`.nested` at the major's instantiation), at a
606member major as `sumRules` builds it. -/
607def tgtStoredRules (find? : Name → Option ConstantInfo)
608 (cv : ConstantVal) (rP : Nat) (M : TargetMajor) (rhss : List Expr) : List RecRule :=
609 let rules := sumRules find? cv.name M.nPc M.ctors rhss
610 match M.member with
611 | none =>
612 let fire := auxRuleFire M rP
613 rules.map fun rl => { rl with fire := fire }
614 | some _ => rules
). In the certified case of (Section 4.2.8), the constructor application’s levels and parameters are compared with the stored ones, instantiated at the recursor’s levels and parameter argumentsConLeche/Kernel/CoreDefs.lean:L917-L924917def recFireComparands (rl : RecRule) (lps : List Name)
918 (us : List Level) (cvjLps : List Name) (args : List Expr)
919 (rP : Nat) : List Level × List Expr :=
920 match rl.fire with
921 | .nested lvls pins =>
922 (lvls.map (Level.subst lps us),
923 pins.map fun p => Expr.instSpine (args.take rP) (rP - 1)
924 (p.instantiateLevelParams lps us))
(the levels by the level oracle, the parameters definitionallyConLeche/Rules/Rel.lean:L213-L224213 (iotaCase3 env.find? rl cvj
214 (recFireComparands rl cv.levelParams us cvj.levelParams
215 e.getAppArgs rP).1 = true →
216 Level.isEquivList usj
217 (recFireComparands rl cv.levelParams us cvj.levelParams
218 e.getAppArgs rP).1 = some true) →
219 (iotaCase3 env.find? rl cvj
220 (recFireComparands rl cv.levelParams us cvj.levelParams
221 e.getAppArgs rP).1 = true →
222 DefEqList env d (major.getAppArgs.take rl.ctorParams)
223 (recFireComparands rl cv.levelParams us cvj.levelParams
224 e.getAppArgs rP).2) →
), and the index comparison is as before. In the other cases the rule fires comparing nothing, as for a simple block. The constructor’s fields are what follows its own parameters.
Large elimination. A nested block may eliminate into every sort only if its sort is never zeroConLeche/Kernel/Inductives/BlockRec.lean:L82-L8482def blockLargeElimAllowed (p : BlockShape) (nested : Bool) : Bool :=
83 p.resSort.isNeverZero ||
84 (p.large && p.k == 1 && !nested && (p.numCtors == 0 || p.numCtors == 1))
(where the checker enforces itConLeche/Kernel/Inductives/GenRec.lean:L548-L552548 unless 0 < p.k do
549 throw (.invalid "generated recursor: the block declares no family")
550 if p.large && !blockLargeElimAllowed p (nestedBit || Ms.any (·.member.isNone)) then
551 throw (.invalid "generated recursor: large eliminator on a block whose sort may be Prop \
552 (official: elim_only_at_universe_zero)")
), as in the official kernel. A nested proposition eliminates only into , and the subsingleton criterion is never asked of a nested block.
* the CLASSES — the types the family eliminates, one per MOTIVE of the16
shared rule prefix (a motive `∀ ı⃗ (t : I.{us} D⃗ ı⃗), Sort ℓ` names the17
class `I.{us} D⃗`);18
* the LAYOUT of the prefix — which binder after the parameters is which19
motive, which is the minor premise of which constructor of which class;20
* per minor premise, which fields have an inductive hypothesis and at21
which class (`∀ a⃗, motive_t e⃗ (f_i a⃗)`), and per recursor the class its22
conclusion eliminates.23
24
This file READS that data off the recursor types, syntactically, and25
checks nothing. Everything it returns is re-established by the stage:26
every class is checked like a major (`targetMajorOf`), the inductive27
hypotheses read here must be exactly the positivity check's recursive28
fields, and every recursor TYPE is generated from the classes and the29
positivity check's recorded constructors and compared by `isDefEq` with30
the stream's. What the stage installs is the generated family, never31
anything read here: a wrong reading can only make the generated type32
differ from the stream's, i.e. reject., and each class is then checked: the type being defined at the block’s levels and parameters, or a stored inductive type, not the quotient, whose parameters mention the type being defined and nothing but the block’s parameters, in the block’s sortConLeche/Kernel/Inductives/RecCheck.lean:L410-L447410
| none => do411
-- an OUTSIDE inductive (a nested block's container)412
if I == quotName then413
throw (.invalid "target rec: the recursor's major is Quot, which is no inductive")414
let some (nPc, ctors) := targetCtorsOf fe I415
| throw (.invalid "target rec: the recursor's major is not a stored inductive")416
let ds := args.take nPc417
unless ds.length == nPc &&418
ds.all (fun x => x.bvarB == 0 && x.fvarB ≤ p.nP) do419
throw (.invalid "target rec: the major's parameters mention more than the \420
recursor's parameters")421
-- **An auxiliary type of the block**, official's `is_nested`: some422
-- parameter `Dᵢ` names a member of the block (`inductive.cpp` v4.34.0423
-- :1033–1049: `find` over each of the `nparams` arguments for a424
-- constant of `m_new_types`; every auxiliary type is such an425
-- application, restored verbatim by `restore_nested`, and official426
-- accepts an auxiliary recursor exactly at an auxiliary type). The427
-- calls' proof reads it: a call on a field whose walked type names no428
-- member and no hole targets no class. Read without whnf and without429
-- entering a free variable's annotation (`nestOcc` at an empty hole430
-- range). Every class resolved here SEEDS the positivity walk431
-- (`classSeeds`, `GenRec.lean`), so it is a node of the walk by432
-- construction: nothing ties it to a node here.433
unless ds.any (fun x => x.nestOcc p.memberNames 0 0) do434
throw (.invalid "target rec: the recursor's major is an outside inductive at an \435
instantiation that is no auxiliary type of the block (official generates no such \436
auxiliary recursor: `elim_nested_inductive`, `is_nested`)")437
let (nIdx, sI) ← targetOutsideInst fe I us ds438
-- **Q1**: an outside major in ANOTHER439
-- universe than the block (a Type block's family eliminating a440
-- Prop inductive, or the converse) is refused here: the441
-- elimination guard is the BLOCK's (`blockLargeElimAllowed`),442
-- and it says nothing about another universe's inductive443
unless ← liftFueled "level comparison" (Level.isEquiv sI p.resSort) do444
throw (.invalid "target rec: the recursor's major lives in another universe than \445
the block (Q1)")446
pure { ind := I, lvls := us, ds := ds, nPc := nPc, nIdx := nIdx, ctors := ctors,447
member := none, pfvs := pfvs }. Every class other than the type being defined is walkedConLeche/Kernel/Inductives/Positivity.lean:L1645-L16531645
def nestSeeds (ops : CheckerOps m) (env : Env) (ctx : NestCtx) :1646
List (NestKey × Nat) → NestState → m NestState1647
| [], st => pure st1648
| (key, nPc) :: ks, st => do1649
let F := key.ds.foldl (fun a d => max a (whnfWalkFuel d)) fuelSlack1650
let (_, cty) ← nestInstType ctx (ctx.hiAt 0) key1651
let (_, st) ← nestContKey ctx ops env (nestPos ops env ctx F) [] 0 key.cname key.lvls1652
key.ds nPc cty st1653
nestSeeds ops env ctx ks stas a container met at the root, so every class is an instantiation the walk visited. A field landing at a class for which the stream gives no recursorConLeche/Kernel/Inductives/GenRec.lean:L442-L445442
| x :: xs => do443
let gen ← unwrapOr (classGenRule g recOf (cvR.levelParams.map .param) c x)444
(.invalid s!"generated recursor: the rule of {x.cv.name} calls a class whose recursor the \445
stream omits (official: unknown constant)")is rejected. One class may be visited at several places. In a block with a field of type tests/e2e/src/corner_nestind_f13_listrose.lean:L12-L1512
inductive RL (α : Type) : Type where13
| node (a : α) (cs : List (RL α)) : RL α14
inductive TL : Type where15
| node (l : List (RL TL)) : TL, with a rose tree over lists, that instantiation is met at the block’s field and again inside , and the generator checks that every visit gives each field the same inductive hypothesisConLeche/Kernel/Inductives/GenRec.lean:L251-L265251
/-- **Node agreement at one recursive field** (K.53′): at every entry252
`E` of the constructor, field `i` (opened at the datum's variables `fvs`)253
has the datum's telescope `tele`, the datum's leaf head and indices, and254
a leaf class matching the inductive hypothesis's class `Mc` per component255
(`targetK53`). -/256
def classNodesAgree (ops : CheckerOps m) (env : Env) (p : BlockShape) (formerTys : List Expr)257
(Mc : TargetMajor) (tele : List Expr) (leaf : Expr) (fvs : List Expr)258
(i : Nat) (ctor : Name) : List NestCtorNf → m Unit259
| [] => pure ()260
| e :: es => do261
let some f := ((targetPiDomsWith fvs e.ty).getD [])[i]?262
| throw (.invalid s!"generated recursor: a recorded normal form of {ctor} is too short")263
unless ← targetK53 ops env p formerTys Mc tele leaf f do264
throw (.invalid s!"generated recursor: field {i} of {ctor} does not land at its inductive \265
hypothesis's class at every node of the positivity check (official: invalid recursor)").
5.4. The model, as a sketch
What follows is intuition for how the construction of Section 4.2 carries over. The details are in the linked con-leche proof.
The operator. Fix the parameters, and let be the approximant, the family the operator is applied to, as in Section 4.2. A container field ranges over the container’s own family at the instantiation, with in place of the type being defined: for Tree, the field List (Tree α) of ranges over the lists whose entries all lie in . That family is known, because every installed block leaves a clause in the modelConLeche/Model/Env/BlockLfp.lean:L33-L4433**The clause** (`LfpClause`), at every level assignment `ψ` and every
34parameter frame `ρp` satisfying the parameter telescope (exactly the
35quantification `BlockModelAt` has, which is what the install proves):
36
37* `maps` — `Φ` maps the tuple space into itself;
38* `fitAcc` — the hole fit is ACCESSIBLE (`LfpDatum.FitAcc`), with a
39 bound of the level at a `Type`-valued block;
40* `fibre` — component `c`'s fibre at `(X, t)` is the set of injections
41 of the spines fitting one of `c`'s constructors at `(X, t)`;
42* `leaf` — a member's former, at fitting parameters and its own
43 indices, is the least pre-fixed tuple's component at the index
44 tuple.
: its type former denotes, at all parameter values, the least fixed point of its operator. So the container’s least fixed point is reused, at each approximant, and nothing new is constructed for it. At depth two the same holds one level down: an element of holds a list from ’s family at the parameter .
Accessibility. The block’s operator stays accessible, because the walk that checked positivity also bounds the support. An element of the container’s family at depends on only through the positions the walk found: the fields that held the root’s hole, such as the entries of a list, and, through the fields that held the frame’s own hole, the same positions of its sub-values. Read as an operator of two arguments, the approximant and the container’s own family, the container’s operator at the instantiation is accessible by the same case-by-case argument as §4′sConLeche/Semantics/Inductives/HoleAcc.lean:L50-L5550| case | accessibility | bound | the point |
51|---|---|---|---|
52| a hole-free reading | `ConstOn.accOn` | `∅` | `ConstOn.ptOn` |
53| the `whnf` step | `AccOn.of_eqOn` | the reduct's | `PtOn.of_eqOn` |
54| Π, hole-free domain | `AccOn.pi` | `piBound` | `PtOn.pi` |
55| a hole at hole-free arguments | `AccOn.holeApp` | `{pt}` | `PtOn.holeApp` |
, run on the frameConLeche/Model/Inductives/ContAcc.lean:L15-L3415/-!
16# A container instance is accessible
17
18The container pieces of the accessibility induction on the positivity
19derivation (`posD_acc`, `PosDerivAcc.lean`):
20
21* the leaf (`accConcl_of_frameAccOut`, over `keyLeaf`): a frame's
22 accessibility at the key frames makes the container instance
23 accessible along the enclosing relation;
24* a frame's accessibility at EMPTY enclosing frames, read back at the
25 frameless relation (`frameAccOut_unextend`, with
26 `HoleRelA.extendEmpty`);
27* a key accessible at the block's own depth (`KeyAcc`) makes a cache
28 hit's instance accessible (`contHit_acc`, at the enclosing relation
29 seen at that depth, `HoleRelA.dropBase`).
30
31The TYPE REGIME of a container instance: the container lives at the
32block's level (`n2_sort`, the kernel's N3), so a truth-valued fibre of a
33container is empty (`LfpClause.injNePt`).
34-/
; and the least fixed point of such an operator is accessible in its first argumentConLeche/SetModel/Access.lean:L530-L540530/-- **THE NESTED CASE: the least tuple is accessible in its parameter.**
531If the joint operator `Θ` (parameter components first, then its own) is
532`A`-accessible and maps the space into itself, then `X ↦ lfp_Y Θ(X, Y)`
533is `accPaths A`-accessible — no key, no wide operator, no container
534presentation. (W) of each section and its monotonicity come from
535`closed_of_acc` / `AccTuple.monoTuple`. -/
536theorem lfpP_acc {Θ : (Nat → V) → Nat → V} {A : V} (hA : w ≠ 0 → A ∈ˢ (tg w : V))
537 (hmaps : ∀ X Y, InTupleSpace w kX IsX X → InTupleSpace w kY IsY Y →
538 InTupleSpace w kY IsY (Θ (catT kX X Y)))
539 (hacc : AccTuple w (kX + kY) (catT kX IsX IsY) kY IsY Θ A) :
540 AccTuple w kX IsX kY IsY (lfpP w kX kY IsY Θ) (accPaths A) := by
, the supports of an element’s sub-values collected along paths into one support. Monotonicity follows from accessibility as in §4. For the recursor, con-leche also proves the walk’s readings monotone case by caseConLeche/Semantics/Inductives/HoleMono.lean:L17-L2317| `nestPos` case | lemma |
18|---|---|
19| the `whnf` step before every case | `MonoOn.of_eqOn` (the reading of the reduct is the reading of the term — `WhnfClaim`'s conclusion shape) |
20| `const`: the reduct mentions no hole | `ConstOn.of_noBVar`, `ConstOn.monoOn` |
21| `pi`: hole-free domain, positive codomain, fibres types | `MonoOn.pi` |
22| `holeApp`: a hole applied to hole-free arguments | `MonoOn.holeApp` |
23| `contApp`: a container instance | `Model/Env/BlockLfpMono.lean`'s container case, through the container's lfp clause |
; at a container that compares the container’s least fixed points at two instantiations, by leastnessConLeche/SetModel/HoleClose.lean:L17-L2017* `lfpTuple_le_on` — the least tuple lies below `B` on a group `G` of
18 its components as soon as the operator, at the least tuple with its
19 `G`-components replaced by `B`'s, does: a container frame abstracts
20 only the reached part of its group, the rest is read concretely.
. Nothing about the container’s parameter is recorded when it is installed. The walk at the instantiation is what makes the argument go through.
The family. The container lives in the block’s sort (the container case of Section 5.2 requires it), so its family at an approximant in the universe is in the universe, and the operator maps the universe to itself. With accessibility, Theorem 8 gives a closed family, and the block’s family is the least fixed point as in Section 4.2.4, with its fixed-point equation and its induction principle: a property that holds of every element the operator produces from elements having it holds of the whole family.
The recursors. For a simple block, Section 4.2 defines the recursor’s value by the recursion equation and obtains it as a graph in the set-theoretic sense: a relation between elements of the family and values, the least one closed under the equation, which is then shown to be a function. A nested block has several recursors, one per class, and they call each other: the rules of Section 5.3, read as equations, define on in terms of at the list of children, and on in terms of at the head. So the two are defined together, by one relation. It is the least relation between a class (here Tree α or List (Tree α)), an element of that class, and a value, closed under all these equations at once. As in Section 4.2 it is single-valued, because tagged tuples are injective. At the elimination level zero both sides are the point, and a large eliminator out of a proposition is refused for nested blocks, so the decodings argument of Section 4.2 is not needed. It is total (every element of every class has a value) by one induction over all the classes together: the induction principle of Tree’s family, which at the nested field List (Tree α) hands over to the induction principle of List’s family at that instantiation, and so on as deep as the walk went (the induction over the classesConLeche/SetModel/NestRec.lean:L8-L46 8/-!
9# The nested recursor's graph kit: majors over several CLASSES
10
11The graph kit (`SetModel/GraphRec.lean`, `GraphRecKit`) asks ONE thing of
12the majors: an induction principle (`ind`). At a flat block the majors
13are the members' carriers and `ind` is the block's lfp induction. At a
14NESTED block the recursor family also ranges over the CONTAINER CLASSES
15— `List Tree`, `Rose T`, `List (Rose T)` — each the least fixed point of
16its OWN operator, at its OWN instantiation (the container's lfp clause at
17the parameter frame the instantiation reads). This module builds the
18kit over such a family of classes and proves `ind` from the classes'
19own clauses, by a STRENGTHENED PREDICATE:
20
21* a class is a clause (`SClause`: an operator per parameter frame, its
22 fit relation and injections) at its TRUE frame `fr b`;
23* its container fields' classes are reached, during the induction, at
24 SEPARATED frames — the instantiation read at the enclosing class's
25 separated tuple; which frames count is the kit's `Adm` (admissible:
26 "a frame whose parameter positions hold elements satisfying `G`");
27* at such a frame the class's own induction runs with the predicate
28 "lies in the TRUE class ∧ the property"; membership in the true class
29 is re-established at each constructor by the class's clause at the
30 TRUE frame (the fibre law, backwards, and closure), after moving the
31 constructor's fit from the separated frame and tuple to the true ones
32 (`trans`). The only inclusion the induction itself produces is the
33 separation `sep ⊆ carrier`.
34
35**What is used of a class** (charter items 2, 4): its clause at each
36frame the induction visits (`OkAt`: monotone in its own holes, a closed
37tuple, the fibre law) and ONE fact per class tying a frame to the true
38one, `trans`: a constructor fitting at an admissible frame, at holes
39below the true carrier, fits at the true frame and the true carrier. At
40the term level `trans` is `LfpDatum.hfits_mono` along the relation
41between the instantiation's two hole frames (the constructor's
42positivity AT THE INSTANTIATION, `CtorPos` — what `nestPos`'s container
43descent certifies, keyed by the instantiation) followed by the clause's
44own `fitsMono` at the true frame. Nothing is asked of a container "in
45its parameter": no monotonicity of its value or carrier, no Bekić, no
46joint (wide) operator.
, read at the recursors’ classesConLeche/SetModel/NestRecCls.lean:L6-L29 6/-!
7# The nested kit's induction at the RECURSOR's classes
8
9The nested kit `NestKit` (`SetModel/NestRec.lean`: `trans` at every
10admissible frame) proves the induction principle of its majors,
11the tagged elements `nenc b m t x` of every CLAUSE class `b` (a member
12block, or a container at an instantiation — a positivity NODE),
13component `m`. `NestNodeInd` is that principle, DECODED (a major's
14predecessors are the kit's `pred` at a decoding at the true frame and
15carrier), and the one interface the recursor side reads; `NestKit.toNodeInd`
16is its instance.
17
18The recursor model's graph kit (`graphKitG`,
19`Model/Inductives/BlockRecGraph.lean`) is stated over the RECURSOR's
20classes: recursor `c`'s majors are `tagged c t x`, its decodings are the
21rule data's, and its predecessors are the rule's call targets (`predR`).
22`NestNodeInd.ind_recNodesOn` transports the one to the other, with
23SEVERAL nodes per class (`Rel c b`: node `b` visits recursor `c`'s class
24at component `mOf c b`; one instantiation may be visited at several
25nodes), on a set `S` of classes that have nodes.
26The property is read back along every node of a class: `P'` holds at the
27node major `nenc b (mOf c b) t x` when `P` holds at `tagged c t x`
28for every related recursor `c`; the kit's predecessors must contain every
29related recursor's call targets (`hpredR`).
). Each recursor is then this relation restricted to its class, curried over its arguments.
The ι laws. They follow as in Section 4.2. For a rule of the major is a constructor of List applied; the recursor’s fit puts its value in the class at the recursor’s parameters, and List’s fixed-point equation there identifies the constructor and the fields with the rule’s (the graph’s equation, lifted to the lawConLeche/Model/Inductives/BlockRecLaw.lean:L877-L883877/-- **A new block recursor's rule law, at either firing**. `hpins` is
878`RecRuleLaw`'s outer `.nested` conjunct — the pins'
879open readings and their context-guarded grading — vacuous at a `.plain`
880rule; `hrhs` receives no parameter comparison at either firing: the
881major's membership in the family at the comparands identifies the fired
882spine with the rule's own (`BlockRuleDataAt`'s `mk` conjunct). -/
883theorem blockRecRuleLaw_gen {env : Env} {m₃ : EnvModel V env} {φ : Name → Nat}
).
6. Consequences of extensionality
The official kernel has two rules of definitional equality that the fragment does not have: η for structures, and unit-likeness. Section 7 lists them among the omissions. This section shows that adding either would cost one case of the induction of Section 2.7 and no new idea. In the model, each rule follows from what the values are. A proof is the point, a function is the graph of its applications, a constructor value is a tagged tuple of its fields, and a member of an inductive family is a value that one constructor step produces. Each of these says that a set is determined by its parts, and each is an extensionality law of the assumed set theory or follows directly from one. η for functions, which the fragment has, is the same law at graphs: a member of a function space is the graph of its applicationswhitepaper/Fragment/Lib.lean:L110110 graph_app_eq : ∀ {A f : V} {B : V → V}, Mem f (piSet A B) → graph (fun x => app f x) A = f
, and §2 proved its case. The K rule of Section 4.2.8, which the fragment also has, rests on a law of the same kind, inversion of the least fixed point.
Recall what Section 4 sets up. A constructor application of a block whose family is type-valued denotes a tagged tuplewhitepaper/Fragment/IndLib.lean:L50-L6450 /-- The n-ary tuple of a list of sets. -/
51 tuple : List V → V
52 /-- Tuples are injective. -/
53 tuple_inj : ∀ {xs ys : List V}, tuple xs = tuple ys → xs = ys
54 /-- A tuple of members of a positive universe is a member of it. -/
55 tuple_mem_univ : ∀ {n : Nat} {xs : List V}, n ≠ 0 →
56 (∀ x ∈ xs, Mem x (univ n)) → Mem (tuple xs) (univ n)
57 /-- A value tagged with a number. -/
58 tag : Nat → V → V
59 /-- Tags are injective. -/
60 tag_inj : ∀ {i j : Nat} {x y : V}, tag i x = tag j y → i = j ∧ x = y
61 /-- A tagged member of a positive universe is a member of it. -/
62 tag_mem_univ : ∀ {n i : Nat} {x : V}, n ≠ 0 → Mem x (univ n) → Mem (tag i x) (univ n)
63 /-- A tagged value is never the point. -/
64 tag_ne_pt : ∀ {i : Nat} {x : V}, tag i x ≠ pt
, the constructor’s number whitepaper/Fragment/IndCommon.lean:L336-L338336/-- **A constructor's value** at its fields: the point at a
337proposition, the tagged tuple above. -/
338def ctorVal (j : Nat) (fs : List V) : V := if S.z ls then pt else tag (S.tagOf j) (tuple fs.reverse)
followed by its fields. The family at parameters and indices denotes the least set closed under the constructor stepswhitepaper/Fragment/IndSem.lean:L1458-L14611458/-- **The family**: the fibre at the index values `is` is the least
1459fixed point of the block's operator inside the set theory.
1460Con-leche: `LfpDatum.carrier`, `lfpTuple` of the hole operator. -/
1461noncomputable def Fam (ps is : List V) : V := lfpFamSet (S.u₀ ls) (S.famOp M ls ps) is
, so that a member of the family is a tagged tuple that one constructor step produceswhitepaper/Fragment/IndSem.lean:L1478-L14861478/-- **The family's case analysis**: a member of the fibre at `is` is
1479a constructor instance over the family at `is`, and conversely.
1480Con-leche: `LfpClause.carrier_case`. -/
1481theorem mem_Fam {ps : List V} (hnr : S.NoRecDep) (hb : S.DomsBounded M ls ps)
1482 (hco : S.ContOk M ls ps) {is : List V} {t : V} :
1483 t ∈ˢ S.Fam M ls ps is ↔ S.Inst M ls (S.Fam M ls ps) ps is t := by
1484 rw [← S.famOp_Fam M ls hnr hb hco]
1485 exact S.mem_famOp M ls
1486
from members of the field domains. This is the fixed-point equation of §4, read from left to rightwhitepaper/Fragment/LfpSet.lean:L137-L141137/-- **The fixed-point equation**, fibrewise. Con-leche:
138`lfpTuple_eq`. -/
139theorem lfpFamSet_eq (h : ∃ L, IsClosedFam n Φ L) (hmono : MonoFam n Φ) (hmaps : MapsFam n Φ)
140 (i : ι) : Φ (lfpFamSet n Φ) i = lfpFamSet n Φ i :=
141 Sub.antisymm (lfpFamSet_closed h hmono i) (lfpFamSet_fixed h hmono hmaps i)
. When the family is proposition-valued (its result sort has evaluating to at ), the family denotes a truth valuewhitepaper/Fragment/IndSem.lean:L592-L595592/-- A fibre of a family in the universe at a proposition is a truth
593value. -/
594theorem fibre_mem_univ_zero {W : List V → V} (hW : InUniv (S.u₀ ls) W) (hz : S.z ls = true) (is : List V) :
595 W is ∈ˢ (univ 0 : V) := by
instead. The constructor step’s tuple is not stored, only whether some such tuple exists, and a member of the family, like a constructor application, is the pointwhitepaper/Fragment/IndSem.lean:L1539-L15431539/-- At a proposition a member of the fibre is the point and some
1540decoding exists. -/
1541theorem mem_Fam_true {ps : List V} (hnr : S.NoRecDep) (hb : S.DomsBounded M ls ps)
1542 (hco : S.ContOk M ls ps) (hz : S.z ls = true) {is : List V} {t : V} :
1543 t ∈ˢ S.Fam M ls ps is ↔ t = pt ∧ ∃ x, S.Mem M ls ps is x := by
. Tagged tuples are injective (tupleswhitepaper/Fragment/IndLib.lean:L5353 tuple_inj : ∀ {xs ys : List V}, tuple xs = tuple ys → xs = ys
, tagswhitepaper/Fragment/IndLib.lean:L6060 tag_inj : ∀ {i j : Nat} {x y : V}, tag i x = tag j y → i = j ∧ x = y
) and a tagged tuple is whitepaper/Fragment/IndLib.lean:L6464 tag_ne_pt : ∀ {i : Nat} {x : V}, tag i x ≠ pt
.
6.1. Proof irrelevance and propositional extensionality
These two need no rule; they are built into the model, and §2 used the first already. A proposition denotes a truth valuewhitepaper/Fragment/Lib.lean:L142-L148142/-- The truth value of a proposition: `{pt}` if it holds, `∅` if
143not. -/
144def truthVal (P : Prop) : V := sep one (fun _ => P)
145
146/-- The members of a truth value. -/
147theorem mem_truthVal {p : Prop} {z : V} : z ∈ˢ truthVal p ↔ z = pt ∧ p := by
148 unfold truthVal; rw [mem_sep, mem_one]
, a subset of , so any two proofs of any two propositions denote the same set. That is the proof-irrel case of Theorem 2, which never compared the two propositionsConLeche/Model/Rules/DefEqSound.lean:L301-L303301theorem DefEq.proofIrrel_sound {d : Nat} {a ta b : Expr}
302 (hta : InferSemIO m φ d a ta) (hpt : PropPt m φ d ta) :
303 DefEqSem m φ d a b := by
. And two propositions that imply each other have the same truth valuewhitepaper/Fragment/Lib.lean:L173-L174173theorem truthVal_congr {p q : Prop} (h : p ↔ q) : (truthVal p : V) = truthVal q :=
174 ext fun z => by rw [mem_truthVal, mem_truthVal, h]
, by extensionality of sets. This is Lean’s axiom propext, which therefore holds in the modelConLeche/Model/AxiomMem.lean:L297-L304297/-- **`propext` inhabits its stored type's reading.** -/
298theorem propext_mem (mp : EnvModelM V μ env)
299 {cv : ConstantVal} (hok : ConLeche.stdAxiomOk env cv = true)
300 (hn : cv.name = propextName)
301 (ψ : Name → Nat) (ta : Term)
302 (hta : denote mp.base2.acval env ψ 0 cv.type = some ta)
303 (ρ : Nat → V) :
304 interp V ρ (.const .propext []) ∈ˢ interp V ρ ta := by
.
6.2. η for structures
The rule. A structure is a block with one constructor mk, no indices, no recursive field, and a type-valued family. The official kernel equates any of the structure type with the constructor applied to ’s projections: mk . The check is that ’s type reduces to the structure at the parameters , and compares each field of the constructor application with the corresponding projection of . The fragment has no projection terms (Section 7). Read below as the projection defined through the recursor, S.rec , whose value is the -th component of the tuple by the ι law, or as a primitive projection, whose interpretation reads the component directly. The argument is the same either way.
mk .The lemma proves nothing specific to the structure. The equation is what membership in the family means, read from left to right, plus the fact that a tuple determines its components. In the soundness case the checker’s field comparisons deliver for the fields the constructor was actually applied to, and the lemma does the rest. A block declared in Prop is never granted the rule. Where an instance of a Sort u structure happens to be a proposition, both sides are the point, and the law’s proposition case covers it.
ι through the projections. The same law on values, that a member is the constructor at its projections, gives a second way to fire on a block of a structure’s shape (one constructor, no indices, no recursive field). For such a block whose instances may be propositions, can go through the projections of the major instead of a constructor application:
This step is soundwhitepaper/Fragment/EtaIota.lean:L242-L260242/-- **ι through the projections, the semantic core**: given
243the plain ι law and the structure-η law, the redex
244`rec ps motive minor x` reduces to `minor (p₀ ps x) … (pₖ ps x)` with
245no certificate — no slot certificate on the major, no constructor
246telescope, no level, parameter or index comparison, and no reduction
247of the major. The redex is not the point, so its whole spine fits
248the recursor's telescope (`teleFit_of_ne_pt`); everything else is the
249laws'. -/
250theorem iotaEta_core_sem {Γ : List Expr} {c : Name} {us : List Level} {ci : ConstInfo}
251 {args : List Expr} {numParams numBefore d : Nat} {rl : RecRule} {cij : ConstInfo}
252 {projs : List Name}
253 (hfind : env.find? c = some ci)
254 (hus : us.length = ci.lparams.length)
255 (hlen : args.length = numBefore + 1)
256 (hpis : ci.type.hasPis (numBefore + 1) = true)
257 (hlaw : RecRuleLaw m.M c ci numParams numBefore numBefore rl cij)
258 (heta : StructEtaLaw m.M ci numParams numBefore d rl.ctor cij projs)
259 (hnP : numParams ≤ numBefore) (hnf : projs.length = rl.nfields)
260 (hd : d + cij.lparams.length = ci.lparams.length) :
with no certificate, no comparison and no reduction of the major, given the plain law and the structure-η law on values. The redex is not the point, so the whole spine, the major included, fits the recursor’s telescope (Section 4.2.7). Where the structure is a proposition the major is the point, and so are its projectionswhitepaper/Fragment/EtaIota.lean:L402-L410402/-- **The projection of the point is the point**, from the field's
403sort: if the projection's type, walked along the parameters, is
404`(x : D) → B` and `B` is a proposition at every member of `D` whenever
405`D` holds the point — the field's sort is at most the structure's, so
406it is `Prop` wherever the structure is (non-impredicativity) — then the
407projection at the point is the point. This is what fails for an
408impredicative structure (a data field of a `Prop` structure), which
409after the renovation has no projections. -/
410theorem projPt_of_fieldSort {M : Name → List Nat → V} {T : Expr} {ρ : Nat → V} {f : V}
when every field’s sort is Prop wherever the structure’s is; the reduct is then the minor at the point, which is the recursor’s value there. The step must expand the major as found, before any reduction of it, and also when it already is a constructor application. A major that denotes the point may reduce to a term that is not well-denoted, and the projections of that reduct break the reduction claim (the witnesswhitepaper/Fragment/EtaIota.lean:L707-L724707open EtaWit in
708/-- **Eta on the reduct of the major is outside the claims**.
709The major `maj := (fun x : A => g (k x)) a` is well-denoted
710and the point (a lambda that is the point constrains nothing, so `a` may
711lie outside `A`), and it β-reduces to `maj' := g (k a)`, which is not
712well-denoted (`k` is a graph over `A`). The redex `rec minor maj` is
713well-denoted and not the point. Eta-expanding the major's reduct,
714`minor (p0 maj')`, breaks the reduction claim; eta-expanding the major
715as it stands, `minor (p0 maj)`, satisfies it. So the checker's eta
716path must not reduce the major first (lean4#15371 expands before the
717major's `whnf`). -/
718theorem etaIota_reduced_major_witness [LevelOracle] (φ : Name → Nat) (env : Env)
719 (Γ : List Expr) :
720 Red env Γ maj maj' ∧
721 (∀ ρ : Nat → V, WellDenoted (witM (V := V)) φ ρ redex2 ∧ interp witM φ ρ redex2 ≠ pt ∧
722 interp witM φ ρ maj = pt) ∧
723 ¬ RedSem (EnvModel.empty (witM (V := V))) φ [] redex2 etaRed2 ∧
724 RedSem (EnvModel.empty (witM (V := V))) φ [] redex2 eta2 := by
). For a structure without fields the step is K’s step (the reducts coincidewhitepaper/Fragment/EtaIota.lean:L349-L356349/-- **A structure with no fields: the eta reduct is the K reduct**,
350syntactically — the minor at the recursor's parameters, motives and
351minors (`True`, `PUnit`). -/
352theorem iotaEta_noFields (rhs : Expr) (args : List Expr) (numBefore numParams : Nat)
353 (ls : List Level) (x : Expr) :
354 Expr.mkAppN rhs (args.take numBefore ++ projApps [] ls (args.take numParams) x)
355 = Expr.mkAppN rhs (args.take numBefore) := by
356 simp [projApps]
). The fragment has no projections, so this step is a theorem about the claims, and not a rule of its relations.
/-- **Structure η** (`structEtaCert` → `structEtaCertWith`,459
`Kernel/Core.lean`): `a` is a fully applied constructor of an460
η-capable stored structure, `b`'s io-inferred type head-normalises to461
that structure at agreeing levels, every field slot has a462
projection-table entry, the parameters agree pairwise, and every463
field is the corresponding projection of `b`.464
The type application is not certified: the type is a type, so its465
spine fits the former's telescope (task #342 L11b). -/466
| structEta {d : Nat} {a b tb wtb : Expr} {c : Name} {us : List Level}467
{cvc : ConstantVal} {cnP cnF : Nat} {T : Name} {us' : List Level}468
{cvT : ConstantVal} {caps : IndCaps} :469
Infer env .io d b tb → Red env d tb wtb →, with its soundness caseConLeche/Model/Rules/DefEqSound.lean:L675-L677675
theorem DefEq.structEta_sound (hin : RulesInputs V m φ) {d : Nat}676
{a b tb wtb : Expr} {c : Name} {us : List Level} {cvc : ConstantVal}677
{cnP cnF : Nat} {T : Name} {us' : List Level} {cvT : ConstantVal}. It uses the same law when it prepares the major premise of a recursor on a structure that is never a proposition at the instance. The major is taken as found, before any head normalisation, and the constructor is applied to its projections, with no further check (the ruleConLeche/Rules/Rel.lean:L275-L285275
/-- **The structure-η rescue** (`majorToCtor`'s η branch,276
`Kernel/Core.lean`; official `to_cnstr_when_structure`): at an277
η-flagged single-rule recursor whose major's type is a never-`Prop`278
instance of the structure, the constructor of the major's279
projections is fabricated and scope-guarded. No certificate: the280
major is the fabrication by the family's η law, and the fabrication281
is graded by the η constructor's telescope coherence (task #342282
L11b, `EtaCoherent`). The guards are the ones the η law is keyed283
on: the two names not reserved, every field slot a projection-table284
entry. -/285
| rescueEta {d : Nat} {major tm tmaj fab : Expr} {recName : Name}, its soundness caseConLeche/Model/Rules/IotaSound.lean:L721-L722721
theorem Red.rescueEta_sound (hin : RulesInputs V m φ) {d : Nat}722
{major tm tmaj fab : Expr} {recName : Name} {cv : ConstantVal}). Only if that does not give a constructor application is the major head-normalised, and the same expansion is tried once more on the result. Whether a type has the rule at all is decided once, when it is installed, from its shape (one constructor, no index, not a proposition, in a block where no constructor is recursive), and recorded with the typeConLeche/Kernel/Inductives/BlockInstall.lean:L67-L8067
def blockCapsAt (p : BlockShape) (mi : Nat) (isRec : Bool) : IndCaps :=68
match (p.members.getD mi default).ctors, (p.members.getD mi default).nIdx with69
| [c], nIdx =>70
{ eta := nIdx == 0 && !p.isProp && !isRec71
etaCtor := c.1.name72
etaParams := p.nP73
etaFields := c.274
unitlike := nIdx == 0 && c.2 == 0 && !isRec75
unitParams := p.nP76
ruleK := p.k == 1 && c.2 == 0 && p.isProp77
sortZ := Level.zeronessOf p.resSort78
all := p.memberNames79
nparams := p.nP80
ctors := [c.1.name] }; the law itself is established there too, not at the use. It is the η law of the structure’s projection table, the record of its fields’ types that the checker stores for every one-constructor, index-free type (Section 7, “Projections”) (a member is the constructor at the parameters and its own projectionsConLeche/Model/Inductives/FixKit.lean:L720-L721720
* **(C) the η law** (`fixEntryEtaCore`): a member is the constructor at721
the parameters and its own projections below the tag.). It is proved when the checker stores the tableConLeche/Model/Inductives/BlockStageTables.lean:L10-L1210
`stageBlockTables`: the loop `checkBlockTables` runs — a table at11
every member with ONE constructor and no index, nothing at any other12
member — with `stageFixTable` at each table (abstract in the former's, by the argument above on the tagged tuple.
6.3. Unit-likeness
The rule. A block with one constructor, no indices and no fields, such as PUnit or, at Prop, the proposition True, has one element up to definitional equality: the checker equates any two terms whose types reduce to it.
A set with at most one member takes one of two shapes in the model: the singleton of the empty tagged tuple for a type-valued family, and a truth value for a proposition-valued one. The lemma holds for both by the same argument.
| structUnit {d : Nat} {a ta wta b tb wtb : Expr} {T : Name}495
{us' : List Level} {cvT : ConstantVal} {caps : IndCaps} :496
Infer env .io d a ta → Red env d ta wta →497
wta.getAppFn = .const T us' →498
env.find? T = some (.indInfo cvT caps) →499
caps.unitlike = true →, with its soundness caseConLeche/Model/Rules/DefEqSound.lean:L313-L314313
theorem DefEq.structUnit_sound (hin : RulesInputs V m φ) {d : Nat}314
{a ta wta b tb wtb : Expr} {T : Name} {us' : List Level}. Unit-likeness is recorded at installation, like η (one constructor, no index, no field, in a block where no constructor is recursive), and the law is established there from the fixed point (the fibre is the one tagged empty tupleConLeche/Model/Inductives/FixKit.lean:L1782-L17831782
/-- **Unit-likeness at a fieldless one-constructor block's leaf**: the1783
fibre is the one tagged empty tuple (the point at a squash instance).). Neither
PUnit nor True is special to the checker: both are installed like any other block.7. What we left out
The fragment of §2–§4 is the real checker with everything removed that does not change the shape of the argument. This section lists what was removed, with one sentence on what the real proof does about it and a link to the code. Then it describes where the real proof’s architecture differs from the fragment’s on the features they share.
7.1. Dropped features
Projections — Lean’s primitive projection e.i, the i-th field of a structure value. For every inductive type with one constructor and no index the checker stores a projection tableConLeche/Kernel/Inductives/BlockTail.lean:L106-L109106/-- **The projection table at every STRUCTURE-LIKE member** (one
107constructor, no index): the member's table at the tagged tower's
108projection offset `1`; nothing at any other member. -/
109def checkBlockTables (p : BlockShape) :
, the fields’ types read off the constructor. The real checker reduces a projection of a constructor application to that fieldConLeche/Rules/Rel.lean:L169-L185169 /-- **The structural projection** `proj_i (ctor p⃗ x⃗) ↦ x_i`
170 (`whnfCoreBody`'s `.proj` clause, `Kernel/Core.lean`), driven by
171 the projection table, fired under the entry's guard, with the
172 constructor telescope's syntactic arity (`projCertAt`,
173 `teleArityOk`). -/
174 | proj {d : Nat} {sn : Name} {i : Nat} {e : Expr} {entry : ProjEntry}
175 {us : List Level} {cvC : ConstantVal} {nP nF : Nat} :
176 env.findProj? sn i = some entry →
177 e.getAppFn = .const entry.ctor us →
178 i < entry.numFields →
179 e.getAppArgs.length = entry.numParams + entry.numFields →
180 us.length = entry.levelParams.length →
181 entry.fireOk us = true →
182 env.find? entry.ctor = some (.ctorInfo cvC nP nF) →
183 (Expr.peelPis e.getAppArgs.length
184 (cvC.type.instantiateLevelParams cvC.levelParams us)).isSome = true →
185 Red env d (.proj sn i e) (e.getAppArgs.getD (entry.numParams + i) (.bvar 0))
and infers its type from the tableConLeche/Rules/Rel.lean:L627-L637627 | proj {g : Grade} {d : Nat} {sn : Name} {i : Nat} {pe tpe te : Expr}
628 {us : List Level} {entry : ProjEntry} :
629 Infer env g d pe tpe → Red env d tpe te →
630 te.getAppFn = .const sn us →
631 env.findProj? sn i = some entry →
632 te.getAppArgs.length = entry.numParams →
633 us.length = entry.levelParams.length →
634 (Level.isEquiv entry.structSort .zero = some true →
635 Level.isEquiv (Level.subst entry.levelParams us entry.fieldSort) .zero
636 = some true) →
637 Infer env g d (.proj sn i pe) (entry.typeAt us te.getAppArgs pe)
; in the model a structure value is the tagged tuple of Section 6.2 and a projection reads a component, which the install of the tableConLeche/Model/Inductives/BlockStageTables.lean:L10-L1210`stageBlockTables`: the loop `checkBlockTables` runs — a table at
11every member with ONE constructor and no index, nothing at any other
12member — with `stageFixTable` at each table (abstract in the former's
establishes (its typing and ι lawsConLeche/Model/Inductives/FixKit.lean:L714-L719714* **(A) the typing law** (`fixEntryTypingCore`): a member projects at
715 `i + 1` into the body's residual — the graph regime by the tower's
716 projection membership below the tag, the squash regime by the point;
717* **(B) the iota law** (`fixEntryIotaCore`/`fixEntryIotaCoreZero`): the
718 projection of a graded constructor application is the selected field
719 (`sumMkAV_fold`: the application folds to the tagged tuple);
).
Mutual inductive types — Several types declared together, each constructor free to mention any of them. The real checker checks such a block the same way as a single type, with a tuple of operators, one per type of the block, and each type’s family is a component of their simultaneous least fixed pointConLeche/SetTheory/Derive/LfpTuple.lean:L8-L12 8
9The carrier of a block of `k` inductive families `T₀ … T_{k-1}` is the
10simultaneous least pre-fixed point of its constructor-tower functor
11acting on **tuples of families** — a meta-level tuple `X : Nat → V`
12whose `m`-th component (`m < k`) is a family over member `m`'s own
. The argument is the one for a single type, with more indices, so this document skips it.
Nat and String literals, and the fast Nat path — Numerals and strings are terms of their own; the checker expands a literal to its constructor form when reduction needs it (natLitConLeche/Rules/Rel.lean:L144-L146144 | natLit {d : Nat} {n : Nat} :
145 natLitSupported env = true →
146 Red env d (.lit (.natVal n)) (natLitToConstructor n)
, strLitConLeche/Rules/Rel.lean:L151-L153151 | strLit {d : Nat} {s : String} :
152 strLitSupported env = true →
153 Red env d (.lit (.strVal s)) (strLitToConstructor s)
) and folds Nat.succConLeche/Rules/Rel.lean:L156-L159156 | natSucc {d : Nat} {a w : Expr} {n : Nat} :
157 natLitSupported env = true →
158 Red env d a w → rawNatLit? w = some n →
159 Red env d (.app (.const natSuccName []) a) (.lit (.natVal (n + 1)))
and the binary Nat operationsConLeche/Rules/Rel.lean:L163-L168163 | natOp {d : Nat} {c : Name} {a wa b wb r : Expr} {n₁ n₂ : Nat} :
164 c ∈ natBinOpNames → natOpStored env c = true →
165 Red env d a wa → rawNatLit? wa = some n₁ →
166 Red env d b wb → rawNatLit? wb = some n₂ →
167 natOpResult c n₁ n₂ = some r →
168 Red env d (.app (.app (.const c []) a) b) r
on literals with machine arithmetic. The folding is sound only for the operations as the toolchain defines them, so the checker compares each stream’s definition against a pinned copyConLeche/Kernel/NatOpPins.lean:L8-L21 8`#load_natop_pins` below splices, per **committed dump** listed —
9`pins/<toolchain>.json`, the repository's top-level `pins/` directory,
10see `pins/README.md` — one **pin variant** `natOpPinSet_v<i> :
11NatOpPinSet` (`ConLeche/Kernel/NatOpPinSet.lean`): per pin-certified
12operation (`Nat.div`, `Nat.mod`, …) that toolchain's pinned defining
13expression and certificate proof blobs, as constants
14`nat…DeclPin_v<i> : Expr` / `nat…CertProofs_v<i> : List Expr`. The
15variants are listed in `natOpPinSets` in the order below. That list is
16what the shipped binary hands the fold as its PIN-LIST PARAMETER (task
17#304), and the order is the order the install gate tries them in
18(`checkDivModPinLoop`, `ConLeche/Kernel/Checker.lean`): the first
19whose guards pass, whose pin is definitionally equal to the stream's
20stored value and whose certificates check enables the operation's fast
21path. The hand-pinned certificate *statements* the proofs are checked against
and the model establishes the operations’ recurrencesConLeche/Model/NatEqs.lean:L1101-L11051101theorem natOps_install (mp : EnvModelM V μ env) {φ : Name → Nat}
1102 {F : Nat} (hde : DefEqClaim μ mp.base2 φ F)
1103 (hprev : NatOps mp.base2 φ)
1104 {c : Name} {lps : List Name} {type' value' : Expr}
1105 {hint : ReducibilityHint}
from that comparison.
Quotients — Quot, Quot.mk, Quot.lift, Quot.ind and the axiom Quot.sound are installed from a built-in pinConLeche/Kernel/Basis/Quot.lean:L8-L14 8# The pinned `Quot` basis block
9
10Lean's kernel quotient bundle, pinned as an installed basis block:
11`Quot` as a stored inductive with the single constructor `Quot.mk`,
12the eliminators `Quot.lift`/`Quot.ind` as stored recursors with one
13synthetic rule each (applying the lift/ind slot to the packed
14element), and `Quot.sound` as a stored axiom (true in the set model).
and modelled by the set-theoretic quotient of a set by a relationConLeche/SetTheory/Derive/Quot.lean:L65-L6765/-- The quotient (see module docs). -/
66noncomputable def quotSet (u : Nat) (A R : V) : V :=
67 if u = 0 then image (fun _ => pt) A else image (fun a => kpair empty (qclass A R a)) A
, for which the pinned constants are shown to be members of their typesConLeche/Model/BasisQuot.lean:L2435-L24392435theorem extendQuotLift (mp : EnvModelM V μ env)
2436 (hQ : env.find? quotName = some quotA)
2437 (hM : env.find? quotMkName = some quotMkA)
2438 (hE : env.find? eqName = some eqA)
2439 (hfresh : env.find? quotLiftA.name = none)
.
Pinned blocks — In the fragment False is whatever empty proposition the stream declares. The real checker installs Eq, Nat, Empty, False and the quotient Quot from hand-written copies of the toolchain’s declarations (False, for instanceConLeche/Kernel/Basis/False.lean:L37-L3937/-- `False : Prop`. -/
38def falseRaw : ConstantInfo :=
39 .indInfo ⟨falseName, [], prop⟩ { all := [falseName] }
, the builderConLeche/Kernel/Basis/Builder.lean:L9-L18 9# A tiny builder for the hand-written raw pins
10
11The pinned basis blocks, the standard-axiom prerequisite families and
12the compiler-trust pins are all *raw* `ConstantInfo`s: exactly what an
13export carries for them, which is exactly the toolchain's own
14`Init.Prelude` declaration — binder names and infos
15are not part of an `Expr` (task #205; `piI`/`lmI` mark where the
16signature says `{…}`, for the reader) — with every install-computed
17recursor-rule field at its parse placeholder (`ctorParams := 0`,
18`fire := .plain`).
): a stream block that carries a pin’s names and agrees with it up to renaming of universe parameters installs the pinConLeche/Kernel/Basis.lean:L64-L6764def basisPinHit (block : List ConstantInfo) : Option BasisKind :=
65 ([BasisKind.eqK, .natK, .emptyK, .falseK].find? fun k =>
66 k.decls.map (·.name) == block.map (·.name)).filter fun k =>
67 canonEqList block k.decls
, and one that disagrees is rejected. The pins are how the main theorem can name False and Eq and say what they denote.
Axioms — The fragment has none. The real checker accepts exactly Lean’s three standard axioms: propext and Classical.choice, each pinned to the toolchain’s statementConLeche/Kernel/StdAxioms.lean:L8-L15 8# Recognized standard axioms and their prerequisite shapes
9
10A stream may use the standard axioms `propext` and `Classical.choice`.
11Both are true in the set-theoretic model — `propext` by extensionality
12of propositions (derived through the stored `Iff` recursor),
13`Classical.choice` by global choice (through the stored `Nonempty`
14recursor) — so the checker accepts exactly these two axioms, after
15pinning their types and the *shapes of the inductives they quantify
and each true in the modelConLeche/Model/AxiomMem.lean:L635-L641635theorem choice_mem (mp : EnvModelM V μ env)
636 {cv : ConstantVal} (hok : ConLeche.stdAxiomOk env cv = true)
637 (hn : cv.name = choiceName)
638 (ψ : Name → Nat) (ta : Term)
639 (hta : denote mp.base2.acval env ψ 0 cv.type = some ta)
640 (ρ : Nat → V) :
641 interp V ρ (.const .choice [ψ uN]) ∈ˢ interp V ρ ta := by
(propext by the extensionality of truth values, choice by choice in the meta-logic), and Quot.sound as part of the pinned Quot block above. Any other axiom record declines the stream, with two tolerated exceptions: a declared but unused sorryAx installs nothing, and Lean’s deprecated compiler-trust axioms are accepted as pinned definitions of their own types.
Theorems and opaques — The fragment has definitions only. The real checker never unfolds a theorem or an opaque: only a definition unfoldsConLeche/Rules/Rel.lean:L138-L141138 `lazyDeltaStep`): one definition unfolded
139 at the head. A theorem never unfolds (`unfoldDefinition`). -/
140 | delta {d : Nat} {e e' : Expr} :
141 unfoldDefinition env e = some e' → Red env d e e'
.
Structure η and unit-likeness — The checker has structure ηConLeche/Rules/Rel.lean:L466-L469466 | structEta {d : Nat} {a b tb wtb : Expr} {c : Name} {us : List Level}
467 {cvc : ConstantVal} {cnP cnF : Nat} {T : Name} {us' : List Level}
468 {cvT : ConstantVal} {caps : IndCaps} :
469 Infer env .io d b tb → Red env d tb wtb →
and unit-likenessConLeche/Rules/Rel.lean:L494-L499494 | structUnit {d : Nat} {a ta wta b tb wtb : Expr} {T : Name}
495 {us' : List Level} {cvT : ConstantVal} {caps : IndCaps} :
496 Infer env .io d a ta → Red env d ta wta →
497 wta.getAppFn = .const T us' →
498 env.find? T = some (.indInfo cvT caps) →
499 caps.unitlike = true →
as Lean has them; Section 6.2 and Section 6.3 derive both from extensionality.
Recursors on stuck proofs of A ∧ B — The checker never unfolds a theorem, while the official kernel does. Some elaborated terms rely on that, with a case split on a stuck proof h of A ∧ B. There the recursor fires on And.intro h.1 h.2ConLeche/Rules/Rel.lean:L307-L332307 /-- **The `And` rescue** (`majorToCtor`'s `And` branch,
308 `Kernel/Core.lean`; `And` only, by ruling): `And.intro a b
309 (.proj And 0 h) (.proj And 1 h)` is fabricated for a stuck proof
310 `h`, certified the K branch's way; the last four premises are the
311 major's half of the `proofIrrel` run, as in the K branch. -/
312 | rescueAnd {d : Nat} {major tm tmaj fab tf tp ttp : Expr} {v : Level} {recName : Name}
313 {cv : ConstantVal} {mI rP : Nat} {rl : RecRule} {cvj : ConstantVal}
314 {cnP cnF : Nat} {tus ust : List Level} {cvT : ConstantVal}
315 {caps : IndCaps} :
316 env.find? recName = some (.recInfo cv mI rP [rl]) →
317 env.find? rl.ctor = some (.ctorInfo cvj cnP cnF) →
318 (cvj.type.piResult).getAppFn = .const andName tus →
319 env.find? andName = some (.indInfo cvT caps) →
320 Infer env .io d major tm → Red env d tm tmaj →
321 tmaj.getAppFn = .const andName ust →
322 tmaj.getAppArgs.length = cnP →
323 cvj.levelParams.length = ust.length →
324 andRescueSlots env rl.ctor cnP ust = true →
325 fab = Expr.mkAppN (.const rl.ctor ust)
326 (tmaj.getAppArgs ++ [.proj andName 0 major, .proj andName 1 major]) →
327 fab.wscopedB d = true → fab.looseBVarsBounded 0 = true →
328 fab.fvarLeaves.all (fun l => major.fvarLeaves.contains l) = true →
329 Infer env .io d fab tf → DefEq env d tmaj tf →
330 Infer env .io d major tp → Infer env .io d tp ttp →
331 Red env d ttp (.sort v) → Level.isEquiv v .zero = some true →
332 Red env d major fab
, the constructor applied to h’s two projections, built from h’s type.
Fuel, memo tables, the executable checker and its bridge theorem — In the fragment the relations are the checker. The real checker is a program, connected to the relations by further theorems (Section 7.2).
The two-phase parallel fold — The foldConLeche/Cached/Installed.lean:L443-L448443def checkDecls (mode : CheckMode) (pins : List NatOpPinSet)
444 (ds : Array Declaration) :
445 Except (CheckError × Nat) Env := do
446 let p ← ds.foldlM (installDeclStep mode pins) (0, mkFEnv Env.empty, #[])
447 checkPendingList mode p.2.1 p.2.2.toList
448 pure p.2.1.env
installs every declaration first and then checks the recorded declarations one by one, each against the prefix of the environment it was installed at; the binary runs the second phase on a pool of workersConLeche/Cached/Installed.lean:L343-L354343The driver's check phase — the in-thread loop or a pool of workers —
344runs `checkRecord` on every record. Its result is the record's own
345evidence: on an accept the `GroupChecked` fact of that record, on a
346failure the error tagged with the record's fold position, the tag
347`checkPendingList` gives. A worker hands back exactly this (a `Nat`
348and an erased proof, or the error), so the thread that computed a
349check is irrelevant to what it proves, and the results of any number
350of workers, in whatever order they finished, are assembled into
351`∀ i, GroupChecked mode e i` by `collectChecks` — a walk over the
352results in record order, which is also what makes the verdict of a
353pool the verdict of the walk `checkPendingList`: the first failing
354record in fold order. Nothing here is `IO`. -/
whose results are reassembled in record order, so its verdict is the fold’s. The proof reads an accept of the foldConLeche/Verify/Cached/MainC.lean:L37-L4037theorem checkDecls_sound (hμ : μ.isVerified = true)
38 {ds : Array Declaration} {env' : Env}
39 (h : checkDecls μ pins ds = .ok env') :
40 Nonempty (EnvModelM V μ env') := by
as an environment in which every declaration was checked where it was installed, which is what the one-declaration-at-a-time argument of §3 and §4 needs.
7.2. How the real proof differs from the fragment
Free variables carry their types. The paper uses named variables and a context. The real checker keeps no context at all. When it opens a binder it substitutes a free variable that carries its own type, fvar idx typeConLeche/Kernel/Expr.lean:L315-L317315inductive Expr where
316 | bvar (i : Nat)
317 | fvar (idx : Nat) (type : Expr)
, identified by the depth at which it was opened; so a variable is never looked up, the relations are indexed by the opening depth in place of a context, and two free variables are compared by depth aloneConLeche/Rules/Rel.lean:L380-L381380 | fvar {d i : Nat} {ty₁ ty₂ : Expr} :
381 DefEq env d (.fvar i ty₁) (.fvar i ty₂)
. Stored types are closed terms, and Denotes, the relation the main theorem is stated over (below), reads them with de Bruijn indices under binders, with no opening at all.
An intermediate term language. The real proof does not interpret the checker’s terms directly. It first reads a term into a smaller language, TermConLeche/Term/Syntax.lean:L187-L218187inductive Term where
188 /-- de Bruijn index -/
189 | bvar (i : Nat)
190 /-- `Sort u` at a concrete level -/
191 | sort (u : Nat)
192 /-- a built-in constant at a concrete level instantiation -/
193 | const (c : BConst) (us : List Nat)
194 /-- application -/
195 | app (f a : Term)
196 /-- `fun (_ : ty) => body` -/
197 | lam (ty body : Term)
198 /-- `(_ : ty) → body` -/
199 | pi (ty body : Term)
200 /-- `let _ := val; body`, **without the type**: the body is under one
201 binder, read at the value -/
202 | letE (val body : Term)
203 /-- `@Eq _ lhs rhs`, **without the type**: the interpretation is
204 `⟦eqE a b⟧ = eqv ⟦a⟧ ⟦b⟧`, so soundness never constrains the type,
205 and no definition and no theorem in any tier reads the type, so
206 the constructor does not carry it; a producer that knows the type
207 simply drops it. -/
208 | eqE (lhs rhs : Term)
209 /-- First field of a pair. Carries **only** the subject: the pair's
210 type arguments come from the typing premise `Γ ⊢ p : Σ A B`,
211 not from the term — see the module docstring. Interpreted by
212 `sfst`, i.e. literally `interp`'s clause. -/
213 | fst (e : Term)
214 /-- Second field of a pair; the `fst` twin, interpreted by `ssnd`. -/
215 | snd (e : Term)
216 /-- the canonical (irrelevant) proof of a derivable equation -/
217 | prf
218 deriving Repr, Inhabited
: de Bruijn indices, sorts at concrete numbers (the level valuation already applied), and built-in constants at concrete levels. That reading, denoteConLeche/Verify/Denote.lean:L152-L153152@[expose] def denote (cval : Name → (Name → Nat) → Term) (env : Env)
153 (φ : Name → Nat) : (d : Nat) → Expr → Option Term
, is partial only for syntactic reasons: an unknown constant, a wrong number of levels, or a projection with no table. The interpretation interpConLeche/Semantics/Interp.lean:L134-L145134noncomputable def interp : (Nat → V) → Term → V
135 | ρ, .bvar i => ρ i
136 | _, .sort u => univ u
137 | _, .const c us => bval V c us
138 | ρ, .app f a => SetTheory.app (interp ρ f) (interp ρ a)
139 | ρ, .lam A b => lamV (interp ρ A) fun x => interp (cons x ρ) b
140 | ρ, .pi A B => piV (interp ρ A) fun x => interp (cons x ρ) B
141 | ρ, .letE v b => interp (cons (interp ρ v) ρ) b
142 | ρ, .eqE a b => eqv (interp ρ a) (interp ρ b)
143 | ρ, .fst e => sfst (interp ρ e)
144 | ρ, .snd e => ssnd (interp ρ e)
145 | _, .prf => pt
then maps the term to a set, reading each binder off the values as Section 2.5 does. The paper folds the two steps into one denotation on the terms themselves.
Six relations, and a mode index. Beside reduction, definitional equality and inference the real description has three more: the certification of an argument spine against a binder telescopeConLeche/Rules/Rel.lean:L639-L648639/-- **The full-grade telescope certificate** (`iotaCertsFull`,
640`Kernel/Core.lean`; ι's case 3, task #342 L10): each argument's
641FULL-grade type is definitionally equal to its binder domain, the
642domains instantiated along the spine; no slot is skipped. -/
643inductive CertsF (env : Env) : Nat → Expr → List Expr → Prop where
644 | nil {d : Nat} {T : Expr} : CertsF env d T []
645 | cert {d : Nat} {ty body arg ta : Expr} {rest : List Expr} :
646 Infer env .full d arg ta → DefEq env d ta ty →
647 CertsF env d (body.instantiate1 arg) rest →
648 CertsF env d (.forallE ty body) (arg :: rest)
(the certified case of ), pairwise definitional equalityConLeche/Rules/Rel.lean:L650-L655650/-- **Pairwise definitional equality** (`defEqList`, `Kernel/Core.lean`). -/
651inductive DefEqList (env : Env) : Nat → List Expr → List Expr → Prop where
652 | nil {d : Nat} : DefEqList env d [] []
653 | cons {d : Nat} {a b : Expr} {as bs : List Expr} :
654 DefEq env d a b → DefEqList env d as bs →
655 DefEqList env d (a :: as) (b :: bs)
, and the congruence of two lambda telescopesConLeche/Rules/Rel.lean:L507-L546507/-- **λ-telescope congruence, typed at the innermost level only**
508(task #349; the fragment's `DefEqLams`). Two λ-telescopes are compared
509level by level: the domains at every level, the opened bodies' FULL-grade
510types only where the comparison stops descending through two λs — where
511the checker's `Expr.lamPair?` fails (`defeqLams`, `Kernel/Core.lean`).
512
513*Why the outer levels need no type comparison.* The type comparison is
514what makes λ-congruence's claim **unconditional** — `DefEq`'s claim says
515nothing where a side is the point, and two bodies in one type are both
516the point or neither — and an unconditional equality of the bodies at
517every value of the variable is an equality of the λs, with no type at
518all; so the claim propagates outward through any number of levels.
519
520*Unequal depth, and reduction between levels.* The descent continues
521only through two **syntactic** λs (`outer`'s premise is this relation
522itself, which has no reduction rule). Where one body is not a λ — the
523comparison ends in η or any other rule, possibly after δ or whnf steps —
524that level is the innermost one (`inner`), and its bodies' types are
525inferred and compared at the full grade. Syntactically equal λs
526(`refl`) are equal outright. -/
527inductive DefEqLams (env : Env) : Nat → Expr → Expr → Prop where
528 /-- Syntactically equal sides (the checker's `a == b` fast path). -/
529 | refl {d : Nat} {a : Expr} : DefEqLams env d a a
530 /-- The innermost level: domains, bodies opened with the RIGHT
531 domain, and the opened bodies' full-grade types. -/
532 | inner {d : Nat} {ty₁ body₁ ty₂ body₂ t₁ t₂ : Expr} :
533 DefEq env d ty₁ ty₂ →
534 DefEq env (d + 1) (body₁.instantiate1 (.fvar d ty₂))
535 (body₂.instantiate1 (.fvar d ty₂)) →
536 Infer env .full (d + 1) (body₁.instantiate1 (.fvar d ty₂)) t₁ →
537 Infer env .full (d + 1) (body₂.instantiate1 (.fvar d ty₂)) t₂ →
538 DefEq env (d + 1) t₁ t₂ →
539 DefEqLams env d (.lam ty₁ body₁) (.lam ty₂ body₂)
540 /-- An outer level: the domains, and the opened bodies by this
541 relation — no inference, no type comparison. -/
542 | outer {d : Nat} {ty₁ body₁ ty₂ body₂ : Expr} :
543 DefEq env d ty₁ ty₂ →
544 DefEqLams env (d + 1) (body₁.instantiate1 (.fvar d ty₂))
545 (body₂.instantiate1 (.fvar d ty₂)) →
546 DefEqLams env d (.lam ty₁ body₁) (.lam ty₂ body₂)
(Section 2.8). A rule with a premise about a list or a telescope needs a relation of its own in a mutual block; the paper inlines them into the rules that use them. Inference also carries a mode index, for infer_only (Section 2.9), which the fragment states as a relation of its own.
The bridge and the recomposition. Because the real checker is a program, its proof has three parts where the paper has one. The bridgeConLeche/Verify/Rules/Bridge.lean:L30-L3330theorem bridgeAt (env : Env) :
31 ∀ (fuel : Nat) (μ : CheckMode), μ.verifiedChecks = true →
32 WhnfCoreBridge μ env fuel ∧ WhnfBridge μ env fuel ∧ DefEqBridge μ env fuel ∧
33 InferBridge μ env fuel ∧ InferIOBridge μ env fuel := by
turns an accepting run at any fuel into a derivation, by one induction on fuel in which each checker site lands on one rule. The soundnessConLeche/Model/Rules/Sound.lean:L49-L5049theorem red_sound (hin : RulesInputs V m φ) :
50 ∀ {d : Nat} {e e' : Expr}, Red env d e e' → RedSem m φ d e e'
is the paper’s induction: one structural induction over the six relations, each case a lemma about one rule, never mentioning the implementation. The recompositionConLeche/Model/Rules/Recompose.lean:L50-L5550theorem checkSoundAtP5 (hμ : μ.isVerified = true)
51 {m : EnvModel V env} (h : RulesInputs V m φ) :
52 ∀ fuel : Nat,
53 WhnfCoreClaim μ m φ fuel ∧ WhnfClaim μ m φ fuel ∧
54 DefEqClaim μ m φ fuel ∧ InferClaim μ m φ fuel ∧
55 InferClaimIO μ m φ fuel := by
puts them together, claim by claim. It bridges the run, applies the soundness, and hands the result to the declaration fold.
The statement over Denotes. The main theorem is stated over DenotesConLeche/Denotes.lean:L124-L127124/-- `Denotes cval env φ ρ e v`: the term `e` denotes the set `v`. See
125the module docstring. -/
126inductive Denotes (cval : Name → (LevelParam → Nat) → V) (env : Env) (φ : LevelParam → Nat) :
127 (BVarIdx → V) → Expr → V → Prop
, a relation on the checker’s own terms with one rule per syntax form, written to be read in one sitting, and over the structure ModelConLeche/Denotes.lean:L269-L289269structure Model (V : Type w) [SetTheory V] (env : Env) where
270 /-- the set a constant denotes, per level assignment -/
271 cval : Name → (LevelParam → Nat) → V
272 /-- every stored constant is a member of what its type denotes, at
273 every level assignment (and every variable environment — the type
274 is closed) -/
275 mem : ∀ c ∈ env.consts, ∀ (φ : LevelParam → Nat) (ρ : BVarIdx → V),
276 ∃ T, Denotes cval env φ ρ c.toConstantVal.type T ∧ cval c.name φ ∈ˢ T
277 /-- whatever the built-in `False` denotes is the empty set -/
278 false_empty : ∀ (φ : LevelParam → Nat) (ρ : BVarIdx → V) (F : V),
279 Denotes cval env φ ρ (.const falseName []) F → F = empty
280 /-- whatever the built-in `Eq` denotes is set equality: at a type `A`
281 of the universe the level `u` names, and two of its members `a` and
282 `b`, `@Eq.{u} A a b` denotes the truth value of `a = b`. The three
283 membership premises are the graph's domains — `Eq`'s denotation is a
284 three-fold graph over `univ (Level.eval φ u)`, then over `A`, then
285 over `A` again, and a graph read off its domain says nothing -/
286 eq_equality : ∀ (u : Level) (φ : LevelParam → Nat) (ρ : BVarIdx → V) (E A a b : V),
287 Denotes cval env φ ρ (.const eqName [u]) E →
288 A ∈ˢ univ (Level.eval φ u) → a ∈ˢ A → b ∈ˢ A →
289 app (app (app E A) a) b = eqv a b
built on it. A theoremConLeche/Model/Denotes.lean:L192-L198192theorem Denotes_of_denote {acval : Name → (Name → Nat) → Term}
193 (hcl : ∀ n ψ, Term.Closed (acval n ψ)) {env : Env} {φ : Name → Nat} :
194 ∀ (d : Nat) (e : Expr) {ta : Term},
195 denote acval env φ d e = some ta →
196 Expr.fvarsBelow d e → e.looseBVarsBounded 0 = true →
197 ∀ ρ : Nat → V,
198 Denotes (cvalOf (V := V) acval) env φ ρ (closeN d e) (interp V ρ ta) := by
connects the two: wherever the reading denote exists, its interpretation is a Denotes-denotation of the term. The paper’s theorem is the model side directly.
The cached checker and its simulation. The core the binary runs is a clone of the pure oneConLeche/Cached/CoreC.lean:L8-L11 8# The cached checker core
9
10The core over the computed-field representation: `whnfCore`, `whnf`,
11`infer`, `inferIO` and `defeq`, each memoized in `CState`; a
over a term representation that carries a precomputed hash in every node, with memo tables for reduction, equality and inference. A simulation proofConLeche/Verify/Cached/SimC.lean:L10-L1510# The cached-core faithfulness kit (task #163, batch 5)
11
12The relation and combinators for proving that the cached clone core
13(`ConLeche/Cached/CoreC.lean`) simulates the pure fueled families. The
14cached and pure sides are *the same terms*, so the value relation is
15equality.
shows that every successful run of the cached core is a successful run of the pure fuelled core at some fuel, with the memo tables’ invariant preserved; the main theorem composes it with the fold’s soundnessConLeche/Verify/Cached/MainC.lean:L37-L4037theorem checkDecls_sound (hμ : μ.isVerified = true)
38 {ds : Array Declaration} {env' : Env}
39 (h : checkDecls μ pins ds = .ok env') :
40 Nonempty (EnvModelM V μ env') := by
.
The frontend. Between the export file and the declaration fold sit a decoder and one transformation of the decoded list, preparePreludeConLeche/Frontend/Prepare.lean:L171-L172171def preparePrelude (pre : PreludeIx) (ds : Array Declaration) : Array Declaration :=
172 (prepareD pre ds).decls
. It moves the declarations of a built-in copy of the toolchain’s prelude (Eq, Nat, Bool and their kin) to the front, the stream’s own record where it has one and the copy’s where it has none, and it moves the structural Nat operations that a pinned Nat operation is defined from ahead of it. A theorem says that this is allConLeche/Verify/Frontend/Prepare.lean:L160-L161160theorem preparePrelude_perm (pre : PreludeIx) (ds : Array Declaration) :
161 ∃ extra : List Declaration, (∀ d ∈ extra, d ∈ pre.decls.toList) ∧
. The fold’s input is a permutation of the decoded list plus records of the built-in prelude, and nothing is rewritten or generated. What no theorem says is that the decoded list means the same as the export file: the decoder is written to be meaning-preserving, and that is a review claim. The fragment has no frontend: its stream is its environment.
8. Related work
This section compares our proof with a small selection of the most closely related work.
Carneiro, “The Type Theory of Lean”. The thesis [1] presents Lean’s type theory in full by a typing judgement, studies its metatheory, and builds a set-theoretic model, from which Lean’s consistency follows relative to ZFC together with, for each , the existence of inaccessible cardinals. It is the starting point of our model, whose interpretation follows the thesis’s. A proof is one point, a proposition is the intersection of its fibres with the singleton of that point (our propositional function space , Section 2.5), and a into a higher universe is the full dependent function space. To decide which reading applies, the thesis first translates every well-typed term into a language in which the propositional and the non-propositional and lambda are separate constructs, using the sort of each body, which is well defined given the thesis’s unique typing theorem. Our interpretation reads the choice off the values instead (Section 2.5): a function space is a proposition when all its fibres are truth values. So it consults no typing information, and our proof does not need unique typing. The thesis models a fixed basis of eight inductive types, the W-type among them, to which it reduces the others (parts of the reduction left as future work); here every inductive block the checker accepts is interpreted by its own least fixed point (Section 4.2). The statements also differ in form. There, each derivation of a formal typing judgement is sound in the model once a number of inaccessible cardinals depending on the derivation is assumed; here the theorem is about the verdicts of the checker’s own rules, and assumes the whole chain of universes at once (Section 2.4).
Lean4Lean. Lean4Lean [2] is a checker for Lean 4 written in Lean, closely following the official kernel and able to check all of Mathlib. The paper also formalises a typing judgement, a slight variation of the thesis’s, and proves the main body of the checker correct against it: an equality test that answers yes yields a definitional equality of the judgement, an inferred type a typing; inductive types and additions to the environment are listed as unfinished. The paper notes that this correctness proof needs, in some form, unique typing and the inversion of definitional equality on sorts and -types for its judgement. Our proof relates the checker’s verdicts to the model directly, with no typing judgement in between, and uses no such property (Section 2.7). The Lean development lean4lean-model [3], built on Lean4Lean’s judgement, states as its target theorem the consistency of that judgement assuming inaccessible cardinals, a proof still to be supplied at the time of writing; the set theory con-leche assumes follows from that hypothesisbridge/lean4lean-model/ConLecheBridge/Carneiro.lean:L200-L202200theorem carneiro_implies_conleche :
201 OmegaInaccessibles.{u} → Nonempty (Σ V : Type (u + 1), ConLeche.SetTheory V) :=
202 fun h => ⟨⟨ZFSet.{u}, setTheoryOfCarneiro h⟩⟩
.
Werner, “Sets in types, types in sets”. Werner [4] interprets the Calculus of Inductive Constructions in ZFC with an increasing sequence of inaccessible cardinals, one for each universe, and conversely encodes ZFC in the type theory, so that the two hierarchies interleave in strength. The interpretation is proof irrelevant: a proposition denotes or , a proof denotes , a into propositions denotes exactly when every fibre does, and a function type of the predicative levels denotes the full set of set-theoretic functions. These are the two readings of a in Section 2.5. It is defined on a term in its context, by structural induction on the term, and partially: whether the clause for proofs or for propositions applies to a term is a matter of its type. It is shown defined and sound on every derivable judgement by induction on the derivation, and the case of the conversion rule uses subject reduction and confluence of reduction. Our interpretation is total and term-directed, reads the choice off the values, and Theorem 2 is an induction over the checker’s three relations that uses neither. Werner interprets an inductive type of a universe as the least fixed point, in that universe, of the monotone operator that strict positivity provides, and his calculus has no inductive propositions; here proposition-valued families are least fixed points too, in (Section 4.2).
Barras, “Sets in Coq, Coq in Sets”. Barras [5] axiomatises intuitionistic ZF, possibly with inaccessible cardinals, inside Coq, develops functions, ordinals and fixpoint theory over the axioms, and proves set-theoretic models of the Calculus of Constructions sound, as well as its extension with a hierarchy of universes (interpreted by Grothendieck universes) and an extension with the natural numbers. The set theory is given by axioms, as ours is (Section 2.4). Π-types are interpreted by one clause: with Aczel’s encoding of a function as the set of pairs with in its value at , a function into propositions collapses to a proposition, so the interpretation, like ours, consults no typing information. In our set theory a graph is never the point (Section 2.4), so that a graph determines its domain; the two readings of a or a lambda are then different sets, and the values choose between them (Section 2.5). Soundness there is stated for semantic judgements and over valuations, close to our three claims, and the typing derivations of the calculus, in a presentation with judgemental equality, are shown to imply them; our claims are proved of the checker’s verdicts instead. Barras observes that set-theoretic holds only for arguments in the function’s domain, so his semantic rule has a typing premise; in our proof has no premise, because the reduction claim is asked only of a redex that is not the point, whose function is then a graph that determines its domain (Lemma 1).
Dybjer, “Inductive families”. Dybjer [6] gives a general scheme for inductive definitions of families of sets in Martin-Löf’s type theory: formal criteria for the formation and introduction rules of a new set former (parameters, indices, non-recursive premises and recursive premises), and an inversion principle that derives the elimination and equality rules from them. A recursive premise may be a function over a sequence of types that does not mention the set former being defined (strictly positive, generalised induction). Our blocks follow this scheme (Section 4.1): ordinary fields are his non-recursive premises, reflexive fields his recursive premises with the field’s telescope as that sequence, and recursive fields the case of an empty one. His order, non-recursive premises before recursive ones, appears here as the condition that nothing after a reflexive field depends on its value. The recursor’s type and its laws are generated from the constructors as his elimination and equality rules are. In our proof they are statements about the model, where the recursor’s set is constructed and the law proved (Theorem 10). Proposition-valued families, and the subsingleton criterion for their large elimination, belong to the impredicative setting, which his scheme, for a predicative theory, does not have.
Abbott, Altenkirch and Ghani, “Containers”. Abbott, Altenkirch and Ghani [7] represent strictly positive types as containers: a set of shapes and, for each shape, a set of positions, an element being a shape with an element of the argument at each of its positions. They show that nested strictly positive inductive and coinductive types exist in any Martin-Löf category (locally cartesian closed, with disjoint coproducts and W-types) by representing them as containers and constructing them from W-types. In these terms an element’s support (Section 4.2) is the set of its positions with the occurrences that fill them, and the bound is one set that holds the positions of every shape. Our proof represents no operator as a container: the support and the bound are read off the block’s fields, and a nested occurrence is handled by checking the container’s own constructors at the given instantiation (Section 5.2) rather than by composing containers. Coinductive types are outside our proof, and indexed families outside the paper’s containers.
Traytel, Popescu and Blanchette, “Foundational, compositional (co)datatypes for higher-order logic”. Traytel, Popescu and Blanchette [8] build datatypes and codatatypes in Isabelle/HOL, with mutual and nested recursion and corecursion, from bounded natural functors: type constructors equipped with a map function, set functions returning the elements an argument holds, and an infinite cardinal bounding those sets, subject to conditions including naturality and the bound. The class of these functors is closed under composition, initial algebras and final coalgebras, so a datatype nested through list uses the fact, proved once, that list is a bounded natural functor; it includes non-free type constructors such as finite sets. The bound is what lets the initial algebra be constructed inside HOL. Our proof uses no map function and records nothing per type former: the operator’s accessibility, a support bound in the same role, is read off the checker’s positivity check for each block, and a container’s positivity in its parameter is decided at each instantiation by walking its constructors (Section 5.2), never stored. Coverage differs as well: their framework has codatatypes and nesting through non-free type constructors; ours has indexed, universe-polymorphic families and proposition-valued ones, and no codatatypes.
Aczel, “An introduction to inductive definitions”. Aczel’s chapter [9] treats inductive definitions by rule sets and by monotone operators on the subsets of a set: the set inductively defined is the least set closed under the rules, which is the least fixed point of the operator and is reached by iterating it. It calls an operator -based when every element it produces from a set it already produces from a subset of of cardinality less than (as when every rule has fewer than premises), and shows that for regular such an operator’s iteration closes by stage . Accessibility (Section 4.2) is this idea of rules with a bound on their premises, the bound being a set indexing the support rather than a cardinal. What is specific to our proof is where the bound comes from. It is read off the checker’s positivity check for each block, with one occurrence per reflexive field and tuple of its telescope, and, for a nested block, at each instantiation of a container (Section 5.2).
References
- [1] M. Carneiro, “The Type Theory of Lean,” Master's thesis, Carnegie Mellon University, 2019. [Online]. Available: https://github.com/digama0/lean-type-theory/releases/tag/v1.0
- [2] M. Carneiro, “Lean4Lean: Verifying a Typechecker for Lean, in Lean,” in 31st International Conference on Types for Proofs and Programs (TYPES 2025), in Leibniz International Proceedings in Informatics (LIPIcs), vol. 384. Schloss Dagstuhl – Leibniz-Zentrum für Informatik, 2026, p. 2:1–2:23. doi: 10.4230/LIPIcs.TYPES.2025.2.
- [3] M. Carneiro, “lean4lean-model.” [Online]. Available: https://github.com/digama0/lean4lean-model
- [4] B. Werner, “Sets in Types, Types in Sets,” in Theoretical Aspects of Computer Software (TACS '97), in Lecture Notes in Computer Science, vol. 1281. Springer, 1997, pp. 530–546. doi: 10.1007/BFb0014566.
- [5] B. Barras, “Sets in Coq, Coq in Sets,” Journal of Formalized Reasoning, vol. 3, no. 1, pp. 29–48, 2010, doi: 10.6092/issn.1972-5787/1695.
- [6] P. Dybjer, “Inductive Families,” Formal Aspects of Computing, vol. 6, no. 4, pp. 440–465, 1994, doi: 10.1007/BF01211308.
- [7] M. Abbott, T. Altenkirch, and N. Ghani, “Containers: Constructing Strictly Positive Types,” Theoretical Computer Science, vol. 342, no. 1, pp. 3–27, 2005, doi: 10.1016/j.tcs.2005.06.002.
- [8] D. Traytel, A. Popescu, and J. C. Blanchette, “Foundational, Compositional (Co)datatypes for Higher-Order Logic: Category Theory Applied to Theorem Proving,” in 27th Annual IEEE Symposium on Logic in Computer Science (LICS 2012), IEEE, 2012, pp. 596–605. doi: 10.1109/LICS.2012.75.
- [9] P. Aczel, “An Introduction to Inductive Definitions,” in Handbook of Mathematical Logic, J. Barwise, Ed., North-Holland, 1977, pp. 739–782. doi: 10.1016/S0049-237X(08)71120-0.