cubical-transport-hott-lean4/CubicalTransport/Inductive.lean
Maximus Gorog 4d6853a0ef REL1 Inductive.lean + Rust dispatch + 9 new smoke tests (25/25 + 46/46)
Inductive.lean (new module): schema combinators (mkSchema, mkCtor,
mkPath) and canonical schema instances:
  - Plain inductives: natSchema, boolSchema, listSchema
  - HITs: s1Schema, intervalSchema, propTruncSchema
Plus type-level helpers (CType.natC, listC, s1C, …) and term-level
ergonomic builders (zeroC, succC, nilC, consC, baseC, loopC, …,
natLit, natElim, boolElim, listElim).

Rust kernel (native/cubical/src/):
  · tags.rs       — TY_IND, TERM_DIMEXPR/CTOR/INDELIM, VAL_VCTOR/VDIMEXPR,
                    NEU_NINDELIM tag constants per docs/INDUCTIVE_TYPES.md §6.
  · value.rs      — mk_vctor, mk_vdimexpr, mk_nindelim builders.
  · eval.rs       — TERM_DIMEXPR / TERM_CTOR / TERM_INDELIM dispatch
                    arms; full β-reduction on canonical vctor target
                    (find matching branch by name, vapp chain over
                    ctor args); stuck nIndElim build for vneu target;
                    eval_term_list / eval_branches / find_branch_body
                    helpers (recursive list walking).
  · readback.rs   — VAL_VCTOR / VAL_VDIMEXPR readback arms;
                    readback_val_list, map_readback_branches helpers;
                    NEU_NINDELIM neutral readback; mk_term_dimexpr,
                    mk_term_ctor, mk_term_indelim builders.

Tests (CubicalTransport/FFITest.lean): nine new smoke arms exercising
canonical-form eval (zero, succ-of-succ, false, cons-true-nil, base,
loop@r), readback round-trip on succ zero, and indElim β on Bool with
both branch directions.  Result: 25/25 smoke + 46/46 properties =
71/71 passing.

Existing tests untouched (constructor tags preserved per REL1 freeze).
Rust ABI version is now de facto v2 (new tags); update the
TOPOLEI_FFI_ABI_VERSION constant in a follow-up commit.

Remaining REL1 work (per task list):
  - #4  HasType arms for ctor / indElim
  - #8  transport over .ind axioms
  - #9  composition over .ind axioms
  - Path-ctor boundary firing (REL1.1)
  - Recursive-arg IH wiring in indElim β (REL1.1)
  - Topolei migration (sibling repo) per docs/INDUCTIVE_TYPES.md §9.1

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 15:15:50 -06:00

265 lines
10 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
CubicalTransport.Inductive
==========================
Helper combinators and canonical schema instances for the REL1
schema-based inductive (and higher-inductive) type system.
This module sits *on top of* `Syntax.lean` — it doesn't add new
primitives, only ergonomic builders. Every value here is a plain
CTypeSchema / CType / CTerm; nothing is opaque. Downstream
consumers should prefer these helpers over hand-spelling
CTypeSchema literals so the canonical encodings are maintained
in one place.
## Sections
· §1 Argument-shape helpers
· §2 Schema constructors (mkSchema, mkCtor, mkPath)
· §3 Type-level helpers (CType.list, CType.nat, …, CType.s1, …)
· §4 Term-level helpers (zeroC, succC, nilC, consC, baseC, loopC, …)
· §5 Eliminator builders (natElim, listElim, …)
## Design notes
· Boundary clauses inside HIT path constructors reference *dim args*
via `DimVar.mk "$d_k"` (zero-indexed among `.dim` args) and
*non-dim args* via `CTerm.var "$arg_k"` (zero-indexed in the full
arg list). See INDUCTIVE_TYPES.md §4 for the discipline.
· The schemas here are concrete values, not `def`s under a
`noncomputable` veil — `Nat`, `Bool`, etc. are first-class data
that downstream code can pattern-match on or use as parameters.
-/
import CubicalTransport.Syntax
namespace CubicalTransport.Inductive
-- ── §1. Argument-shape helpers ─────────────────────────────────────────────
/-- A non-recursive argument of a closed CType. -/
@[inline] def argType (A : CType) : CTypeArg := .type A
/-- A reference to the schema's `i`th type parameter. -/
@[inline] def argParam (i : Nat) : CTypeArg := .param i
/-- A recursive argument of the inductive being defined. -/
@[inline] def argSelf : CTypeArg := .self
/-- A dimension argument (path-constructor binder). -/
@[inline] def argDim : CTypeArg := .dim
-- ── §2. Schema constructors ────────────────────────────────────────────────
/-- A point constructor (no boundary system, empty boundary list). -/
@[inline] def mkCtor (name : String) (args : List CTypeArg) : CtorSpec :=
.mk name args []
/-- A path constructor: a constructor whose argument list contains at
least one `.dim` arg, and whose boundary system specifies the
canonical reduction on the corresponding cube faces. -/
@[inline] def mkPath (name : String) (args : List CTypeArg)
(boundary : List (FaceFormula × CTerm)) : CtorSpec :=
.mk name args boundary
/-- Build a schema from name, parameter count, and constructor list. -/
@[inline] def mkSchema (name : String) (numParams : Nat)
(ctors : List CtorSpec) : CTypeSchema :=
.mk name numParams ctors
-- ── §3. Canonical plain inductive schemas ──────────────────────────────────
/-- The natural numbers as a schema.
`Nat` has two point constructors:
- `zero : Nat`
- `succ : Nat → Nat` (recursive arg: `.self`)
-/
def natSchema : CTypeSchema :=
mkSchema "Nat" 0
[ mkCtor "zero" []
, mkCtor "succ" [.self] ]
/-- The booleans as a schema. -/
def boolSchema : CTypeSchema :=
mkSchema "Bool" 0
[ mkCtor "false" []
, mkCtor "true" [] ]
/-- Polymorphic lists as a schema, parameterised by the element type. -/
def listSchema : CTypeSchema :=
mkSchema "List" 1
[ mkCtor "nil" []
, mkCtor "cons" [.param 0, .self] ]
-- ── §4. Canonical HIT schemas ──────────────────────────────────────────────
/-- The circle `S¹` as a HIT.
Constructors:
- `base : S¹` (point)
- `loop : (i : I) → S¹` (path: `loop @ 0 = base = loop @ 1`)
Boundary: at `i=0` and `i=1`, `loop` returns `base`.
The `loop` boundary references `base` via the schema's own
`.ctor` constructor at the same schema (recursive self-reference
inside the schema's body). We construct it inline here. -/
def s1Schema : CTypeSchema :=
-- Build a self-referential schema: `loop`'s boundary uses
-- `.ctor s1Schema "base" [] []` which in turn references `s1Schema`.
-- Since the recursion is structural through the schema's data
-- (not through a Lean-level fixpoint), we define `s1Schema` via
-- Lean's well-founded recursion through structural data; the
-- result is a finite, well-formed `CTypeSchema` value — but Lean
-- won't accept a literal self-recursive `def` like this.
--
-- Workaround: take the schema *name* + a fresh local schema that
-- refers to itself via a placeholder. At construction time we
-- patch the placeholder. This is implemented as a small fix-up
-- below.
let baseAt (s : CTypeSchema) : CTerm := .ctor s "base" [] []
-- We need `s1Schema = mkSchema "S¹" 0 [base, loop]` where
-- `loop` carries a boundary that references `s1Schema`.
-- Lean's `let rec` doesn't handle this for `CTypeSchema` the
-- way it would for a function — but we can use a `where`-style
-- two-step build: first build a "stub" schema, then build the
-- final `loop` referring to it. Both schemas have the same
-- structural shape, so `s1Schema` is well-defined.
let stub : CTypeSchema :=
mkSchema "S¹" 0
[ mkCtor "base" []
, mkPath "loop" [.dim] [] ]
let d : DimVar := ⟨"$d_0"⟩
mkSchema "S¹" 0
[ mkCtor "base" []
, mkPath "loop" [.dim]
[ (.eq0 d, baseAt stub)
, (.eq1 d, baseAt stub) ] ]
/-- The interval as a HIT.
Constructors:
- `zero : I`
- `one : I`
- `seg : (i : I) → I` with `seg @ 0 = zero`, `seg @ 1 = one`. -/
def intervalSchema : CTypeSchema :=
let stub : CTypeSchema :=
mkSchema "I" 0
[ mkCtor "zero" []
, mkCtor "one" []
, mkPath "seg" [.dim] [] ]
let zeroAt (s : CTypeSchema) : CTerm := .ctor s "zero" [] []
let oneAt (s : CTypeSchema) : CTerm := .ctor s "one" [] []
let d : DimVar := ⟨"$d_0"⟩
mkSchema "I" 0
[ mkCtor "zero" []
, mkCtor "one" []
, mkPath "seg" [.dim]
[ (.eq0 d, zeroAt stub)
, (.eq1 d, oneAt stub) ] ]
/-- Propositional truncation `‖A‖₋₁` as a HIT.
Constructors:
- `inT : A → ‖A‖₋₁` (point)
- `squash : (x y : ‖A‖₋₁) → (i : I) → ‖A‖₋₁`
with `squash x y @ 0 = x`, `squash x y @ 1 = y`. -/
def propTruncSchema : CTypeSchema :=
let d : DimVar := ⟨"$d_0"⟩
-- arg index: 0 = first .self ("$arg_0"), 1 = second .self ("$arg_1"),
-- 2 = .dim ($d_0). Boundary references positional arg names.
mkSchema "‖_‖₋₁" 1
[ mkCtor "inT" [.param 0]
, mkPath "squash" [.self, .self, .dim]
[ (.eq0 d, .var "$arg_0")
, (.eq1 d, .var "$arg_1") ] ]
-- ── §5. Type-level helpers ─────────────────────────────────────────────────
/-- The `CType` for natural numbers. -/
@[inline] def CType.natC : CType := .ind natSchema []
/-- The `CType` for booleans. -/
@[inline] def CType.boolC : CType := .ind boolSchema []
/-- The `CType` for lists with element type `A`. -/
@[inline] def CType.listC (A : CType) : CType := .ind listSchema [A]
/-- The `CType` for the circle. -/
@[inline] def CType.s1C : CType := .ind s1Schema []
/-- The `CType` for the interval. -/
@[inline] def CType.intervalC : CType := .ind intervalSchema []
/-- The `CType` for propositional truncation `‖A‖₋₁`. -/
@[inline] def CType.propTruncC (A : CType) : CType :=
.ind propTruncSchema [A]
-- ── §6. Term-level helpers ─────────────────────────────────────────────────
namespace CTerm
/-- The `CTerm` `zero : Nat`. -/
@[inline] def zeroC : CTerm := .ctor natSchema "zero" [] []
/-- The `CTerm` `succ n` for a given `n : Nat`. -/
@[inline] def succC (n : CTerm) : CTerm := .ctor natSchema "succ" [] [n]
/-- The `CTerm` `false : Bool`. -/
@[inline] def falseC : CTerm := .ctor boolSchema "false" [] []
/-- The `CTerm` `true : Bool`. -/
@[inline] def trueC : CTerm := .ctor boolSchema "true" [] []
/-- The `CTerm` `nil : List A`. Carries the element type as parameter. -/
@[inline] def nilC (A : CType) : CTerm := .ctor listSchema "nil" [A] []
/-- The `CTerm` `cons x xs : List A`. -/
@[inline] def consC (A : CType) (x xs : CTerm) : CTerm :=
.ctor listSchema "cons" [A] [x, xs]
/-- The `CTerm` `base : S¹`. -/
@[inline] def baseC : CTerm := .ctor s1Schema "base" [] []
/-- The `CTerm` `loop @ r : S¹` where `r : DimExpr`. -/
@[inline] def loopC (r : DimExpr) : CTerm :=
.ctor s1Schema "loop" [] [.dimExpr r]
/-- The `CTerm` `inT a : ‖A‖₋₁`. -/
@[inline] def inTC (A : CType) (a : CTerm) : CTerm :=
.ctor propTruncSchema "inT" [A] [a]
/-- The `CTerm` `squash x y @ r : ‖A‖₋₁`. -/
@[inline] def squashC (A : CType) (x y : CTerm) (r : DimExpr) : CTerm :=
.ctor propTruncSchema "squash" [A] [x, y, .dimExpr r]
/-- Build a Nat literal `n` as a tower of `succ`s on `zero`. -/
def natLit : Nat → CTerm
| 0 => zeroC
| n + 1 => succC (natLit n)
end CTerm
-- ── §7. Eliminator builders ────────────────────────────────────────────────
/-- Build an `indElim` for `Nat`. `motive` is the result type-former,
`caseZero` the body for `zero`, `caseSucc` the body for `succ`
(curried as `λ pred ih. body`). `target` is the natural being
eliminated. -/
def natElim (motive caseZero caseSucc target : CTerm) : CTerm :=
.indElim natSchema [] motive
[("zero", caseZero), ("succ", caseSucc)] target
/-- Build an `indElim` for `Bool`. -/
def boolElim (motive caseFalse caseTrue target : CTerm) : CTerm :=
.indElim boolSchema [] motive
[("false", caseFalse), ("true", caseTrue)] target
/-- Build an `indElim` for `List A`. `caseCons` is curried as
`λ head tail ih. body`. -/
def listElim (A : CType) (motive caseNil caseCons target : CTerm) : CTerm :=
.indElim listSchema [A] motive
[("nil", caseNil), ("cons", caseCons)] target
end CubicalTransport.Inductive