catena

Go ≥ 1.27 · v1.1.0 · Apache-2.0

Pipelines that say what they cost.

Kotlin and LINQ ergonomics on top of iter.Seq, built on Go 1.27 generic methods. Every operator declares its memory class, its termination behaviour, and what it does with errors — and a conformance suite proves each one.

top := catena.FromSlice(orders).
    Filter(func(o Order) bool { return o.Paid }).
    TopNBy(10, func(o Order) int { return o.Amount })

byUser := catena.FromSlice(orders).FoldBy(
    func(o Order) UserID { return o.User },
    func(UserID) int { return 0 },
    func(sum int, o Order) int { return sum + o.Amount },
)
go get github.com/NerdMeNot/catena

Nothing is hidden

No reflect, no any in value position, no hidden goroutines, no hidden buffering. An operator that buffers says so in its doc comment, with its bound; a terminal that would hang on an infinite sequence is marked; every panic happens at construction, at the line that made the mistake.

Contracts tested, not promised

Every exported operator is registered in a conformance harness that consumes it twice, runs it over infinite sources, breaks early and checks the producer’s cleanup ran, and compares it against a hand-written loop. A completeness check fails CI for any operator nothing registers. Statement coverage is 100%, enforced.

Slow only where Go is slow

Benchmarked against the same pipelines hand-built from raw iter.Seq closures, catena meets or beats the raw mechanism on every path — the library adds nothing. What remains against a plain loop is the iterator protocol itself, and it is published rather than waved at.

The operators worth switching for

Not syntax sugar — these change the memory class of the computation.

UseInstead ofAnd it costs
FoldByGroupBy + fold per bucket2.97 MB → 1.2 KB
TopNBy(10)SortedDesc().Take(10)4.1 MB → 1 KB, 32× faster
DedupeByDistinctBy on sorted inputunbounded → O(1)

The full benchmarks, including what a pipeline costs against a hand-written loop and why.