Repository navigation
The largest release since the API froze: the places a chain had to stop
are closed, and three packages now sit beside the core. Nothing existing
changes behaviour.
This reverses three earlier decisions, on purpose. The original design
ruled out parallel operators ("a different library"), capped the
operator count, and kept everything in one package. Concurrency is still
a different package — the core starts no goroutine it did not start
before — but it is no longer someone else's problem.
Added
-
Seq.Thenapplies a function to the sequence, so a function that
takes one sits inside the chain instead of wrapping it. Every
one-argument package function fits, with its types inferred:
s.Then(catena.Distinct).Then(catena.Sorted).Take(10). It is also how
the stages incatena/parallelattach. -
Ordered, chainable grouping.
Seq.FoldedByandcatena.GroupedBy
areFoldByandGroupBythat yield aSeq2of pairs, keys in the
order they were first seen, instead of returning a map: the result is
deterministic and the chain keeps going.GroupedByis a package
function becauseSeq2[K, []T]leads back toSeq[[]T], the same
instantiation cycle that makesChunkedone. -
Joins that drop nothing.
Seq.LeftJoinBykeeps a left element with
no match and tellscombineso;Seq.GroupJoinByyields one result per
left element with all of its matches together. -
Five ideas from
lo, in catena's shape.Seq.FirstOrandSeq.LastOrreturn a fallback when the sequence is
empty, so a lookup can sit inside an expression.IsSorted,Seq.IsSortedByandSeq.IsSortedWithcheck order in one
pass with no buffer, stopping at the first pair out of order.DuplicatesandSeq.DuplicatesByyield each repeated value once, at
its second occurrence.Seq.Sampledrawsnelements uniformly at random in O(n) memory by
reservoir sampling — 803 KB → 456 B against collecting 100k elements
to shuffle them, and 4.4× faster. A seeded generator makes the choice
repeatable.
-
Try.UntilErrstops at the first error, asCollectdoes, but as
a sequence: the chain continues with the fullSeqAPI and the error
is left where you asked, to be checked once the pipeline has run. It is
the policy exitTrywas missing —Ignorekeeps going andMust
panics — and it is how a fallible source feeds a stage that takes a
plain sequence, the concurrent ones included. -
catena/parallel: concurrency. Six operators, each a stage forThen:Map,MapUnorderedandForEachrun a callback up tonat a
time. The source stays on the consumer's goroutine; only the callback
runs elsewhere. A failed call is aTryelement, soCollect,
CollectAllandIgnorechoose the policy as they always have.Bufferreads ahead,ChunkedWithinbatches by size or by time, and
Mergeinterleaves several sequences by arrival; these run their
sources on goroutines of their own.
Every one joins the goroutines it started before its iteration
returns — on a normal end, an early break, a cancelled context and a
panic alike. That is tested undertesting/synctest, in virtual time,
against callbacks that are deliberately slow to stop. -
catena/source: producers that ship.Linesfor the lines of a
reader, of any length;Paginatefor a paginated API, fetching a page
only once the one before it has been yielded in full; and
sqlsource.Rowsfor a query, in a package of its own so the others do
not linkdatabase/sql. Each opens its resource inside the iteration
and releases it on every exit — the pattern the error-handling guide
taught, now code. -
catena/lint: a linter, as a separate module so the library's own
still depends on nothing.catenalintruns as ago vettool and
reports the pipelines that are correct but wasteful —
Sorted(s).Take(n)whereTopNholdsnelements,
Sorted(s).First()whereMinholds one,Distinct(Sorted(s))where
Dedupeholds none, aGroupByused only to count — and the lazy
sequence that is built and never consumed. It offers a fix only where
the replacement returns the same values of the same type. It carries
the library's version, under its own prefix:lint/v1.5.0. -
Guides for concurrency and
linting, and
a lookup from lo, Kotlin and LINQ names to
catena's, every entry of which a test checks against the library.
Reference pages for the two new packages, andconcurrencyand
sourcesexamples.
Documentation
- The
v1.4.xseries is archived at/1-4/, per the rule that a minor
series is snapshotted when the next minor ships.
Changed
-
Terminals and grouping operators are faster, some of them much.
Every operator consumed its source through an adapter that made a nil
sequence behave as an empty one, and that made every source an opaque
function value. The call now goes through a method small enough to
inline, so a source built in the calling function is one the compiler
can see through. Measured interleaved against 1.4, over 100k elements:
SumandContains71µs → 24µs, the speed of a hand-written loop;
FoldBy16% faster;GroupByandJoinBy10–13%; single-stageMap
5%. Multi-stage pipelines are unchanged. No API or behaviour change. -
The published hand-loop baselines were too slow, and are corrected.
They were written inline in the benchmark body, where nothing may be
optimised away, which made a plain loop look up to four times slower
than it is. Measured as functions of their own, the hand-written
4-stage pipeline takes 25µs, not 39µs — so a 4-stage catena pipeline is
about 10× a hand loop on trivial arithmetic, not 6.6× as the README and
the performance guide said. The library did not get slower; the earlier
comparison flattered it. -
The 100% coverage gate now covers
parallel,sourceandsqlsourceas
well as the core package, and the linter's module is gated to 100%
separately. CI also runs the linter over the examples, which must come
back clean. -
The
selectionexample's leaderboard sorted every player to show four.
The linter found it; it usesTopNBynow, with identical output. -
The
joinexample rolls up withFoldedByin place of collecting map
keys and sorting them, and shows the two new joins. -
The operator reference is generated for the sub-packages too, and the
README's "exactly one goroutine" is now stated of the core package,
which is where it is true.