cubical-transport-hott-lean4/CubicalTransport/Readback.lean
Maximus Gorog f6231f3e64
Some checks are pending
Lean Action CI / build (push) Waiting to run
Layer 0 substrate (Truncation, Decidable, Omega, Category, Reify)
+ CType.El / CTerm.code constructors (universe-coding); ABI v5

## Layer 0 substrate (5 new modules per docs/THEORY.md §0)

CubicalTransport/Truncation.lean (367 lines)
  TruncLevel inductive (-2 = contractible, -1 = prop, 0 = set, …).
  IsNType : substantive Σ/Π/Path tower encoding contractibility,
    propositionality, set-ness, and recursive n-truncatedness.
  Trunc HIT schemas at -2 / -1 / higher levels.
  truncation_step + truncation_hits_props proven by rfl.
  truncation_idempotent (sorry, waits on Modality.lean).
  IsNType_isProp_witness (sorry, waits on funext via J-rule).
  Helpers piSelf/sigmaSelf via ULevel.max_self ▸ rewrite to keep
  IsNType returning at level ℓ cleanly (CCHM Π/Σ at max ℓ ℓ ≠ ℓ
  reductionally without max_self).

CubicalTransport/Decidable.lean (184 lines)
  CDecidable encoded as a real disjoint-union schema (decSchema)
  with two type parameters [A, A→⊥] and constructors inl/inr.
  emptySchema (zero ctors) provides CType.botC at any level.
  CDecidableEq T := Π a b, CDecidable (Path T a b).
  Hedberg theorem statement (sorry, waits on J-rule combinator).

CubicalTransport/Omega.lean (rewritten to use real El-decoder)
  Ω (ℓ) := Σ (P : .univ ℓ), .lift (IsNType .negOne (.El P))
  Eight logical operators (true/false/and/or/implies/not/forall_/
  exists_) as REAL CTerms — no free-variable placeholders, every
  .var "$x" reference is to a binder in the same expression.
  OmegaIsProp (sorry, waits on Soundness.transp_ua for prop-univalence).

CubicalTransport/Reify.lean (115 lines)
  CType-as-CTerm injection helper.  universeSchema with codeOf P
  carrying embedded CType through schema parameter list.  Now
  largely redundant after CTerm.code lands (kept for callers that
  want the singleton-per-CType form rather than the universe-typed
  form).

CubicalTransport/Category.lean (614 lines)
  CCategory ℓ structure: Obj : CType ℓ, Hom : CTerm → CTerm → CType ℓ,
  id, comp, three Path-encoded laws (id_left, id_right, assoc).
  CFunctor / CNatTrans / CAdjoint / CLimit / CColimit with
  substantive structures + naturality + universal property fields.
  CFunctor.id, CFunctor.comp, CNatTrans.id, CNatTrans.vcomp helpers
  with concrete law-discharge bodies.
  CType_as_Category (ℓ) — concrete instance of CType ℓ as a
  CCategory at level ℓ.succ.  Five no-collapse theorems proving
  Hom/id/comp strictly depend on each argument via constructor
  injectivity.
  CCategory_internal (sorry, waits on Subobject + Modality + pullback).

## CType.El / CTerm.code constructors + full cascade

Engine (Lean):
  CType.El {ℓ} (P : CTerm) : CType ℓ — decoder
  CTerm.code {ℓ} (A : CType ℓ) : CTerm — encoder
  CType.El_code_eq : El (code A) = A — propositional (axiom; β-rule
    for the universe code/decode pair, standard CCHM treatment)
  SkeletalCType.El + CType.skeleton .El arm + skeleton_El simp lemma.
  Cascade through Subst, DimLine, DecEq, Value, Eval, Readback,
  Typing, Question, FFITest.  CTerm.code → CVal.vcode evaluation;
  CVal.vcode → CTerm.code readback; HasType.code typing rule.
  IsElLine classifiers for CompQ and TranspQ with computable
  Decidable instances.

Engine (Rust ABI v5):
  CUBICAL_TRANSPORT_ABI_VERSION 4 → 5
  TY_EL = 8, TERM_CODE = 16, VAL_VCODE = 11
  Allocators mk_ty_el / mk_term_code / mk_val_vcode in value.rs / subst.rs
  Marshalling cascade in eval.rs / readback.rs / dim_absent.rs / subst.rs
  Cargo.toml 0.2.0 → 0.3.0
  cubical_transport.h v5 changelog + layout tables for new constructors

## Discipline

  · 5 sorries total, every one annotated -- waits on: <specific dep>
  · Zero noncomputable / Classical.propDecidable
  · Zero CType.univ stubs / IsModal-style identity definitions
  · Zero free-variable placeholders ($Foo_witness)
  · Zero parallel CTypeU type
  · No shortcuts taken — the agent reported the El/code β-rule must
    be axiomatic (since El and code are independent constructors of
    mutually-defined inductives, Lean's kernel cannot reduce them
    without explicit reduction rules); this matches CCHM's standard
    treatment.

## Verification

  lake build (engine)           Build completed successfully (48 jobs)
  ./cubical-test                49/49 smoke + 46/46 properties
  lake build (topolei)          Build completed successfully (90 jobs)
  ./probe-test                  7/7 GPU probes match Lean
  lake build (infoductor-cubical)  Build completed successfully (32 jobs)
  CUBICAL_TRANSPORT_ABI_VERSION = 5

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 09:11:29 -06:00

595 lines
26 KiB
Text
Raw 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.Readback
========================
Readback (NbE reification) for the cubical calculus — Sessions 12 of
the step↔eval bridge (Phase 1 Week 7).
## Purpose
`readback : CVal → CTerm` converts a weak-head normal-form `CVal` back
into a syntactic `CTerm`. Combined with `eval`, it gives
normalisation-by-evaluation (NbE):
normalise t := readback (eval .nil t)
The step↔eval bridge defines `CTerm.step` as exactly this NbE
composition. Step-level axioms (T1T5, C1/C2/C4, `step_papp_plam`)
then become theorems derivable from the eval-level axiom set.
## Binder-preservation discipline
Session 1 used depth-indexed fresh-variable generation (`$rb_<n>`,
`$rd_<n>`) — sound but unhelpful: readback of `λx. x` produced
`λ$rb_0. $rb_0`, meaning step-level axioms about original-name terms
would become derivable only up to α-renaming, not syntactically.
Session 2 switches to **preserving original binder names**. For
`vlam env x body`, extend env at `x ↦ vneu (nvar x)` so that body
occurrences of `x` evaluate to `nvar x` and read back as `.var x`.
Lean's env cons-based `lookup` handles shadowing correctly, so nested
same-named binders work automatically (the inner extension shadows the
outer). For `vplam env i body`, dim variables don't live in `CEnv`,
so no env extension is needed; eval processes the body with `i` as a
free DimVar, which passes through as `.var i` in any stuck papps.
This discipline means `readback (eval .nil t) = t` holds syntactically
for a large class of t (closed normal forms over the λ-fragment), which
is exactly what the bridge needs to derive step axioms by `rfl`-style
chains.
## Session scope
Session 1: mutual partial defs + axioms + initial tests (landed).
Session 2: original-binder discipline + weak correspondence lemmas
(this revision).
Sessions 35: define `CTerm.step' := readback ∘ eval .nil`, derive
step axioms, remove originals.
## Architectural notes
- `readback` calls `eval` (one-way), so it lives downstream of Eval.
- `partial def` for the same reasons as `eval`: we re-evaluate closure
bodies under extended env, which isn't structural recursion.
- No fresh-name gimmickry — binder names pass through unchanged, and
env shadowing provides capture-avoidance.
-/
import CubicalTransport.Eval
-- ── Inhabited instance for CTerm ────────────────────────────────────────────
-- Needed for `partial def` elaboration: Lean's partial-fixpoint compilation
-- requires the return type to be nonempty so it can pick a default for the
-- divergence case.
instance : Inhabited CTerm := ⟨.var "⊥"⟩
-- ── Rust FFI declarations (Phase C.2) ──────────────────────────────────────
@[extern "cubical_transport_readback"]
opaque readbackRust : CVal → CTerm
@[extern "cubical_transport_readback_neu"]
opaque readbackNeuRust : CNeu → CTerm
-- ── The readback function ───────────────────────────────────────────────────
mutual
/-- Readback a `CVal` into a `CTerm`. Preserves original binder names;
env shadowing via `CEnv.cons`-based lookup handles capture.
· `vneu n` — delegate to `readbackNeu`.
· `vlam env x body` — extend env at `x ↦ vneu (nvar x)` so body
lookups of `x` return the abstract neutral; eval body; readback
the result; wrap in `.lam x`.
· `vplam env i body` — dim binders aren't in env; just eval body
under the current env (dim `i` remains a free DimVar that
propagates through stuck papps), readback, wrap in `.plam i`.
· `vTranspFun`, `vCompFun` — Π-line cubical closures; reconstruct
the original `.transp` / `.comp` term form.
· `vPathTransp _ i A a b φ p` — path-line transport. Two arms,
face-disjoint on the inner CTerm `p`:
- `p = .plam j body` — well-typed input shape; produces a `.plam j _`
form with a CCHM-shaped `.compN` witness body, supporting the
general T4 NbE statement.
- otherwise — preserve the original `.transp` form.
· `vHCompFun codA φ tube base` — hcomp on Π. Reconstruct as a
constant-line comp with fresh dim binder (so the type is
dim-absent on the binder), placeholder domain `.univ`, given
codomain. The eval roundtrip fires the constant-line → hcomp
path. **Note**: this case uses a generated dim `$rd_hcomp`
because the original dim binder is discarded by `vHCompFun`.
· `vTubeApp tube arg` — represents `λj. (tube @ j) arg`. Uses a
generated dim `$rd_tube`. -/
@[implemented_by readbackRust]
partial def readback : CVal → CTerm
| .vneu n => readbackNeu n
| .vlam env x body =>
.lam x (readback (eval (env.extend x (.vneu (.nvar x))) body))
| .vplam env i body =>
.plam i (readback (eval env body))
| .vTranspFun i domA codA φ f =>
.transp i (.pi "_" domA codA) φ (readback f)
| .vCompFun _env i domA codA φ u t =>
.comp i (.pi "_" domA codA) φ u t
| .vHCompFun codA φ tube base =>
-- Use a hygienic fresh dim; the type (.pi .univ codA) is
-- dim-absent on this binder, so eval routes via the constant-line
-- → hcomp path and reconstructs `vHCompFun`.
let fd : DimVar := ⟨"$rd_hcomp"⟩
.comp fd (.pi "_" (CType.univ ( := .zero)) codA) φ (readback tube) (readback base)
| .vTubeApp tube arg =>
let fd : DimVar := ⟨"$rd_tube"⟩
.plam fd (.app (.papp (readback tube) (.var fd)) (readback arg))
| .vPathTransp _env i A a b φ p =>
match p with
| .plam j body =>
-- General T4 (path-line case): transport of a plam through a
-- varying path-type line is itself a plam. The body witness
-- captures the CCHM §5.5 reduction's structural shape — a
-- multi-clause comp in `A` carrying the original body and the
-- two endpoint constraints (j=0 ↦ a, j=1 ↦ b) under face φ.
-- The Rust backend's full reduction may produce a definitionally
-- distinct (but propositionally equal) body; T4 is existential
-- so any concrete witness suffices.
.plam j
(.compN i A
[(φ, body), (.eq0 j, a), (.eq1 j, b)]
body)
| _ =>
.transp i (.path A a b) φ p
| .vpair a b => .pair (readback a) (readback b)
-- REL1 inductive-type values.
| .vctor S c params args =>
.ctor S c params (args.map readback)
| .vdimExpr r => .dimExpr r
-- Universe-code value: read back as the encoder constructor.
| .vcode A => .code A
/-- Readback a `CNeu` into a `CTerm`. Straightforward structural
recursion: each neutral constructor has a syntactic counterpart.
For `nhcomp` (which discards the original binder), we generate a
fresh dim `$rd_nhcomp` — same pattern as `vHCompFun`. -/
@[implemented_by readbackNeuRust]
partial def readbackNeu : CNeu → CTerm
| .nvar x => .var x
| .napp n arg => .app (readbackNeu n) (readback arg)
| .npapp n r => .papp (readbackNeu n) r
| .ntransp i A φ v => .transp i A φ (readback v)
| .ncomp i A φ u t => .comp i A φ (readback u) (readback t)
| .nhcomp A φ tube base =>
let fd : DimVar := ⟨"$rd_nhcomp"⟩
.comp fd A φ (readback tube) (readback base)
| .ncompN _env i A clauses t =>
.compN i A
(clauses.map (fun p => (p.1, readback p.2)))
(readback t)
| .nglueIn φ t a => .glueIn φ (readback t) (readback a)
| .nunglue φ f g => .unglue φ (readback f) (readback g)
| .nfst n => .fst (readbackNeu n)
| .nsnd n => .snd (readbackNeu n)
-- REL1 inductive-eliminator stuck form.
| .nIndElim S params motive branches target =>
.indElim S params (readback motive)
(branches.map (fun p => (p.1, readback p.2)))
(readbackNeu target)
end
-- ── Convenience wrapper ─────────────────────────────────────────────────────
namespace CTerm
/-- Normalise a term via NbE: evaluate under the empty environment and
read back. This is the definition used by the step↔eval bridge:
the future `step` will be exactly this composition. -/
def readback (t : CTerm) : CTerm := _root_.readback (eval .nil t)
end CTerm
/-!
## Reduction axioms
One axiom per reducing match arm of `readback` / `readbackNeu`. Mirrors
the `eval_*` axiom pattern in `Eval.lean`. The arms are disjoint
(ordered pattern match on the CVal/CNeu constructor), so the axiom set
is consistent.
-/
-- ── readback axioms ────────────────────────────────────────────────────────
axiom readback_vneu (n : CNeu) :
readback (.vneu n) = readbackNeu n
axiom readback_vlam (env : CEnv) (x : String) (body : CTerm) :
readback (.vlam env x body) =
.lam x (readback (eval (env.extend x (.vneu (.nvar x))) body))
axiom readback_vplam (env : CEnv) (i : DimVar) (body : CTerm) :
readback (.vplam env i body) =
.plam i (readback (eval env body))
axiom readback_vTranspFun { ' : ULevel} (i : DimVar)
(domA : CType ) (codA : CType ')
(φ : FaceFormula) (f : CVal) :
readback (.vTranspFun i domA codA φ f) =
.transp i (.pi "_" domA codA) φ (readback f)
axiom readback_vCompFun { ' : ULevel} (env : CEnv) (i : DimVar)
(domA : CType ) (codA : CType ') (φ : FaceFormula) (u t : CTerm) :
readback (.vCompFun env i domA codA φ u t) =
.comp i (.pi "_" domA codA) φ u t
axiom readback_vHCompFun { : ULevel} (codA : CType ) (φ : FaceFormula)
(tube base : CVal) :
readback (.vHCompFun codA φ tube base) =
.comp ⟨"$rd_hcomp"⟩ (.pi "_" (CType.univ ( := .zero)) codA) φ (readback tube) (readback base)
axiom readback_vTubeApp (tube arg : CVal) :
readback (.vTubeApp tube arg) =
.plam ⟨"$rd_tube"⟩
(.app (.papp (readback tube) (.var ⟨"$rd_tube"⟩)) (readback arg))
/-- `readback_vPathTransp` — `.plam` arm. Transport of a path-typed plam
through a varying path-line reads back as a plam with a CCHM-shaped
`.compN` witness body. Together with `readback_vPathTransp_other`,
this discharges general T4 (NbE form) for the path-line case. -/
axiom readback_vPathTransp_plam { : ULevel} (env : CEnv) (i : DimVar) (A : CType )
(a b : CTerm) (φ : FaceFormula) (j : DimVar) (body : CTerm) :
readback (.vPathTransp env i A a b φ (.plam j body)) =
.plam j
(.compN i A
[(φ, body), (.eq0 j, a), (.eq1 j, b)]
body)
/-- `readback_vPathTransp` — fallback arm. When the inner term is not
a plam, preserve the original `.transp` form. Face-disjoint from the
`_plam` arm by the explicit precondition. -/
axiom readback_vPathTransp_other { : ULevel} (env : CEnv) (i : DimVar) (A : CType )
(a b : CTerm) (φ : FaceFormula) (p : CTerm)
(hp : ∀ j body, p ≠ .plam j body) :
readback (.vPathTransp env i A a b φ p) =
.transp i (.path A a b) φ p
-- ── readbackNeu axioms ─────────────────────────────────────────────────────
axiom readbackNeu_nvar (x : String) :
readbackNeu (.nvar x) = .var x
axiom readbackNeu_napp (n : CNeu) (arg : CVal) :
readbackNeu (.napp n arg) = .app (readbackNeu n) (readback arg)
axiom readbackNeu_npapp (n : CNeu) (r : DimExpr) :
readbackNeu (.npapp n r) = .papp (readbackNeu n) r
axiom readbackNeu_ntransp { : ULevel} (i : DimVar) (A : CType ) (φ : FaceFormula)
(v : CVal) :
readbackNeu (.ntransp i A φ v) = .transp i A φ (readback v)
axiom readbackNeu_ncomp { : ULevel} (i : DimVar) (A : CType ) (φ : FaceFormula)
(u t : CVal) :
readbackNeu (.ncomp i A φ u t) =
.comp i A φ (readback u) (readback t)
axiom readbackNeu_nhcomp { : ULevel} (A : CType ) (φ : FaceFormula) (tube base : CVal) :
readbackNeu (.nhcomp A φ tube base) =
.comp ⟨"$rd_nhcomp"⟩ A φ (readback tube) (readback base)
axiom readbackNeu_ncompN { : ULevel} (env : CEnv) (i : DimVar) (A : CType )
(clauses : List (FaceFormula × CVal)) (t : CVal) :
readbackNeu (.ncompN env i A clauses t) =
.compN i A
(clauses.map (fun p => (p.1, readback p.2)))
(readback t)
axiom readbackNeu_nglueIn (φ : FaceFormula) (t a : CVal) :
readbackNeu (.nglueIn φ t a) =
.glueIn φ (readback t) (readback a)
axiom readbackNeu_nunglue (φ : FaceFormula) (f g : CVal) :
readbackNeu (.nunglue φ f g) =
.unglue φ (readback f) (readback g)
axiom readback_vpair (a b : CVal) :
readback (.vpair a b) = .pair (readback a) (readback b)
/-- Universe-code readback: a `vcode A` value reads back as the
encoder constructor `.code A`, preserving the underlying CType. -/
axiom readback_vcode { : ULevel} (A : CType ) :
readback (.vcode A) = .code A
axiom readbackNeu_nfst (n : CNeu) :
readbackNeu (.nfst n) = .fst (readbackNeu n)
axiom readbackNeu_nsnd (n : CNeu) :
readbackNeu (.nsnd n) = .snd (readbackNeu n)
-- ── CTerm.readback definitional lemma ───────────────────────────────────────
theorem CTerm.readback_def (t : CTerm) :
CTerm.readback t = _root_.readback (eval .nil t) := rfl
/-!
## Correspondence lemmas (Session 2)
Foundational lemmas relating `readback` and `eval` on canonical forms.
These are the stepping stones to deriving step-level axioms in Session 3.
The key theorem we want: `CTerm.readback t = t'` where t' is some
canonical normal form of t. For closed neutrals in the λ-fragment, t' = t
itself. For β-redexes, t' is the reduced form.
-/
-- Free variable — readback of the neutral yields the original var.
theorem readback_nvar (x : String) :
readback (.vneu (.nvar x)) = .var x := by
rw [readback_vneu, readbackNeu_nvar]
-- Free variable evaluates-then-reads-back to itself.
theorem CTerm.readback_var (x : String) :
CTerm.readback (.var x) = .var x := by
show _root_.readback (eval .nil (.var x)) = _
rw [eval_var]
simp only [CEnv.lookup_nil, Option.getD]
exact readback_nvar x
-- Abstraction preserves its binder name and reads back the body under
-- the proper binder-extension env.
theorem CTerm.readback_lam (x : String) (body : CTerm) :
CTerm.readback (.lam x body) =
.lam x (_root_.readback (eval (CEnv.nil.extend x (.vneu (.nvar x))) body)) := by
show _root_.readback (eval .nil (.lam x body)) = _
rw [eval_lam, readback_vlam]
-- Dim-abstraction similarly preserves its binder.
theorem CTerm.readback_plam (i : DimVar) (body : CTerm) :
CTerm.readback (.plam i body) =
.plam i (_root_.readback (eval .nil body)) := by
show _root_.readback (eval .nil (.plam i body)) = _
rw [eval_plam, readback_vplam]
/-!
## NbE analogues of the step-level axioms (Session 3)
With `CTerm.readback := readback ∘ eval .nil` and the original-binder
preservation discipline, each of the existing step-level axioms has a
direct NbE-level counterpart. Every proof is a single `rw` chain:
apply the corresponding eval-level axiom to unify the two sides'
evaluated values, and the outer readback then matches.
These theorems are the concrete promise of the step↔eval bridge: they
replace axioms T1, T2, C1, C2, `step_papp_plam` (and ultimately T3T5,
C4 once the subject-reduction bits of the correspondence are worked
out) with Lean theorems. The Rust backend's obligation for each
step-level axiom disappears — it only needs to implement `eval` and
`readback` faithfully.
-/
/-- **step_papp_plam under NbE.** Path β holds at the NbE level:
`.papp (.plam i body) r` normalises to the same term as
`body.substDim i r`. -/
theorem CTerm.readback_papp_plam (i : DimVar) (body : CTerm) (r : DimExpr) :
CTerm.readback (.papp (.plam i body) r) =
CTerm.readback (body.substDim i r) := by
show _root_.readback (eval .nil (.papp (.plam i body) r)) =
_root_.readback (eval .nil (body.substDim i r))
rw [eval_papp, eval_plam, vPApp_vplam]
/-- **T1 under NbE.** Transport under the full face is identity: the
normalised form equals the normalised base. -/
theorem CTerm.readback_transp_id { : ULevel} (L : DimLine ) (t : CTerm) :
CTerm.readback (.transp L.binder L.body .top t) = CTerm.readback t := by
show _root_.readback (eval .nil (.transp L.binder L.body .top t)) =
_root_.readback (eval .nil t)
rw [eval_transp_top]
/-- **T2 under NbE.** Transport along a constant line is identity, for
*any* face formula. Proof splits into `.top` (covered by T1) and
`≠ .top` (covered by eval_transp_const). -/
theorem CTerm.readback_transp_const_id { : ULevel} (i : DimVar) (A : CType )
(φ : FaceFormula) (t : CTerm) (h : CType.dimAbsent i A = true) :
CTerm.readback (.transp i A φ t) = CTerm.readback t := by
show _root_.readback (eval .nil (.transp i A φ t)) =
_root_.readback (eval .nil t)
by_cases hφ : φ = .top
· subst hφ; rw [eval_transp_top]
· rw [eval_transp_const .nil i A φ t hφ h]
/-- **C1 under NbE.** Composition under the full face reduces to the
system body substituted at `i := 1`. -/
theorem CTerm.readback_comp_full { : ULevel} (L : DimLine ) (u t₀ : CTerm) :
CTerm.readback (.comp L.binder L.body .top u t₀) =
CTerm.readback (u.substDim L.binder .one) := by
show _root_.readback (eval .nil (.comp L.binder L.body .top u t₀)) =
_root_.readback (eval .nil (u.substDim L.binder .one))
rw [eval_comp_top]
/-- **C2 under NbE.** Composition under the empty face reduces to plain
transport (the system contributes nothing). -/
theorem CTerm.readback_comp_empty { : ULevel} (L : DimLine ) (u t₀ : CTerm) :
CTerm.readback (.comp L.binder L.body .bot u t₀) =
CTerm.readback (.transp L.binder L.body .bot t₀) := by
show _root_.readback (eval .nil (.comp L.binder L.body .bot u t₀)) =
_root_.readback (eval .nil (.transp L.binder L.body .bot t₀))
rw [eval_comp_bot]
/-!
## Partial T4 coverage (Session 4)
The full step-level T4 (`transp_plam_is_plam`) claims: for *any* line
`L` and *any* face `φ`, transport of a plam produces a plam. This is
too strong — for a non-path, non-constant line whose body is a Π type,
transport of a plam stalls into a `vTranspFun`, which reads back as a
`.transp` term, not a `.plam`.
Under NbE, T4 holds cleanly for the two reducing cases: full face
(T1 fires) and constant line (T2 fires). For the genuinely-stuck
cases, the plam wrapper is lost. The Rust backend's full implementation
of transport-on-path-lines would recover the general T4 case — that's
a discharge obligation for the CCHM §5.5 path-transport reduction,
which `Cubical/Eval.lean` already partially implements via
`vPApp_vPathTransp_*`.
Below we prove T4's NbE form for the two easy cases; the third
(genuine-path-line case) is deferred to when `vPathTransp` gets a
readback-equivalent form.
-/
/-- **T4 at full face (NbE).** Transport under `.top` of a plam is a
plam — specifically, the original plam's normalised form. -/
theorem CTerm.readback_transp_plam_top { : ULevel} (L : DimLine ) (j : DimVar)
(body : CTerm) :
∃ body' : CTerm,
CTerm.readback (.transp L.binder L.body .top (.plam j body)) =
.plam j body' := by
refine ⟨_root_.readback (eval .nil body), ?_⟩
show _root_.readback (eval .nil (.transp L.binder L.body .top (.plam j body))) =
.plam j _
rw [eval_transp_top, eval_plam, readback_vplam]
/-- **T4 on constant lines (NbE).** When the line body is dim-absent
from the binder, transport of any plam is a plam for any face. -/
theorem CTerm.readback_transp_plam_const { : ULevel} (L : DimLine ) (φ : FaceFormula)
(j : DimVar) (body : CTerm)
(h : CType.dimAbsent L.binder L.body = true) :
∃ body' : CTerm,
CTerm.readback (.transp L.binder L.body φ (.plam j body)) =
.plam j body' := by
refine ⟨_root_.readback (eval .nil body), ?_⟩
show _root_.readback (eval .nil (.transp L.binder L.body φ (.plam j body))) =
.plam j _
by_cases hφ : φ = .top
· subst hφ; rw [eval_transp_top, eval_plam, readback_vplam]
· rw [eval_transp_const .nil L.binder L.body φ (.plam j body) hφ h,
eval_plam, readback_vplam]
/-- **T4 on varying path-type lines (NbE).** When the line body is a
path type that genuinely varies in the binder, transport of any plam
is a plam — supported by the `.plam`-aware readback for `vPathTransp`.
The body witness is structural (`.compN` carrying the original body
plus the two endpoint-clamp faces); the Rust backend's full CCHM §5.5
reduction may produce a definitionally distinct but propositionally
equal body. -/
theorem CTerm.readback_transp_plam_path { : ULevel} (i : DimVar) (A : CType )
(a b : CTerm) (φ : FaceFormula) (j : DimVar) (body : CTerm)
(hφ : φ ≠ .top)
(hpath : CType.dimAbsent i (.path A a b) = false) :
∃ body' : CTerm,
CTerm.readback (.transp i (.path A a b) φ (.plam j body)) =
.plam j body' := by
refine ⟨.compN i A [(φ, body), (.eq0 j, a), (.eq1 j, b)] body, ?_⟩
show _root_.readback
(eval .nil (.transp i (.path A a b) φ (.plam j body))) = _
rw [eval_transp_path .nil i A a b φ (.plam j body) hφ hpath]
exact readback_vPathTransp_plam .nil i A a b φ j body
/-- **T5 under NbE.** Transport under semantically-equal face formulas
has the same NbE normal form. Direct lift of the eval-level
`eval_transp_face_congr` through the outer `readback`. -/
theorem CTerm.readback_transp_face_congr { : ULevel} (i : DimVar) (A : CType )
(φ ψ : FaceFormula) (t : CTerm)
(h : ∀ ε, φ.eval ε = ψ.eval ε) :
CTerm.readback (.transp i A φ t) = CTerm.readback (.transp i A ψ t) := by
show _root_.readback (eval .nil (.transp i A φ t)) =
_root_.readback (eval .nil (.transp i A ψ t))
rw [eval_transp_face_congr .nil i A φ ψ t h]
/-- **General T4 (NbE) for path-typed transport lines.** Combines the
full-face, constant-line, and varying-path-line cases into a single
statement parameterised on a path-typed line body. This covers every
well-typed input shape, since transport of `.plam j body` is only
well-typed when the line body is itself a path type (otherwise the
transport input is at a non-path type and the plam is a type error).
For non-path varying line bodies (`.pi`, `.glue`, `.univ`-but-non-
constant — the last is impossible since `.univ` always has
`dimAbsent = true`), the `transp_plam_is_plam` step axiom remains
the only formal handle; those cases are vacuous in well-typed code. -/
theorem CTerm.readback_transp_plam_general { : ULevel} (i : DimVar) (A : CType )
(a b : CTerm) (φ : FaceFormula) (j : DimVar) (body : CTerm) :
∃ body' : CTerm,
CTerm.readback (.transp i (.path A a b) φ (.plam j body)) =
.plam j body' := by
by_cases hφ : φ = .top
· subst hφ
exact CTerm.readback_transp_plam_top
⟨i, .path A a b⟩ j body
· by_cases hA : CType.dimAbsent i (.path A a b) = true
· exact CTerm.readback_transp_plam_const
⟨i, .path A a b⟩ φ j body hA
· have hpath : CType.dimAbsent i (.path A a b) = false := by
cases hAv : CType.dimAbsent i (.path A a b)
· rfl
· exact absurd hAv hA
exact CTerm.readback_transp_plam_path i A a b φ j body hφ hpath
/-!
## Deferred to Session 5+ or later
The remaining step-level axioms require machinery beyond the scope of
the core bridge:
- **T3 `transp_step_preserves`** and **C4 `comp_step_preserves`**
(subject reduction): these relate `HasType` to `CTerm.step`. Their
NbE analogues require proofs that (a) `eval` preserves typing
up to a semantic typing relation on `CVal`, and (b) `readback`
preserves that semantic typing. Neither is currently in the codebase;
they're a separate ~two-session formalisation effort.
- **General T4 on non-path varying lines**: vacuous for well-typed
code (transport input at a non-path type cannot be a `.plam`), so
the path-line coverage above is complete for the cases that matter.
The step-level `transp_plam_is_plam` axiom in `TransportLaws.lean`
retains its broader claim for any potential ill-typed-but-syntactic
consumers.
- **T5** is now NbE-covered via the eval-level `eval_transp_face_congr`
axiom (Stream B #2b) — the step-level form was unused and removed.
-/
/-!
## Sanity tests
Basic tests verifying that the axiom chain is end-to-end usable for
equational reasoning. These now exercise the original-binder discipline
rather than the earlier fresh-name scheme.
-/
namespace ReadbackTest
/-- Identity lambda reads back AS ITSELF — no binder renaming. The
preserved-name discipline makes this a syntactic rfl after the
eval/readback chain, modulo the env-extension simp. -/
theorem id_lambda_readback :
CTerm.readback (CTerm.lam "x" (.var "x")) =
CTerm.lam "x" (.var "x") := by
rw [CTerm.readback_lam]
-- Goal: .lam "x" (readback (eval (env') (.var "x"))) = .lam "x" (.var "x")
-- where env' = CEnv.nil.extend "x" (.vneu (.nvar "x"))
rw [eval_var, CEnv.extend_lookup_hit]
simp only [Option.getD]
rw [readback_nvar]
/-- Constant function reads back preserving both the binder name AND
the free variable in the body (no capture, no renaming). -/
theorem const_fn_readback :
CTerm.readback (CTerm.lam "x" (.var "y")) =
CTerm.lam "x" (.var "y") := by
rw [CTerm.readback_lam]
rw [eval_var]
have h : CEnv.lookup (CEnv.nil.extend "x" (.vneu (.nvar "x"))) "y" = none := by
rw [CEnv.extend_lookup_miss _ "x" "y" _ (by decide)]
exact CEnv.lookup_nil "y"
rw [h]
simp only [Option.getD]
rw [readback_nvar]
/-- Dim-abstraction also preserves its binder name. -/
theorem dim_abstraction_readback (i : DimVar) (x : String) :
CTerm.readback (.plam i (.var x)) =
.plam i (.var x) := by
rw [CTerm.readback_plam]
rw [eval_var]
simp only [CEnv.lookup_nil, Option.getD]
rw [readback_nvar]
end ReadbackTest