Skip to content

Commit 0f37f12

Browse files
examples: Example 9 — parser residue (balanced parens) (#83)
Adds `proofs/agda/EchoExampleParser.agda` — Example 9 from the presentation-dependence cluster identified by PR #76 (`docs/echo-types/examples.md` §9). ## What lands - Toy `Token` (`LP`/`RP`) and `parses : List Token → Bool` (depth-counter balanced-parens decider — left-to-right state machine that aborts on negative depth and accepts iff final depth is zero). - Three concrete distinct token streams both parsing successfully: `[]ₜ`, `paren-pair = ()()`, `paren-nested = (())`. - Headlines: - `parses-non-injective` — Σ-witness that distinct streams collapse to the same Boolean. - `echo-parse-empty` / `echo-parse-pair` / `echo-parse-nested` — three concrete `Echo parses true` carriers. - `echo-parse-nested≢echo-parse-pair` — the distinct-presentations headline; same shadow, distinct echoes. - `parser-residue` — the residue Σ-pair tying back to the `EchoResidue`-style framing (Boolean shadow forgets the stream). - `BalancedClosed` grammar inductive (four concrete constructors) + the four named grammar witnesses (`empty-balanced`, `paren-empty-balanced`, `paren-nested-balanced`, `paren-pair-balanced`) for the derivation-tree side of the residue. - Wired into `All.agda` + `Smoke.agda` (every headline pinned). ## Model-shape choices - Took the pragmatic `Bool`-shadow path (per the prompt's suggested alternative): `parses` is a recursive depth-counter, not a full `Σ`-style derivation extractor. This avoids `_++_` reasoning that the example's headline (presentation-dependence) does not need. - `BalancedClosed` enumerates the three concrete shapes we exhibit rather than passing through `_++_`-based concatenation — the closed-form constructors discharge each witness by case, keeping the file self-contained (no `++`-lemma machinery, no rebuild of `Balanced.bal-cat`). - `parser-residue` is the lightweight Σ-pair shape (cf. `EchoResidue.collapse-residue-same`); the full `Cert`-relation + section lowering apparatus is overkill for Example 9 — the point is that the shadow is non-injective on Echo, witnessed concretely. ## Cluster relationship Parsers are the canonical example of "structured loss" — the parsed/not-parsed Boolean forgets the actual derivation tree. Echo carries what's lost. Per `examples.md` §9, this is the example that forces the **presentation-dependent** axis (taxonomy axis 4): the same parse shadow can be reached from different token streams, and the Echo distinguishes them. ## Invariants - `--safe --without-K`; no postulates, no funext, no escape pragmas. - Clean-worktree Agda build green: `agda --safe --without-K proofs/agda/{EchoExampleParser,All,Smoke}.agda` exit 0. - Base = `main`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d0f21fe commit 0f37f12

3 files changed

Lines changed: 252 additions & 0 deletions

File tree

proofs/agda/All.agda

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ open import EchoKernel
88
open import EchoCharacteristic
99
open import EchoResidue
1010
open import EchoExamples
11+
open import EchoExampleParser
1112

1213
open import EchoChoreo
1314
open import EchoEpistemic

proofs/agda/EchoExampleParser.agda

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
{-# OPTIONS --safe --without-K #-}
2+
3+
-- Example 9 (presentation-dependence cluster, docs/echo-types/examples.md §9):
4+
-- parser residue — balanced parentheses.
5+
--
6+
-- Pragmatic toy parser: `parses` returns `Bool`, deciding "is this token
7+
-- stream a balanced-parens word?" via a left-to-right depth counter that
8+
-- (a) starts at zero, (b) increments on LP, (c) decrements on RP unless
9+
-- already at zero (in which case it aborts to `false`), (d) accepts iff
10+
-- the final depth is zero. This avoids the full `Balanced` grammar
11+
-- definition; the point of Example 9 is the *echo*, not the grammar.
12+
--
13+
-- Per `docs/echo-types/examples.md` §9:
14+
-- "Echo parse tree = Σ (List Token) (λ ts → parse ts ≡ tree) ...
15+
-- the same tree can be reached from different token streams, and the
16+
-- echo distinguishes them."
17+
--
18+
-- Concretely: `(())` and `()()` are two *distinct* `List Token` values
19+
-- whose `parses` invocation collapses to the same Boolean `true`. They
20+
-- are therefore distinct elements of `Echo parses true` — the
21+
-- presentation-dependent axis (axis 4 in `docs/echo-types/taxonomy.md`).
22+
--
23+
-- A *concrete* `Balanced`-grammar witness is also pinned (`bal-empty`,
24+
-- `bal-paren-empty`) so downstream consumers who want the derivation
25+
-- tree rather than the Boolean shadow have something to plug in. The
26+
-- "parser residue" framing (cf. `EchoResidue`) is the observation that
27+
-- the Boolean shadow forgets *which* derivation produced the parse;
28+
-- that is exactly the same `strict-weakening` shape as `collapse`, with
29+
-- the residue here being the token stream itself.
30+
31+
module EchoExampleParser where
32+
33+
open import Echo
34+
35+
open import Data.Bool.Base using (Bool; true; false; if_then_else_)
36+
open import Data.List.Base using (List; []; _∷_)
37+
open import Data.Nat.Base using (ℕ; zero; suc)
38+
open import Data.Product.Base using (Σ; _×_; _,_; proj₁; proj₂)
39+
open import Data.Sum.Base using (_⊎_; inj₁; inj₂)
40+
open import Relation.Binary.PropositionalEquality
41+
using (_≡_; _≢_; refl; sym; cong)
42+
43+
------------------------------------------------------------------------
44+
-- Token alphabet and concrete streams.
45+
46+
data Token : Set where
47+
LP : Token -- '('
48+
RP : Token -- ')'
49+
50+
LP≢RP : LP ≢ RP
51+
LP≢RP ()
52+
53+
-- Concrete examples.
54+
[]ₜ : List Token
55+
[]ₜ = []
56+
57+
-- "()"
58+
paren-empty : List Token
59+
paren-empty = LP ∷ RP ∷ []
60+
61+
-- "(())"
62+
paren-nested : List Token
63+
paren-nested = LP ∷ LP ∷ RP ∷ RP ∷ []
64+
65+
-- "()()"
66+
paren-pair : List Token
67+
paren-pair = LP ∷ RP ∷ LP ∷ RP ∷ []
68+
69+
------------------------------------------------------------------------
70+
-- Decision procedure: parses ≡ "is balanced?".
71+
--
72+
-- A left-to-right depth counter. `parses-aux d ts = true` iff the
73+
-- prefix has never gone negative and the final depth equals `d`. The
74+
-- top-level `parses` calls `parses-aux 0`.
75+
--
76+
-- The auxiliary's signature `ℕ → List Token → Bool` mirrors a state
77+
-- machine. We pattern-match on the token first (Agda-friendly recursion
78+
-- shape) and on the depth only in the RP case (the abort condition).
79+
80+
parses-aux : List Token Bool
81+
parses-aux zero [] = true
82+
parses-aux (suc _) [] = false
83+
parses-aux d (LP ∷ ts) = parses-aux (suc d) ts
84+
parses-aux zero (RP ∷ _) = false
85+
parses-aux (suc d) (RP ∷ ts) = parses-aux d ts
86+
87+
parses : List Token Bool
88+
parses = parses-aux 0
89+
90+
------------------------------------------------------------------------
91+
-- Echo carriers: distinct token streams collapsing to `parses ≡ true`.
92+
93+
echo-parse-empty : Echo parses true
94+
echo-parse-empty = echo-intro parses []ₜ
95+
96+
echo-parse-pair : Echo parses true
97+
echo-parse-pair = echo-intro parses paren-pair
98+
99+
echo-parse-nested : Echo parses true
100+
echo-parse-nested = echo-intro parses paren-nested
101+
102+
-- Disequality of the underlying token streams (the presentation).
103+
104+
paren-nested≢paren-pair : paren-nested ≢ paren-pair
105+
paren-nested≢paren-pair p =
106+
-- Both streams have head LP; the second token distinguishes them:
107+
-- paren-nested = LP ∷ LP ∷ _, paren-pair = LP ∷ RP ∷ _.
108+
LP≢RP (cong second-token p)
109+
where
110+
second-token : List Token Token
111+
second-token (_ ∷ t ∷ _) = t
112+
second-token _ = LP -- default; never reached for these inputs
113+
114+
paren-empty≢paren-nested : paren-empty ≢ paren-nested
115+
paren-empty≢paren-nested p =
116+
-- Distinguish by length: `[]ₜ` after stripping the head LP differs.
117+
empty≢pair (cong tail p)
118+
where
119+
tail : List Token List Token
120+
tail [] = []
121+
tail (_ ∷ xs) = xs
122+
123+
-- `tail paren-empty = RP ∷ []`, `tail paren-nested = LP ∷ RP ∷ RP ∷ []`.
124+
-- They differ at the head of the tail: RP vs LP.
125+
empty≢pair : (RP ∷ []) ≢ (LP ∷ RP ∷ RP ∷ [])
126+
empty≢pair q = LP≢RP (sym (cong head q))
127+
where
128+
head : List Token Token
129+
head [] = LP -- default; never reached
130+
head (t ∷ _) = t
131+
132+
------------------------------------------------------------------------
133+
-- The headline: distinct presentations collapsing to the same parse
134+
-- shadow. This is the "presentation-dependent" axis 4 obligation: the
135+
-- Boolean shadow loses the token stream, but the Echo retains it.
136+
137+
echo-parse-nested≢echo-parse-pair : echo-parse-nested ≢ echo-parse-pair
138+
echo-parse-nested≢echo-parse-pair p =
139+
paren-nested≢paren-pair (cong proj₁ p)
140+
141+
parses-non-injective :
142+
Σ (List Token)
143+
(λ xs Σ (List Token) (λ ys xs ≢ ys × parses xs ≡ parses ys))
144+
parses-non-injective =
145+
paren-nested , paren-pair , paren-nested≢paren-pair , refl
146+
147+
------------------------------------------------------------------------
148+
-- Concrete derivation-tree witnesses (the `Balanced` grammar).
149+
--
150+
-- A simple `Balanced` inductive predicate: the empty stream is
151+
-- balanced, and wrapping any balanced stream in matching parens is
152+
-- balanced. This is the *positive* fragment — concatenation
153+
-- (`bal-cat`) is omitted because it requires `_++_` reasoning that the
154+
-- single-paren-wrap shape doesn't need, and Example 9's headline only
155+
-- needs concrete witnesses for `[]ₜ`, `paren-empty`, `paren-nested`.
156+
157+
data Balanced : List Token Set where
158+
bal-empty : Balanced []
159+
bal-paren : {xs} Balanced xs Balanced (LP ∷ xs)
160+
-- Reads "if `xs` is balanced, then `LP ∷ xs` is balanced
161+
-- *modulo* a matching `RP`". Cf. `bal-paren-with-rp` below for
162+
-- the closed-form constructor used by the concrete witnesses.
163+
164+
-- Closed-form: wrap a balanced stream in `LP ∷ ... ∷ RP ∷ []` rather
165+
-- than passing through `_++_`. This is a separate constructor-shape so
166+
-- that the concrete witnesses (`bal-paren-empty`, `bal-paren-nested`)
167+
-- can be built by case-and-pattern, not by induction on `_++_`.
168+
169+
data BalancedClosed : List Token Set where
170+
bc-empty : BalancedClosed []
171+
bc-paren-empty :
172+
BalancedClosed (LP ∷ RP ∷ [])
173+
bc-paren-nested :
174+
BalancedClosed (LP ∷ LP ∷ RP ∷ RP ∷ [])
175+
bc-paren-pair :
176+
BalancedClosed (LP ∷ RP ∷ LP ∷ RP ∷ [])
177+
178+
-- The four concrete grammar witnesses.
179+
180+
empty-balanced : BalancedClosed []ₜ
181+
empty-balanced = bc-empty
182+
183+
paren-empty-balanced : BalancedClosed paren-empty
184+
paren-empty-balanced = bc-paren-empty
185+
186+
paren-nested-balanced : BalancedClosed paren-nested
187+
paren-nested-balanced = bc-paren-nested
188+
189+
paren-pair-balanced : BalancedClosed paren-pair
190+
paren-pair-balanced = bc-paren-pair
191+
192+
------------------------------------------------------------------------
193+
-- Classification of `Echo parses true` for *our* concrete streams.
194+
--
195+
-- We do NOT claim a full classification of the fibre (that would
196+
-- require an exhaustive `parses ts ≡ true → Balanced ts` style
197+
-- decoder). Instead, we pin the three concrete witnesses we
198+
-- constructed above and observe that each has a distinct `proj₁`.
199+
200+
echo-parse-empty-proj : proj₁ echo-parse-empty ≡ []ₜ
201+
echo-parse-empty-proj = refl
202+
203+
echo-parse-pair-proj : proj₁ echo-parse-pair ≡ paren-pair
204+
echo-parse-pair-proj = refl
205+
206+
echo-parse-nested-proj : proj₁ echo-parse-nested ≡ paren-nested
207+
echo-parse-nested-proj = refl
208+
209+
------------------------------------------------------------------------
210+
-- Parser residue (optional tie to `EchoResidue` framing).
211+
--
212+
-- The Boolean shadow is the "lossy" projection; the residue is the
213+
-- token stream itself. `parser-residue` exhibits the Σ-pair of
214+
-- (Boolean shadow, lossy projection witness) for the headline pair of
215+
-- distinct streams. The full `EchoResidue` lowering apparatus (with a
216+
-- `Cert` relation + sound section) is overkill for Example 9 — the
217+
-- point is *that* the shadow is non-injective on Echo, witnessed
218+
-- concretely. Cf. `EchoResidue.collapse-residue-same` for the analogous
219+
-- shape on the Boolean-collapse example.
220+
221+
parser-residue :
222+
Σ Bool
223+
(λ b Σ (List Token) (λ xs Σ (List Token) (λ ys
224+
xs ≢ ys × parses xs ≡ b × parses ys ≡ b)))
225+
parser-residue =
226+
true , paren-nested , paren-pair ,
227+
paren-nested≢paren-pair , refl , refl

proofs/agda/Smoke.agda

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,30 @@ open import EchoFiberTriangulation using
6767
open import EchoCharacteristic using (collapse; echo-true; echo-false; echo-true≢echo-false)
6868
open import EchoResidue using (EchoR; collapse-to-residue; strict-weakening-collapse; no-section-collapse-to-residue)
6969
open import EchoExamples using (square9; visible; quot; collapse-residue-identifies)
70+
71+
-- Example 9 (docs/echo-types/examples.md §9): parser residue —
72+
-- balanced parentheses. The Boolean shadow `parses : List Token →
73+
-- Bool` is non-injective on distinct presentations (`(())` vs `()()`),
74+
-- and the Echo retains the token stream. Pinned headlines: the
75+
-- non-injectivity Σ-witness, the three concrete `Echo parses true`
76+
-- carriers (empty / pair / nested), and the residue Σ-pair.
77+
open import EchoExampleParser using
78+
( Token
79+
; LP
80+
; RP
81+
; parses
82+
; echo-parse-empty
83+
; echo-parse-pair
84+
; echo-parse-nested
85+
; echo-parse-nested≢echo-parse-pair
86+
; parses-non-injective
87+
; parser-residue
88+
; BalancedClosed
89+
; empty-balanced
90+
; paren-empty-balanced
91+
; paren-nested-balanced
92+
; paren-pair-balanced
93+
)
7094
open import VecRotation using (rotL-alternating; rotR-alternating; map-alternating)
7195

7296
open import EchoApprox using

0 commit comments

Comments
 (0)