packages feed

circuits-0.1.0.0: other/axioms.md

# Traced Monoidal Category Axioms

**Summary:** Equational proofs for all five axioms, both tensors. For when
you need to be sure.
**Reference:** https://ncatlab.org/nlab/show/traced+monoidal+category
**See also:** `02-a-knot-recovers-fix.md` (GADT derivation), `src/Circuit/Traced.hs` (Trace instances)

The five Joyal–Street–Verity axioms proved for tensors `(,)` and `Either`
over the base arrow `(->)`. Narrative motivation lives in the arc docs (01–07).

## Preliminaries

```haskell
class Trace arr t where
  trace   :: arr (t a b) (t a c) -> arr b c   -- close the channel
  untrace :: arr b c -> arr (t a b) (t a c)   -- inject into channel
```

For `arr = (->)`:

| Tensor | `trace` | `untrace` |
|--------|---------|-----------|
| `(,)` | `\f b -> let (a, c) = f (a, b) in c` | `fmap` / `second` |
| `Either` | while-loop: `Left` continues, `Right` exits | `fmap` / `right` |

The left-channel convention puts the channel on the left: `t a b` means
channel `a`, payload `b`. So `id ⊗ f` means `first f` for `(,)` and
`left f` for `Either` — both act on the channel component, leaving the
payload untouched.

The two tensors are operationally dual:

- **`(,)`** ties a lazy knot — feedback and output co-occur in a single
  recursive binding. Proofs reduce to substituting the knot.
- **`Either`** runs a while-loop — `Left` re-enters, `Right` exits.
  Proofs compare the reachable states of two state machines.

Every axiom holds for both by the same logical structure, reached by
different computational paths.

## The Five Axioms

**1. Vanishing.** Tracing over the unit does nothing. Nested channels
trace in sequence.
```
(a) tr^I(f) = f           for f : A ⊗ I → B ⊗ I
(b) tr^{X⊗Y}(f) = tr^X(tr^Y(f))   for f : A ⊗ X ⊗ Y → B ⊗ X ⊗ Y
```

**2. Sliding.** A morphism on the channel can slide from one side of `f`
to the other inside the trace.
```
tr^X((id_B ⊗ g) ∘ f) = tr^Y(f ∘ (id_A ⊗ g))
for f : A ⊗ X → B ⊗ Y,  g : Y → X
```

**3. Tightening.** Payload morphisms pass freely through the trace.
```
tr^X((g ⊗ id_X) ∘ f ∘ (h ⊗ id_X)) = g ∘ tr^X(f) ∘ h
for h : A → B,  f : B ⊗ X → C ⊗ X,  g : C → D
```

**4. Strength.** An independent payload wire is invisible to the trace.
```
tr^X(g ⊗ f) = g ⊗ tr^X(f)
for g : A → B,  f : C ⊗ X → D ⊗ X
```

**5. Yanking.** Tracing the braiding is the identity.
```
tr^X(swap_{X,X}) = id_X
```

Yanking is the only axiom that requires a braiding. The other four hold
in any monoidal category with a trace.

## Connection to the Hyperfunction Axioms

Launchbury, Krstic & Sauerwein (2013) state six axioms for hyperfunctions.
`push` is primitive — it prepends a function on the input side of the
feedback channel. The six axioms reduce to three structural roles:

| LKS Axiom | JSV Axiom | Structural role |
|-----------|-----------|-----------------|
| 1–3 | — | Free category (`Lift` + `Compose`) |
| 4 | — | `run (lift f) = fix f` — fixed points of base arrows (Hasegawa Thm 3.1) |
| 5 | — | Push composition: `push f p . push g q = push (f . g) (p . q)`. Push distributes over composition — a homomorphism constraint on the primitive. |
| 6 | Sliding | Feedback (forces `Knot` constructor) |

LKS axioms 1–5 have no direct JSV counterpart — LKS 1–3 fall out of the free
category structure, LKS 4 (`run (lift f) = fix f`) follows from the Hasegawa fixpoint correspondence for base arrows,
and LKS 5 (push composition) constrains how push interacts with composition.
Only LKS 6 (sliding/feedback) maps cleanly to a JSV axiom.

Axioms 4 and 5 introduce no new constructors on the final side. Axiom 4
(`run (lift f) = fix f`) is the key property showing that fixed points of
base arrows are available in the final encoding; it is part of the motivation
for why the initial encoding needs the structure that `Knot` provides.
Only axiom 6 (sliding) forces a new constructor on the initial side.
See `02-a-knot-recovers-fix.md` for the derivation.

## The Mendler Case

In `Circuit`, the sliding axiom is reified as a pattern match in `reify`:

```haskell
reify :: (Category arr, Trace arr t) => Circuit arr t x y -> arr x y
reify (Lift f)             = f
reify (Compose (Knot f) g) = ↪ (f . ↩ (reify g))   -- Mendler
reify (Compose f g)        = reify f . reify g
reify (Knot k)             = ↪ k
```

The Mendler case must appear before the general `Compose` case. Without
it, `Compose (Knot f) g` falls through to `trace f . reify g` — the
naive form that closes the channel immediately, losing the feedback
structure. One pattern match separates a free traced monoidal category
from the degenerate model. For the full story see `02-a-knot-recovers-fix.md` and
`05-no-remorse-once-removed.md`.

## Proofs

### Axiom 1: Vanishing

#### (a) Unit channel is a no-op

**With `(,)` — unit `I = ()`.**

A morphism `f :: ((), a) -> ((), b)` is, under the unit isomorphism,
`f = \((), x) -> ((), g x)` for some `g :: a -> b`.

```haskell
trace f b
  = let (a, c) = f (a, b) in c       -- Trace (->) (,) definition
  = let ((), c) = f ((), b) in c     -- a :: ()
  = let ((), c) = ((), g b) in c     -- definition of f
  = g b
  = trace (Lift g) b                  -- same as a plain Lift
```

The lazy knot has nothing to tie — `()` is determined immediately.

**With `Either` — unit `I = Void`.**

`Either Void a ≅ a` since `Left v` is uninhabited. A morphism
`f :: Either Void a -> Either Void b` can only map `Right x` to
`Right (g x)`.

The while-loop enters at `Right b`. On each step, `f` returns either
`Right c` (exit) or `Left v` — but `Left v` is uninhabited, so the
loop terminates immediately at `g b`. The trace degenerates to plain
function application.

#### (b) Nested channels trace in sequence

**With `(,)`.**

`f :: ((x, y), a) -> ((x, y), b)`. Channel is `(x, y)`, payload is `a`.

**LHS** — trace over `(x, y)` in one lazy knot:
```haskell
trace f a0 = let ((x, y), b) = f ((x, y), a0) in b
```

**RHS** — trace `y` first, then `x`. Reshape `f` via associativity:

```haskell
shuffle   (y, (x, a)) = ((x, y), a)
unshuffle ((x, y), b) = (y, (x, b))
f_Y = unshuffle . f . shuffle   -- channel y, payload (x, a)
```

Inner trace ties `y`, yielding a function of `(x, a0)`. Outer trace
ties `x`. Both find the same fixed point: `((x, y), b) = f ((x, y), a0)`.
Lazy evaluation makes the nested knots equivalent to the single knot.

**With `Either`.**

Channel is `Either a b`. LHS runs one while-loop over the state
`Either a b`. RHS runs two nested loops (trace `b` first, then `a`)
via associativity `Either (Either a b) c ≅ Either a (Either b c)`.
Both reach the same exit state — nested state machines vs one.

The `(,)` and `Either` traces are operationally dual: coinductive lazy
knot vs inductive while-loop. Vanishing holds for both by the same
logical structure, reached by different computational paths.

### Axiom 2: Sliding

`f :: (x, a) -> (y, b)`, `g :: y -> x`. Channel changes from `x` to `y`;
`g` bridges them back.

**With `(,)`.**

LHS — apply `g` to the output channel, trace over `x`:
```haskell
trace (first g . f) a
  = let (x, b) = (first g . f) (x, a) in b
  = let (x, b) = let (y, b') = f (x, a) in (g y, b') in b
  = let (y, b) = f (g y, a) in b             -- substitute x = g y
```

RHS — apply `g` to the input channel, trace over `y`:
```haskell
trace (f . first g) a
  = let (y, b) = (f . first g) (y, a) in b
  = let (y, b) = f (g y, a) in b
```

Both reduce to `\a -> let (y, b) = f (g y, a) in b`. The lazy knot ties
`y` to `g y` from `f`'s output — the same fixed point either side.

**With `Either`.**

`f :: Either x a -> Either y b`, `g :: y -> x`.

LHS — apply `g` to the output channel, trace over `x`:

Enter at `Right b`. On step `s :: Either x a`, call `f s`:
- `Right c` → exit with `c`
- `Left y` → re-enter at `Left (g y)`

RHS — apply `g` to the input channel, trace over `y`:

Enter at `Right b`. On `Right a`, call `f (Right a)`. On `Left y`,
call `f (Left (g y))`.

Both implement the same state machine: each loop step feeds `g`-transformed
values back into `f`. The sequence of states fed to `f` is identical;
the exit condition depends only on `f`'s output. Same result.

### Axiom 3: Tightening

`h :: a -> b`, `f :: (x, b) -> (x, c)`, `g :: c -> d`. Channel `x`
is untouched by `h` and `g` throughout.

**With `(,)`.**

```haskell
trace (second g . f . second h) a
  = let (x, d) = (second g . f . second h) (x, a) in d
  = let (x, d) = second g (f (x, h a)) in d
  = let (x, c) = f (x, h a) in g c
  = g (let (x, c) = f (x, h a) in c)
  = g (trace f (h a))
  = (g . trace f . h) a
```

**With `Either`.**

`h :: a -> b`, `f :: Either x b -> Either x c`, `g :: c -> d`.
`second h` maps `Right a` to `Right (h a)`, leaves `Left x` alone.

The while-loop enters at `Right (h a)`. The channel `x` flows through
`Left` transitions in `f`, never touching `h` or `g`. Exit occurs
when `f` returns `Right c`, at which point `g c` is returned.

Both sides: enter at `Right (h a)`, loop on `f`'s `Left` transitions,
exit with `g c`. `h` and `g` are straight wires — they pass freely
through the trace.

### Axiom 4: Strength

`g :: a -> b` acts on payload `a` independently; `f :: (x, c) -> (x, d)`
runs with the channel `x`. They operate on disjoint parts of the tensor.

**With `(,)`.**

`g ⊗ f` acts on payload `(a, c)` with channel `x`:
```haskell
g `par` f :: (x, (a, c)) -> (x, (b, d))
g `par` f (x, (a, c)) = let (x', d) = f (x, c) in (x', (g a, d))
```

```haskell
trace (g `par` f) (a, c)
  = let (x, (b, d)) = (g `par` f) (x, (a, c)) in (b, d)
  = let (x', d) = f (x, c) in (g a, d)       -- x tied to x' by knot
  = (g a, let (x, d) = f (x, c) in d)
  = (g a, trace f c)
  = (g ⊗ trace f) (a, c)
```

`g` is invisible to the channel — the lazy knot ties only `c`/`d` via `f`.

**With `Either`.**

`g` acts on `Right a`, `f` runs the loop on `Either x c`. `g ⊗ f` maps
`Left x` to `Left x` (channel passes through), `Right (Left a)` to
`Right (Left (g a))` (g on the a-side), and delegates `Right (Right c)`
to `f`.

Both sides: `g` is a straight wire on the `a` component, `trace f`
runs its loop on the `c` component. `g` has no feedback path and
cannot affect when or how the loop exits.

### Axiom 5: Yanking

`swap :: (x, x) -> (x, x)` is the braiding.

**With `(,)`.**

```haskell
trace swap x
  = let (a, c) = swap (a, x) in c
  = let (a, c) = (x, a) in c
  = x
  = id x
```

The lazy knot resolves immediately: `a` is set to `x` from
`swap (a, x) = (x, a)`, and `c = a = x`. One substitution, no recursion.

**With `Either`.**

`swapEither :: Either a b -> Either b a` maps `Left x → Right x`,
`Right x → Left x`.

Enter at `Right x`. `swapEither (Right x) = Left x` — loop.
Re-enter at `Left x`. `swapEither (Left x) = Right x` — exit with `x`.

The while-loop runs exactly two steps. Operationally different from
`(,)` — a two-step state machine vs an immediate lazy substitution —
but the same result.

**Why braiding is required.** The other four axioms involve only the
channel and payload structure — they hold in any monoidal category with
a trace. Yanking requires a morphism `swap : X ⊗ X → X ⊗ X` that is
part of a braiding. In a non-braided monoidal category, no such
morphism is guaranteed to exist, so yanking cannot be stated.

For `(,)` and `Either` in Haskell, both are symmetric monoidal —
`swap` exists and is involutive — so yanking holds.

## Summary

| Axiom | What it says | Key mechanism |
|--------|-------------|---------------|
| Vanishing | Unit channel is a no-op; products nest | Knot with nothing to tie |
| Sliding | Channel bridge commutes past `f` | Same fixed point either side |
| Tightening | Payload morphisms pass through | Channel untouched by `h`, `g` |
| Strength | Independent payload wire is invisible | Disjoint types, no contact |
| Yanking | Tracing a swap is identity | Requires braiding |

The `(,)` and `Either` instances are operationally dual throughout:
lazy knot vs while-loop. Every axiom holds for both by the same logical
structure.

## References

- [Joyal, Street & Verity (1996)](https://doi.org/10.1017/s0305004100074338) — traced monoidal categories
- [Launchbury, Krstic & Sauerwein (2013)](https://doi.org/10.4204/eptcs.129.9) — hyperfunction axioms
- [Hasegawa (1997)](https://doi.org/10.1007/978-1-4471-0865-8_7) — Theorem 3.1: cartesian traces = fixpoints
- [Van der Ploeg & Kiselyov (2014)](https://doi.org/10.1145/2633357.2633360) — Reflection Without Remorse
- `other/02-a-knot-recovers-fix.md` — how the axioms force the GADT
- `other/05-no-remorse-once-removed.md` — Mendler case as `viewl`