Skip to content

Commit 3d95018

Browse files
hyperpolymathclaude
andcommitted
docs(adr): add ADR-0002 reversibility as typing case study
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a963d8c commit 3d95018

1 file changed

Lines changed: 182 additions & 0 deletions

File tree

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
<!-- SPDX-License-Identifier: PMPL-1.0-or-later -->
2+
<!-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> -->
3+
4+
# 2. Reversibility Design as a Case Study in Type-System Power
5+
6+
Date: 2026-04-12
7+
8+
## Status
9+
10+
Accepted
11+
12+
## Context
13+
14+
In April 2026, the JTV v2 reversibility system for the 007 language underwent
15+
a comprehensive design session. Five open design decisions were closed, covering:
16+
variable mutation model, linear handle behaviour, agent state rollback, the
17+
reversible–reverse pairing mechanism, and branch interaction.
18+
19+
During that session, three occasions arose where a proposed runtime mechanism,
20+
tooling addition, or syntactic obligation was found to be **entirely redundant**
21+
because the type system already expressed the same constraint. Each time,
22+
the mechanism was dropped and the type system did the work alone.
23+
24+
These three collapses are documented here as a TypeLL case study — concrete
25+
evidence of the principle that a well-designed type system eliminates
26+
mechanisms rather than multiplying them.
27+
28+
---
29+
30+
## Decision
31+
32+
Record the three type-system collapses from the 007 JTV v2 reversibility
33+
design as canonical examples of type-system power, for use in TypeLL
34+
documentation, teaching material, and type-system design rationale.
35+
36+
---
37+
38+
## The Three Collapses
39+
40+
### Collapse 1 — `ExternalHandle` type eliminates `unsend` primitive
41+
42+
**Proposed mechanism:** A new `unsend(h, v)` primitive, callable only inside
43+
`reverse { }` blocks, to undo a `send` on a linear handle. Required a new
44+
grammar production, a new typechecker rule for reversal-context tracking,
45+
and a handle state machine (`Ready → Consumed → Ready`).
46+
47+
**The type-system observation:** The handle already carries a type. If the
48+
handle's type is `ExternalHandle` (crossing an agent boundary), then
49+
attempting to `send` inside a `reversible { }` block is a **static error**
50+
the same way any other type violation is caught. No new primitive. No new
51+
runtime check. No reversal-context flag in the typechecker.
52+
53+
**What the type does:** The `ExternalHandle` annotation is the enforcement
54+
mechanism. The type tells you at the call site whether the send is
55+
reversible-safe. `unsend` becomes unreachable — its only purpose was to
56+
handle the case the type system now prevents statically.
57+
58+
**Lesson:** Before adding a primitive to handle a dangerous operation, ask
59+
whether the operation's danger is already expressible in the type system. If
60+
the type of the target already encodes the relevant constraint, the primitive
61+
is redundant.
62+
63+
---
64+
65+
### Collapse 2 — `Option<ReversalToken>` eliminates the asymmetric-branch lint
66+
67+
**Proposed mechanism:** A compiler lint warning: "this `branch` is asymmetric
68+
— some arms produce a `ReversalToken`, some do not. Did you mean to handle
69+
both cases?" Required a separate analysis pass, a warning type, and
70+
documentation of when to suppress it.
71+
72+
**The type-system observation:** At a `branch` join point where some arms
73+
produce a `ReversalToken` and some do not, path-sensitive typing automatically
74+
promotes the join type to `Option<ReversalToken<S>>`. The programmer who
75+
receives `Option<ReversalToken<S>>` in scope *already knows* the branch was
76+
asymmetric — the type told them. And `match` on `Option<ReversalToken<S>>`
77+
forces exhaustive handling of `Some(tok)` and `None`. The programmer cannot
78+
ignore or forget the asymmetric case.
79+
80+
**What the type does:** The `Option`-lifting IS the warning. It is stronger
81+
than a lint — a lint can be suppressed, ignored, or not seen in CI. An
82+
`Option` that the programmer must `match` on cannot be silently skipped.
83+
The type enforces the handling; the lint would merely have suggested it.
84+
85+
**Lesson:** When you find yourself wanting to add a lint for "this pattern
86+
is probably wrong," ask whether a type transformation at the relevant join
87+
point would make the pattern impossible to ignore rather than merely
88+
flagged. Lints are hints; types are guarantees.
89+
90+
---
91+
92+
### Collapse 3 — `ReversalToken<S>` type parameter eliminates the snapshot data structure for local bindings
93+
94+
**Proposed mechanism:** A `ReversalLog` / snapshot data structure in the
95+
evaluator — a `HashMap<String, RtValue>` capturing all local bindings at
96+
`reversible` entry, restored at `reverse`. Required a new struct, insertion
97+
on every binding within a reversible block, and a restore pass.
98+
99+
**The type-system observation:** In a purely functional language, local
100+
bindings do not need snapshotting. `let x = x + 3` inside a `reversible`
101+
block creates a *new* binding that shadows the outer `x = 5`. When the block
102+
exits, the shadow goes out of scope and the outer binding reappears. Lexical
103+
scope provides snapshot-and-restore for local bindings automatically and for
104+
free. The `HashMap` snapshot would have been capturing something the language
105+
already maintained.
106+
107+
The *only* things that genuinely require snapshotting are `@state` fields —
108+
because those escape lexical scope, persisting at agent lifetime. And these
109+
are precisely what the `ReversalToken<S>` type parameter encodes: `S` is
110+
the record type of the captured `@state` field values. The token IS the
111+
snapshot. No separate data structure. No general-purpose `HashMap`. The
112+
type parameter makes it precise, typed, and statically verified.
113+
114+
**What the type does:** `ReversalToken<{ @balance: Int }>` carries exactly
115+
what was captured, at the type level. The evaluator's restore path unpacks
116+
the token's payload and rebinds those fields — a typed operation over a
117+
known structure, not a dynamic HashMap lookup. Decisions 1 (snapshot) and 4
118+
(linear token) collapse into a single mechanism: the token IS the snapshot,
119+
for the only things that actually needed snapshotting.
120+
121+
**Lesson:** Before adding a runtime data structure to track something, ask
122+
whether the language's existing semantics already maintain it. In a purely
123+
functional language, immutability and lexical scoping do enormous amounts of
124+
tracking work invisibly. The snapshot data structure was solving a problem
125+
that didn't exist in the language as designed.
126+
127+
---
128+
129+
## The Pattern
130+
131+
All three collapses follow the same shape:
132+
133+
1. A mechanism is proposed to handle a problem.
134+
2. On closer inspection, the type system already expresses the relevant
135+
constraint, handles the relevant distinction, or maintains the relevant
136+
invariant.
137+
3. The mechanism is dropped. The type system does the work.
138+
139+
The result in each case: **fewer moving parts, stronger guarantees, smaller
140+
implementation surface.** A lint that can be suppressed becomes a type that
141+
cannot be ignored. A primitive with a dangerous footgun becomes a type error.
142+
A runtime data structure becomes a type parameter.
143+
144+
This is the direction a well-typed language should run: each addition to the
145+
type system should eliminate at least one mechanism elsewhere. If you are
146+
adding to the type system and also adding runtime machinery for the same
147+
concern, the design is not finished.
148+
149+
---
150+
151+
## Consequences
152+
153+
### Positive
154+
155+
- Three concrete, worked examples of the "type system as mechanism eliminator"
156+
principle, grounded in a real language design session.
157+
- Usable directly in TypeLL documentation, teaching material on linear types,
158+
and type-system design rationale documents.
159+
- Demonstrates that the principle applies across different type-system features:
160+
type annotations on values (`ExternalHandle`), type algebra at join points
161+
(`Option`-lifting), and type parameters encoding captured state
162+
(`ReversalToken<S>`).
163+
- The three collapses are believed to constitute a novel contribution to the
164+
reversible computation and type theory literature, publishable as part of
165+
the 007 / JTV v2 language papers.
166+
167+
### Negative
168+
169+
- The collapses only reveal themselves during design — they require the
170+
designer to resist the momentum toward adding a mechanism and instead ask
171+
"what does the type system already know?" This is a discipline, not a
172+
technique that can be automated.
173+
174+
### Neutral
175+
176+
- The underlying reversibility design decisions are documented fully in:
177+
- `007/docs/session-2026-04-12-jtv-v2-reversibility-design.adoc`
178+
- `julia-the-viper/docs/language/DESIGN-JTV-V2-REVERSIBILITY.md`
179+
- `nextgen-languages/docs/design/jtv-007-reversibility-fork.adoc`
180+
- TypeLL does not implement the 007 reversibility system; this ADR records
181+
the design patterns as transferable knowledge, not as an implementation
182+
commitment.

0 commit comments

Comments
 (0)