terni 0.4.0

Ternary error handling: Success, Partial with measured loss, Failure. Because computation is not binary.
Documentation
# terni

> I wanna thank Brene Brown for her work.

Ternary error handling for Rust. Because computation is not binary.

[![crates.io](https://img.shields.io/crates/v/terni.svg)](https://crates.io/crates/terni)
[![docs.rs](https://docs.rs/terni/badge.svg)](https://docs.rs/terni)
[![license](https://img.shields.io/crates/l/terni.svg)](https://github.com/systemic-engineering/prism/blob/main/imperfect/LICENSE)

## `eh`

The type. Three states instead of two.

```rust
use terni::{Imperfect, ConvergenceLoss};

let perfect: Imperfect<u32, String, ConvergenceLoss> = Imperfect::Success(42);
let lossy = Imperfect::Partial(42, ConvergenceLoss::new(3));
let failed: Imperfect<u32, String, ConvergenceLoss> = Imperfect::Failure("gone".into(), ConvergenceLoss::new(0));

assert!(perfect.is_ok());
assert!(lossy.is_partial());
assert!(failed.is_err());
```

[`Loss`](https://docs.rs/terni/latest/terni/trait.Loss.html) measures what didn't survive. It's a monoid: `zero()` identity, `combine` associative, `total()` absorbing.

Three loss types ship with the crate:
- **`ConvergenceLoss`** — distance to crystal. Combine: max.
- **`ApertureLoss`** — dark dimensions. Combine: union.
- **`RoutingLoss`** — decision entropy. Combine: max entropy, min gap.

[Loss types in depth →](docs/loss-types.md)

## `eh!`

The bind. Chain operations, accumulate loss.

```rust
use terni::{Imperfect, ConvergenceLoss};

let result = Imperfect::<i32, String, ConvergenceLoss>::Success(1)
    .eh(|x| Imperfect::Success(x * 2))
    .eh(|x| Imperfect::Partial(x + 1, ConvergenceLoss::new(3)));

assert_eq!(result.ok(), Some(3));
assert!(result.is_partial());
```

For explicit context with loss accumulation:

```rust
use terni::{Imperfect, Eh, ConvergenceLoss};

let mut eh = Eh::new();
let a = eh.eh(Imperfect::<i32, String, ConvergenceLoss>::Success(1)).unwrap();
let b = eh.eh(Imperfect::<_, String, _>::Partial(a + 1, ConvergenceLoss::new(5))).unwrap();
let result: Imperfect<i32, String, ConvergenceLoss> = eh.finish(b);

assert!(result.is_partial());
```

`.imp()` and `.tri()` are aliases for `.eh()` — same bind, different name. Use whichever reads best in your code.

[Pipeline guide →](docs/pipeline.md) · [Context guide →](docs/context.md)

## `eh?`

The question. Coming in a future release.

Block macro for implicit loss accumulation — `eh! { }` will do what `Eh` does without the boilerplate.

## More

- [Loss types]docs/loss-types.md — the `Loss` trait, shipped types, custom implementations
- [Pipeline]docs/pipeline.md`.eh()` bind in depth, loss accumulation rules
- [Context]docs/context.md`Eh` struct, mixing `Imperfect` and `Result`
- [Terni-functor]docs/terni-functor.md — the math behind `.eh()`
- [Migration]docs/migration.md — moving from `Result<T, E>` to `Imperfect<T, E, L>`

## License

Apache-2.0