Skip to content

Commit 3f7e3b9

Browse files
Marasystemic-engineer
authored andcommitted
🔧 docs: real-world examples from prism-core
- pipeline.md: PureBeam constructors, smap usage, propagate function - context.md: why Beam uses direct matches instead of Eh - flight-recorder.md: ScalarLoss on eigenvalue decomposition, Transport holonomy - migration.md: case study — ShannonLoss to ScalarLoss, constructor methods - README.md: "See it in action" section linking to prism-core
1 parent ebc6228 commit 3f7e3b9

5 files changed

Lines changed: 159 additions & 0 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ Block macro for implicit loss accumulation — `eh! { }` will do what `Eh` does
129129
- [Flight recorder](docs/flight-recorder.md)`Failure(E, L)` as production telemetry, not stack traces
130130
- [Benchmarks](docs/benchmarks.md) — 0.65 ns per honest step, zero on the success path
131131

132+
## See it in action
133+
134+
- **[prism-core](https://github.com/systemic-engineering/prism/tree/main/core)** — spectral optics pipeline. Uses `Imperfect` as the value carrier inside `Beam`, with a custom `ScalarLoss` type for eigenvalue decomposition. 182 tests.
135+
132136
## License
133137

134138
Apache-2.0

docs/context.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,4 +209,30 @@ assert_eq!(result.ok(), Some(10));
209209
assert_eq!(result.loss().steps(), 2); // the cost of getting here survives
210210
```
211211

212+
## Why prism-core doesn't use Eh
213+
214+
prism-core's `Beam` trait IS its own composition context. The `tick` primitive destructures the beam's `Imperfect` to extract inner values for building new beam structs — a pattern that requires direct match arms rather than `Eh`'s `Result`-based extraction.
215+
216+
From [`core/src/beam.rs`](https://github.com/systemic-engineering/prism/blob/main/core/src/beam.rs):
217+
218+
```rust
219+
fn tick<T, NE>(self, next: Imperfect<T, NE, L>) -> PureBeam<Out, T, NE, L> {
220+
match self.imperfect {
221+
Imperfect::Failure(_, _) => panic!("tick on Err beam — check is_ok() first"),
222+
Imperfect::Success(old_out) => PureBeam {
223+
input: old_out,
224+
imperfect: next,
225+
},
226+
Imperfect::Partial(old_out, loss) => PureBeam {
227+
input: old_out,
228+
imperfect: propagate(loss, next),
229+
},
230+
}
231+
}
232+
```
233+
234+
The beam needs `old_out` to construct the new struct's `input` field. `Eh.eh()` would discard that inner value into a `Result<T, E>`, losing the structural information beam needs.
235+
236+
This is the right pattern: `Eh` is for code that consumes `Imperfect` values and produces a final result. `Beam` is for code that builds new `Imperfect` values from old ones while carrying structural context. Different layers, different tools.
237+
212238
[Back to README](../README.md) · [Terni-functor →](terni-functor.md)

docs/flight-recorder.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,44 @@ represent them. The type doesn't have a place to put the information.
127127

128128
Those empty cells are why this crate exists.
129129

130+
## Real-world: prism-core's ScalarLoss
131+
132+
prism-core implements its own `Loss` type — [`ScalarLoss`](https://github.com/systemic-engineering/prism/blob/main/core/src/scalar_loss.rs) — for eigenvalue decomposition. When a spectral projection zeroes out eigenvalues below a precision threshold, the magnitude of what was discarded IS the loss. This is the flight recorder in practice: the loss tells you how much signal was thrown away at each stage of a spectral pipeline.
133+
134+
From `core/src/scalar_loss.rs`:
135+
136+
```rust
137+
/// A scalar information loss measured in bits.
138+
#[derive(Clone, Debug, PartialEq, Default)]
139+
pub struct ScalarLoss(pub f64);
140+
141+
impl Loss for ScalarLoss {
142+
fn zero() -> Self { ScalarLoss(0.0) }
143+
fn total() -> Self { ScalarLoss(f64::INFINITY) }
144+
fn is_zero(&self) -> bool { self.0 == 0.0 }
145+
fn combine(self, other: Self) -> Self { ScalarLoss(self.0 + other.0) }
146+
}
147+
```
148+
149+
The `combine` is addition — scalar losses accumulate linearly. `total()` is infinity, acting as a proper absorbing element.
150+
151+
In the `Transport` trait (the bundle tower's parallel transport operation), the holonomy IS the loss — comprehension always costs something:
152+
153+
```rust
154+
impl Transport for TestBundle {
155+
type Holonomy = ScalarLoss;
156+
fn transport(&self, state: &[f64; 4]) -> Imperfect<[f64; 4], Infallible, ScalarLoss> {
157+
let compressed = [state[0], state[1], 0.0, 0.0];
158+
let loss = state[2].abs() + state[3].abs();
159+
if loss == 0.0 {
160+
Imperfect::success(compressed)
161+
} else {
162+
Imperfect::partial(compressed, ScalarLoss::new(loss))
163+
}
164+
}
165+
}
166+
```
167+
168+
The two zeroed-out dimensions are measured. The flight recorder captures exactly what was discarded, in bits. Downstream consumers see `Partial` and know: a value exists, but it cost something. The loss says how much.
169+
130170
[Back to README](../README.md) · [Loss types →](loss-types.md) · [Benchmarks →](benchmarks.md)

docs/migration.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,40 @@ assert_eq!(error, "gone");
166166
assert_eq!(loss.steps(), 7);
167167
```
168168

169+
## Case study: prism-core
170+
171+
prism-core migrated from `ShannonLoss` (a former terni type) to a local `ScalarLoss` when terni removed `ShannonLoss` upstream. The migration also replaced manual enum variant constructors with terni's constructor methods.
172+
173+
**Before:**
174+
175+
```rust
176+
// ShannonLoss from terni (removed upstream)
177+
use terni::ShannonLoss;
178+
179+
Imperfect::Success(output)
180+
Imperfect::Partial(output, ShannonLoss::new(bits))
181+
Imperfect::Failure(error, ShannonLoss::new(0.0))
182+
```
183+
184+
**After:**
185+
186+
```rust
187+
// ScalarLoss — core's own Loss impl
188+
use prism_core::ScalarLoss;
189+
190+
Imperfect::success(output)
191+
Imperfect::partial(output, ScalarLoss::new(bits))
192+
Imperfect::failure(error) // zero loss by default
193+
```
194+
195+
Key decisions in the migration:
196+
197+
1. **Own the loss type.** When the upstream type changed, core defined `ScalarLoss` locally — a 39-line file implementing the `Loss` monoid with additive `combine`. No external dependency for a domain-specific measurement.
198+
199+
2. **Constructor methods over enum variants.** `Imperfect::failure(e)` replaces `Imperfect::Failure(e, L::zero())` — the zero loss is the common case and shouldn't require spelling out. `Imperfect::failure_with_loss(e, l)` for the rare case where accumulated loss needs explicit attachment.
200+
201+
3. **Gradual.** The `Beam` trait's `tick` primitive still pattern-matches on `Imperfect` variants directly — because it needs to destructure the inner values to build new beam structs. The refactor targeted constructors and leaf code, not the pipeline primitive.
202+
203+
Total diff: 6 files changed, 27 insertions, 27 deletions. All 182 tests pass unchanged.
204+
169205
[Back to README](../README.md) · [Loss types →](loss-types.md)

docs/pipeline.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,4 +171,57 @@ assert_eq!(a, b);
171171
assert_eq!(b, c);
172172
```
173173

174+
## Real-world: prism-core
175+
176+
prism-core's `Beam` trait uses `Imperfect` as its value carrier. Every beam carries an `Imperfect<Out, E, L>` internally, and the semifunctor `smap` maps over it using `.eh()`-style closures that return `Imperfect`.
177+
178+
From [`core/src/beam.rs`](https://github.com/systemic-engineering/prism/blob/main/core/src/beam.rs) — the `smap` method on the `Beam` trait:
179+
180+
```rust
181+
fn smap<T>(
182+
self,
183+
f: impl FnOnce(&Self::Out) -> Imperfect<T, Self::Error, Self::Loss>,
184+
) -> Self::Tick<T, Self::Error> {
185+
let imp = match self.result() {
186+
Imperfect::Success(v) | Imperfect::Partial(v, _) => f(v),
187+
Imperfect::Failure(_, _) => panic!("smap on Err beam"),
188+
};
189+
self.tick(imp)
190+
}
191+
```
192+
193+
And how it's used in practice — Traversal's split operation collapses a multi-element result:
194+
195+
```rust
196+
let focused = traversal.focus(seed(vec![1, 2, 3]));
197+
let first = focused.smap(|v| Imperfect::success(v.first().cloned().unwrap_or(0)));
198+
assert_eq!(first.result().ok(), Some(&2));
199+
```
200+
201+
The `PureBeam` constructors use terni's constructor methods directly:
202+
203+
```rust
204+
pub fn ok(input: In, output: Out) -> Self {
205+
Self { input, imperfect: Imperfect::success(output) }
206+
}
207+
pub fn partial(input: In, output: Out, loss: L) -> Self {
208+
Self { input, imperfect: Imperfect::partial(output, loss) }
209+
}
210+
pub fn err(input: In, error: E) -> Self {
211+
Self { input, imperfect: Imperfect::failure(error) }
212+
}
213+
```
214+
215+
Loss propagation through the beam pipeline uses the same accumulation rules as `.eh()`:
216+
217+
```rust
218+
fn propagate<T, E, L: Loss>(loss: L, next: Imperfect<T, E, L>) -> Imperfect<T, E, L> {
219+
match next {
220+
Imperfect::Success(v) => Imperfect::partial(v, loss),
221+
Imperfect::Partial(v, loss2) => Imperfect::partial(v, loss.combine(loss2)),
222+
Imperfect::Failure(e, loss2) => Imperfect::failure_with_loss(e, loss.combine(loss2)),
223+
}
224+
}
225+
```
226+
174227
[Back to README](../README.md) · [Context →](context.md)

0 commit comments

Comments
 (0)