Copatterns: defining observations, over primitive corecursion - #71
Open
Russoul wants to merge 2 commits into
Open
Copatterns: defining observations, over primitive corecursion#71Russoul wants to merge 2 commits into
Russoul wants to merge 2 commits into
Conversation
Three layers, spec-first throughout.
Foundation strengthens the ν-scheme to PRIMITIVE corecursion (the
apomorphism): the coalgebra fills each hole with ν 𝔽 ⊎ a — inj₁ STOPS
at an existing element (released bare by el-nu-beta, via the
stop-or-continue copair [𝕚𝕕 ‖ hᵉˡ]), inj₂ CONTINUES from a seed — and
el-nu-coind's relator goes UP TO EQUALITY (lift_𝕏(R) u v ≜
∥R ⊎ (u ≡ v ∈ ν 𝔽)∥). Both admissible: the derivation note extends by
lift-reflexivity-on-equals, the subsumption note by the copy-leg
encoding of the apomorphism. Kernel replay, elaborator and corpus
migrate; cons/csucc become one-stop machines and tlCons demotes from
the deliberately η-needing coinduction to a β-lemma.
On top of that, the COPATTERN item (docs/NovaElaboration.txt,
'Defining observations') — the clausal def's dual:
def cons : {a : 𝕌} → a → stream a → stream a using (…)
| out (cons x s) ≔ x, s
expands to the contractibility batch: EXISTENCE (a synthesized
primitive corecursor — the body read against the polynomial's shape,
calls continuing at the varying-columns seed, f-free elements
stopping), the OBSERVATION LEMMA fOut (λ…. ⋆ — β in every case), and
UNIQUENESS fEta (coind at the pure graph invariant; stops close in
the relator's up-to-equality leg). Tiers degrade as at the clausal
def: witness-supplied existence keeps the lemma ⋆ an ordinary
obligation, non-fragment bodies demote to declarations. The one
Σ-consultation — the ν-head's polynomial shape — is read by a
snapshot-rolled-back probe under the item's using licenses.
IMPLICIT columns follow the term syntax everywhere: LHS binders and
call arguments elided or {…}-spelled, placement resolved from the
item's own type, elided call values verified against the
insertion-resolved body by the probe (declared under a machine name —
a probe declaration cached under the real name would shadow the later
definition in the Σ-entry name index). Generated spines apply the
item through {…} overrides, a candidate variable fully and plainly;
generated lemma binders mirror the item's implicitness.
Landing the discharge exposed three latent engine gaps, now fixed:
matchElemP had no coinductive-former clauses (an out-headed rewrite
candidate could never fire); observation equations are growing
rewrites, admitted now as OBSERVATION-SHAPED candidates (the
el-nu-beta counterpart of the clause-shaped admission); and the
certificate-bearing rewriter emitted kernel-untypable steps under
branch binders — it now stays at determinable positions,
scrutinee-first, so eliminator collapse surfaces branches at depth 0.
Corpus: conat's czero/cinf/csucc and stream's cons/iterate/map are
copattern items (implicits elided, canonical distill form);
streamEq/streamBisim unchanged. New goldens: elab-copattern,
elab-copattern-implicit, elab-copattern-degrade, distill-copattern,
item-copattern; the ν goldens migrate to stop-tagged coalgebras with
byte-identical expected outputs. 162/162 tests, 94/94 corpus
elaborations, distill round-trip and canonical form verified.
The clausal-def macro adopts the copattern item's term-syntax
conventions wholesale. Clause LHSs align against the item's leading
Π-columns: an implicit column consumes a {x} pattern if one is next
and is ELIDED otherwise (its binder named by the type, invisible to
the RHS); an explicit column consumes the next pattern; constructor
patterns never sit at implicit columns. Recursive calls elide or
{…}-spell f's implicit arguments likewise — an elided argument reads
as the ambient column, verified by the clause lemma's β-discharge
(residue with a spell-it remedy, never a wrong acceptance).
The item-level using-clause now rides along on every generated item
(and the declaration tier elaborates under the item's scope), so
observation-flavored clausal defs work:
def hd : {a : 𝕌} → stream a → a using (stream.stream.unfold)
| hd t ≔ (out t) .π₁
Generated statements follow the spelling conventions: f's spines
carry {…} overrides at implicit positions, the uniqueness candidate
g (a variable — never inserts) applies fully and plainly with its
clause hypotheses REBUILT for that spelling (mkLemTyG replaces the
head substitution), and the generated lemmas' Π-binders mirror the
columns' implicitness.
Mechanically, the bespoke structural-recursion rewriter (rwE and its
Ty/Poly mirrors) is replaced by a generic spine-rewriting traversal
(mapSpinesE) plus alignment-aware call rewriting (rwCalls) — the
same traversal the g-respell and the copattern fallback use.
Corpus: stream.nova's hd and tl are clausal items over the implicit
column. New golden elab-clauses-implicit covers the no-split form,
ℕ-splits with elided and {a}-spelled implicits, and a plain item
alongside. 163/163 tests, 94/94 corpus elaborations, distill
round-trip and canonical form verified.
Russoul
force-pushed
the
co-pattern-matching
branch
from
September 1, 2026 12:51
3030d51 to
acff830
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Basic copattern matching for Nova — a
definto a ν-type states its one observation and the elaborator synthesizes everything else — built on two coordinated strengthenings of the coinductive scheme in Foundation.1. Primitive corecursion + coinduction up to equality (theory)
el-nu-i/-beta/-eta: the corecursor is now the apomorphism — the coalgebra fills each hole withν 𝔽 ⊎ a, whereinj₁stops at an existing element andinj₂continues from a seed. β mediates through the stop-or-continue copair[𝕚𝕕 ‖ hᵉˡ], so a stopped element is released bare, judgementally (the inductive side already ships full dependent elimination; this restores parity — the old scheme was dual to mere iteration).el-nu-coind's relator hole clause is now up to equality:lift_𝕏(R) u v ≜ ∥R ⊎ (u ≡ v ∈ ν 𝔽)∥.NovaFoundation.txt; the setoid model validates them on the nose (equality ⊆ the largest bisimulation).cons/csuccbecome one-stop machines, andtlCons— previously the deliberately η-needing example proven by hand-rolled coinduction — demotes to a β-lemma.2. The copattern item (
docs/NovaElaboration.txt, "Defining observations")An item macro, the clausal def's dual: it asserts the observation determines the definiendum and expands into the batch naming that assertion's pieces — existence (a synthesized primitive corecursor, the body read against the polynomial's shape: literal constructors down to the holes, calls continuing at the varying-columns seed, f-free elements stopping), the Π-closed observation lemma
consOut(λ…. ⋆, β in every case), and pointwise uniquenessconsEta(coind at the pure graph invariant; stops close in the relator's up-to-equality leg). Tiers degrade exactly as at the clausal def — witness-supplied existence keeps the lemma ⋆ an ordinary obligation, non-fragment bodies demote to declarations, never a wall. No new Foundation rule or kernel capability beyond layer 1.3. Implicit columns — both macros
LHS and calls follow the ordinary term syntax — implicits elided or
{x}-spelled, never spelled as explicit arguments. Placement is resolved by the macro from the item's own type; the values of elided call arguments are verified against the insertion-resolved body by a snapshot-rolled-back probe (conversion to the corecursor happens after implicit placement is resolved), degrading with a spell-it remedy if an elided argument varies. Generated spines apply the item through{…}overrides and a candidate variable fully and plainly; generated lemma binders mirror the item's implicitness.The pattern-clause macro takes the same conventions (second commit): clause LHSs align against the item's columns with
{x}-spelled or elided implicits, recursive calls elide likewise (elided arguments read as the ambient columns, verified by the clause lemmas' β-discharge), and the item-levelusing-clause rides along on every generated item — sodef hd : {a : 𝕌} → stream a → a using (…) | hd t ≔ (out t) .π₁works, ℕ-splits over implicit-parameterized items included (nth). The bespoke structural-recursion rewriter is replaced by a generic spine-rewriting traversal shared with the copattern respell.Reviewer notes
matchElemPhad no coinductive-former clauses (anout-headed rewrite candidate could never fire); observation equations are growing rewrites, now admitted as observation-shaped candidates (the el-nu-beta counterpart of the clause-shaped admission,SearchlessElaboration.md§12); and the certificate-bearing rewriter (rewriteElemS) emitted kernel-untypable steps under branch binders — it now stays at kernel-determinable positions, scrutinee-first.sigEntryIx) caches positively, sound because Σ only extends within a run. The macro's probe therefore pre-declares the item under a machine name — a probe declaration cached under the real name would shadow the later definition and silently disable its unfolds. Recorded inSearchlessElaboration.md.liftPoly's sum clause (sub-polynomials' embedded codes weren't weakened under the two ⊎-elim binders; never fired because no corpus polynomial had ambient-referencing codes under a ⊎).conat.nova(czero/cinf/csucc) andstream.nova(cons/iterate/map) are copattern items,hd/tlclausal ones, all in canonical distill form;streamEq/streamBisimneeded zero changes. The migrated ν goldens have byte-identical expected outputs (evaluation through stops included).Deferred (future-work list updated): observation splitting / defined observations (wants the
codataitem macro), the probe verification of elided call arguments for the pattern macro (copattern-only today), course-of-values ↔ futumorphism.