Overview

The h8io.stages.base package sits between the core abstractions and concrete stage implementations. The core Stage interface — implement apply and skip, supply an Evolution with evolve and dispose — is intentionally minimal. That minimalism is useful at the boundary, but it leaves every implementor to repeat the same structural boilerplate: a stage that never changes needs the same skip-returns-this pattern; a stage that wraps another needs to forward dispose; a stage backed by a pure function always writes the same Yield.Some(f(in), Status.Success, this).

The base package captures those patterns as composable, reusable traits. Concrete stages mix in what they need and implement only the logic that is genuinely specific to them.

The package also hosts the terminal side of the model: the execute extension method — the reference one-shot driver that runs a stage once, disposes the evolution, and returns a plain result — together with its result type Outcome.

The Immutability Hierarchy

The most common class of stages is those that do not change between runs: a filter, a transformer, a pure mapping. None of these need to carry state from one invocation to the next; their evolution always returns the same stage. The base package captures this with a small hierarchy, each layer sealing more boilerplate.

Stagnation is the root of it. It is an Evolution with a self-type of Stage: mix it into a stage and the stage becomes its own evolution, with evolve sealed to return this for every status and dispose defaulting to a no-op — still open, so a stage with resources can release them.

SAMStage adds the other half of the pattern: skip is sealed to return this as well, so the stage supplies the same continuation whether it ran or was bypassed. What is left abstract is apply alone — hence the name.

From there the hierarchy branches by how much of the Yield the implementor still has to build. These three are siblings, not layers on one another: each seals apply in its own way, and each is a SAMStage.

Trait Implement Yields
StaticStage process(in): StaticYield[O, E] a value or nothing, any status
FruitfulStaticStage process(in): (O, Status[E]) always a value, any status
Fn f(in): O always a value, always Status.Success

StaticYield is a Yield without the evolution field, which the sealed apply supplies as this. The two fruitful traits also mix in Fruitful, which narrows the return type of apply to Yield.Some — so "always a value" is a guarantee the compiler tracks, not just a convention.

The Rest of the Package

Two more base traits describe shapes rather than immutability: Alterator for a stage wrapping one inner stage (with the UnaryOperator, Decorator, Alteration and Decoration aliases built around it), and BinaryOperator for a stage combining two of them on a shared input.

SafeStage splits apply into a happy path and a recovery path; Projection is the shape shared by the stages that read one value out of a container; ConstEvolution, together with the toEvolution extension, lifts a stage into an evolution that always returns it.