Documentation
¶
Overview ¶
Package catena is a lazy, fully-typed sequence library for Go 1.27+, built on iter.Seq and generic methods.
The four types:
- Seq[T]: lazy, single-pass by contract, possibly infinite.
- Seq2[K, V]: the stdlib pairing currency; a bridge back to Seq, not a peer.
- Try[T]: Seq2[T, error]; error policy is chosen by the consumer (Collect stops at the first error, CollectAll drains, Ignore skips).
- List[T]: eager []T with the mirrored operation set.
Finding an operator: most are methods, but operations that constrain the element type (Distinct, Sorted, Sum, Max, Contains, Union, …) are package functions, because a method on Seq[T any] may require nothing of T. So are Chunked, ChunkedBy and Windowed, which return Seq[[]T] — a method doing that is an instantiation cycle — and the Flatten family, which constrains the receiver's shape. Spelling is catena.Distinct(s), not s.Distinct(); the chain continues normally afterwards, as catena.Distinct(s).Filter(f). If the compiler reports "has no field or method Distinct", this is why.
Contracts every caller should know:
- Treat every Seq as single-pass. Re-iterability depends entirely on the producer (see the table on Seq); operators preserve it either way. Once() is a development guard.
- Nil sequences are empty: every method and package function accepts a nil receiver or argument and treats it as an empty sequence.
- Invalid construction arguments (negative counts, zero step) panic at construction time with a "catena: " prefixed message. Nil callbacks are not defended and panic at first use.
- Operators marked as buffering state their bound; terminals marked as full-drain hang on infinite input.
- Map-returning terminals return Go maps: iteration order is undefined.
- When a Try element carries a non-nil error, do not read the value.
- Not safe for concurrent use; ToChan is the fan-out mechanism.
- comparable-constrained functions panic at runtime if T is an interface type holding a non-comparable value (Go 1.20 semantics).
Example (LazyAcquisition) ¶
package main
// Runnable examples: the resource-owning producer patterns the library
// documents rather than ships — lazy acquisition and the reader adapter —
// plus the operators that justify the library.
import (
"bufio"
"database/sql"
"fmt"
"strings"
"github.com/NerdMeNot/catena"
)
// Lines adapts an io.Reader into a Try of its lines — the reader-adapter
// pattern the library documents rather than ships.
// The reader is wrapped lazily inside the closure: a sequence that is
// never consumed never reads, and early termination stops the scan.
func Lines(open func() (*strings.Reader, error)) catena.Try[string] {
return func(yield func(string, error) bool) {
r, err := open()
if err != nil {
yield("", err)
return
}
sc := bufio.NewScanner(r)
for sc.Scan() {
if !yield(sc.Text(), nil) {
return
}
}
if err := sc.Err(); err != nil {
yield("", err)
}
}
}
func main() {
opened := 0
logs := Lines(func() (*strings.Reader, error) {
opened++
return strings.NewReader("GET /a\nPOST /b\nGET /c\nGET /d"), nil
})
// Building the pipeline opens nothing.
gets := logs.Ignore().
Filter(func(l string) bool { return strings.HasPrefix(l, "GET ") }).
Take(2)
fmt.Println("opened after building:", opened)
fmt.Println(gets.Collect())
fmt.Println("opened after consuming:", opened)
}
// Rows is the producer pattern for *sql.Rows: lazy acquisition, so
// an unconsumed Try holds no resource, and defer runs on early
// termination through any number of stages. bake_test.go exercises it
// against a real database/sql driver.
func Rows[T any](open func() (*sql.Rows, error), scan func(*sql.Rows) (T, error)) catena.Try[T] {
return func(yield func(T, error) bool) {
var zero T
rows, err := open()
if err != nil {
yield(zero, err)
return
}
defer rows.Close()
for rows.Next() {
v, err := scan(rows)
if err != nil {
v = zero
}
if !yield(v, err) {
return
}
}
if err := rows.Err(); err != nil {
yield(zero, err)
}
}
}
Output: opened after building: 0 [GET /a GET /c] opened after consuming: 1
Index ¶
- Constants
- func AssociateWith[T comparable, V any](s Seq[T], f func(T) V) map[T]V
- func Average[T Numeric](s Seq[T]) (float64, bool)
- func BottomN[T cmp.Ordered](s Seq[T], n int) []T
- func CollectMap[K comparable, V any](s Seq2[K, V]) map[K]V
- func Contains[T comparable](s Seq[T], v T) bool
- func Equal[T comparable](a, b Seq[T]) bool
- func IndexOf[T comparable](s Seq[T], v T) int
- func Join(s Seq[string], sep string) string
- func Max[T cmp.Ordered](s Seq[T]) (T, bool)
- func Min[T cmp.Ordered](s Seq[T]) (T, bool)
- func MinMax[T cmp.Ordered](s Seq[T]) (min, max T, ok bool)
- func Product[T Numeric](s Seq[T]) T
- func Self[T any](v T) T
- func Sum[T Numeric](s Seq[T]) T
- func Tally[T comparable](s Seq[T]) map[T]int
- func ToKeySet[T comparable](s Seq[T]) map[T]struct{}
- func TopN[T cmp.Ordered](s Seq[T], n int) []T
- func Unzip[K, V any](s Seq2[K, V]) ([]K, []V)
- type Integer
- type List
- func (l List[T]) All(pred func(T) bool) bool
- func (l List[T]) Any(pred func(T) bool) bool
- func (l List[T]) Append(vals ...T) List[T]
- func (l List[T]) AsSeq() Seq[T]
- func (l List[T]) Associate[K comparable, V any](f func(T) (K, V)) map[K]V
- func (l List[T]) At(i int) T
- func (l List[T]) AverageOf[N Numeric](sel func(T) N) (float64, bool)
- func (l List[T]) BottomNBy[K cmp.Ordered](n int, sel func(T) K) []T
- func (l List[T]) Clone() List[T]
- func (l List[T]) Collect() []T
- func (l List[T]) Concat(others ...Seq[T]) List[T]
- func (l List[T]) Count() int
- func (l List[T]) CountWhere(pred func(T) bool) int
- func (l List[T]) DedupeBy[K comparable](sel func(T) K) List[T]
- func (l List[T]) DistinctBy[K comparable](sel func(T) K) List[T]
- func (l List[T]) DistinctWith(eq func(a, b T) bool) List[T]
- func (l List[T]) Drain()
- func (l List[T]) Drop(n int) List[T]
- func (l List[T]) DropLast(n int) List[T]
- func (l List[T]) DropWhile(pred func(T) bool) List[T]
- func (l List[T]) ElementAt(i int) (T, bool)
- func (l List[T]) Filter(pred func(T) bool) List[T]
- func (l List[T]) FilterErr(pred func(T) (bool, error)) Try[T]
- func (l List[T]) FilterIndexed(pred func(int, T) bool) List[T]
- func (l List[T]) FilterMap[U any](f func(T) (U, bool)) List[U]
- func (l List[T]) FilterNot(pred func(T) bool) List[T]
- func (l List[T]) Find(pred func(T) bool) (T, bool)
- func (l List[T]) FindIndex(pred func(T) bool) int
- func (l List[T]) FindLast(pred func(T) bool) (T, bool)
- func (l List[T]) FindMap[U any](f func(T) (U, bool)) (U, bool)
- func (l List[T]) First() (T, bool)
- func (l List[T]) FlatMap[U any](f func(T) Seq[U]) List[U]
- func (l List[T]) FlatMapSlice[U any](f func(T) []U) List[U]
- func (l List[T]) Fold[A any](init A, f func(A, T) A) A
- func (l List[T]) FoldBy[K comparable, A any](key func(T) K, init func(K) A, f func(A, T) A) map[K]A
- func (l List[T]) FoldErr[A any](init A, f func(A, T) (A, error)) (A, error)
- func (l List[T]) FoldIndexed[A any](init A, f func(int, A, T) A) A
- func (l List[T]) FoldRight[A any](init A, f func(T, A) A) A
- func (l List[T]) FoldWhile[A any](init A, f func(A, T) (A, bool)) A
- func (l List[T]) ForEach(f func(T))
- func (l List[T]) ForEachErr(f func(T) error) error
- func (l List[T]) ForEachIndexed(f func(int, T))
- func (l List[T]) Get(i int) (T, bool)
- func (l List[T]) GroupBy[K comparable](sel func(T) K) map[K][]T
- func (l List[T]) IfEmpty(defaults ...T) List[T]
- func (l List[T]) IndexBy[K comparable](sel func(T) K) map[K]T
- func (l List[T]) Intersperse(sep T) List[T]
- func (l List[T]) IsEmpty() bool
- func (l List[T]) JoinBy[U any, K comparable, R any](other Seq[U], leftKey func(T) K, rightKey func(U) K, combine func(T, U) R) List[R]
- func (l List[T]) JoinToString(sep string, sel func(T) string) string
- func (l List[T]) Last() (T, bool)
- func (l List[T]) Len() int
- func (l List[T]) Map[U any](f func(T) U) List[U]
- func (l List[T]) MapErr[U any](f func(T) (U, error)) Try[U]
- func (l List[T]) MapIndexed[U any](f func(int, T) U) List[U]
- func (l List[T]) MaxBy[K cmp.Ordered](sel func(T) K) (T, bool)
- func (l List[T]) MaxOf[K cmp.Ordered](sel func(T) K) (K, bool)
- func (l List[T]) MaxWith(cmp func(a, b T) int) (T, bool)
- func (l List[T]) MinBy[K cmp.Ordered](sel func(T) K) (T, bool)
- func (l List[T]) MinMaxOf[K cmp.Ordered](sel func(T) K) (min, max K, ok bool)
- func (l List[T]) MinOf[K cmp.Ordered](sel func(T) K) (K, bool)
- func (l List[T]) MinWith(cmp func(a, b T) int) (T, bool)
- func (l List[T]) None(pred func(T) bool) bool
- func (l List[T]) OnEach(f func(T)) List[T]
- func (l List[T]) Partition(pred func(T) bool) (yes, no []T)
- func (l List[T]) Prepend(vals ...T) List[T]
- func (l List[T]) ProductOf[N Numeric](sel func(T) N) N
- func (l List[T]) Reduce(f func(T, T) T) (T, bool)
- func (l List[T]) Reversed() List[T]
- func (l List[T]) Scan[A any](init A, f func(A, T) A) List[A]
- func (l List[T]) Single() (T, bool)
- func (l List[T]) Slice(i, j int) List[T]
- func (l List[T]) SortedBy[K cmp.Ordered](sel func(T) K) List[T]
- func (l List[T]) SortedByDesc[K cmp.Ordered](sel func(T) K) List[T]
- func (l List[T]) SortedWith(cmp func(a, b T) int) List[T]
- func (l List[T]) Step(n int) List[T]
- func (l List[T]) SumOf[N Numeric](sel func(T) N) N
- func (l List[T]) Take(n int) List[T]
- func (l List[T]) TakeLast(n int) List[T]
- func (l List[T]) TakeWhile(pred func(T) bool) List[T]
- func (l List[T]) TallyBy[K comparable](sel func(T) K) map[K]int
- func (l List[T]) ToList() List[T]
- func (l List[T]) TopNBy[K cmp.Ordered](n int, sel func(T) K) []T
- func (l List[T]) WithIndex() Seq2[int, T]
- func (l List[T]) ZipWithNext() Seq2[T, T]
- type Numeric
- type Seq
- func Chain[T any](seqs ...Seq[T]) Seq[T]
- func Chunked[T any](s Seq[T], n int) Seq[[]T]
- func ChunkedBy[T any, K comparable](s Seq[T], sel func(T) K) Seq[[]T]
- func Cycle[T any](s Seq[T]) Seq[T]
- func Dedupe[T comparable](s Seq[T]) Seq[T]
- func Distinct[T comparable](s Seq[T]) Seq[T]
- func Empty[T any]() Seq[T]
- func Except[T comparable](a, b Seq[T]) Seq[T]
- func Flatten[T any](s Seq[Seq[T]]) Seq[T]
- func FlattenSlices[T any](s Seq[[]T]) Seq[T]
- func From[T any](seq func(func(T) bool)) Seq[T]
- func FromChan[T any](ctx context.Context, ch <-chan T) Seq[T]
- func FromSlice[T any](s []T) Seq[T]
- func Generate[T any](seed T, next func(T) T) Seq[T]
- func GenerateWhile[T any](seed T, next func(T) (T, bool)) Seq[T]
- func Intersect[T comparable](a, b Seq[T]) Seq[T]
- func NonZero[T comparable](s Seq[T]) Seq[T]
- func Of[T any](vals ...T) Seq[T]
- func Once1[T any](v T) Seq[T]
- func Range[I Integer](start, stop, step I) Seq[I]
- func Repeat[T any](v T) Seq[T]
- func RepeatN[T any](v T, n int) Seq[T]
- func Sorted[T cmp.Ordered](s Seq[T]) Seq[T]
- func SortedDesc[T cmp.Ordered](s Seq[T]) Seq[T]
- func Union[T comparable](a, b Seq[T]) Seq[T]
- func Windowed[T any](s Seq[T], size, step int) Seq[[]T]
- func (s Seq[T]) All(pred func(T) bool) bool
- func (s Seq[T]) Any(pred func(T) bool) bool
- func (s Seq[T]) Append(vals ...T) Seq[T]
- func (s Seq[T]) Associate[K comparable, V any](f func(T) (K, V)) map[K]V
- func (s Seq[T]) AverageOf[N Numeric](sel func(T) N) (float64, bool)
- func (s Seq[T]) BottomNBy[K cmp.Ordered](n int, sel func(T) K) []T
- func (s Seq[T]) Collect() []T
- func (s Seq[T]) Concat(others ...Seq[T]) Seq[T]
- func (s Seq[T]) Count() int
- func (s Seq[T]) CountWhere(pred func(T) bool) int
- func (s Seq[T]) DedupeBy[K comparable](sel func(T) K) Seq[T]
- func (s Seq[T]) DistinctBy[K comparable](sel func(T) K) Seq[T]
- func (s Seq[T]) DistinctWith(eq func(a, b T) bool) Seq[T]
- func (s Seq[T]) Drain()
- func (s Seq[T]) Drop(n int) Seq[T]
- func (s Seq[T]) DropLast(n int) Seq[T]
- func (s Seq[T]) DropWhile(pred func(T) bool) Seq[T]
- func (s Seq[T]) ElementAt(i int) (T, bool)
- func (s Seq[T]) Filter(pred func(T) bool) Seq[T]
- func (s Seq[T]) FilterErr(pred func(T) (bool, error)) Try[T]
- func (s Seq[T]) FilterIndexed(pred func(int, T) bool) Seq[T]
- func (s Seq[T]) FilterMap[U any](f func(T) (U, bool)) Seq[U]
- func (s Seq[T]) FilterNot(pred func(T) bool) Seq[T]
- func (s Seq[T]) Find(pred func(T) bool) (T, bool)
- func (s Seq[T]) FindIndex(pred func(T) bool) int
- func (s Seq[T]) FindLast(pred func(T) bool) (T, bool)
- func (s Seq[T]) FindMap[U any](f func(T) (U, bool)) (U, bool)
- func (s Seq[T]) First() (T, bool)
- func (s Seq[T]) FlatMap[U any](f func(T) Seq[U]) Seq[U]
- func (s Seq[T]) FlatMapSlice[U any](f func(T) []U) Seq[U]
- func (s Seq[T]) Fold[A any](init A, f func(A, T) A) A
- func (s Seq[T]) FoldBy[K comparable, A any](key func(T) K, init func(K) A, f func(A, T) A) map[K]A
- func (s Seq[T]) FoldErr[A any](init A, f func(A, T) (A, error)) (A, error)
- func (s Seq[T]) FoldIndexed[A any](init A, f func(int, A, T) A) A
- func (s Seq[T]) FoldWhile[A any](init A, f func(A, T) (A, bool)) A
- func (s Seq[T]) ForEach(f func(T))
- func (s Seq[T]) ForEachErr(f func(T) error) error
- func (s Seq[T]) ForEachIndexed(f func(int, T))
- func (s Seq[T]) GroupBy[K comparable](sel func(T) K) map[K][]T
- func (s Seq[T]) IfEmpty(defaults ...T) Seq[T]
- func (s Seq[T]) IndexBy[K comparable](sel func(T) K) map[K]T
- func (s Seq[T]) Intersperse(sep T) Seq[T]
- func (s Seq[T]) IsEmpty() bool
- func (s Seq[T]) JoinBy[U any, K comparable, R any](other Seq[U], leftKey func(T) K, rightKey func(U) K, combine func(T, U) R) Seq[R]
- func (s Seq[T]) JoinToString(sep string, sel func(T) string) string
- func (s Seq[T]) Last() (T, bool)
- func (s Seq[T]) Map[U any](f func(T) U) Seq[U]
- func (s Seq[T]) MapErr[U any](f func(T) (U, error)) Try[U]
- func (s Seq[T]) MapIndexed[U any](f func(int, T) U) Seq[U]
- func (s Seq[T]) MaxBy[K cmp.Ordered](sel func(T) K) (T, bool)
- func (s Seq[T]) MaxOf[K cmp.Ordered](sel func(T) K) (K, bool)
- func (s Seq[T]) MaxWith(cmp func(a, b T) int) (T, bool)
- func (s Seq[T]) MinBy[K cmp.Ordered](sel func(T) K) (T, bool)
- func (s Seq[T]) MinMaxOf[K cmp.Ordered](sel func(T) K) (min, max K, ok bool)
- func (s Seq[T]) MinOf[K cmp.Ordered](sel func(T) K) (K, bool)
- func (s Seq[T]) MinWith(cmp func(a, b T) int) (T, bool)
- func (s Seq[T]) None(pred func(T) bool) bool
- func (s Seq[T]) OnEach(f func(T)) Seq[T]
- func (s Seq[T]) Once() Seq[T]
- func (s Seq[T]) Partition(pred func(T) bool) (yes, no []T)
- func (s Seq[T]) Prepend(vals ...T) Seq[T]
- func (s Seq[T]) ProductOf[N Numeric](sel func(T) N) N
- func (s Seq[T]) Pull() (next func() (T, bool), stop func())
- func (s Seq[T]) Reduce(f func(T, T) T) (T, bool)
- func (s Seq[T]) Reversed() Seq[T]
- func (s Seq[T]) Scan[A any](init A, f func(A, T) A) Seq[A]
- func (s Seq[T]) Seq() iter.Seq[T]
- func (s Seq[T]) Single() (T, bool)
- func (s Seq[T]) SortedBy[K cmp.Ordered](sel func(T) K) Seq[T]
- func (s Seq[T]) SortedByDesc[K cmp.Ordered](sel func(T) K) Seq[T]
- func (s Seq[T]) SortedWith(cmp func(a, b T) int) Seq[T]
- func (s Seq[T]) Step(n int) Seq[T]
- func (s Seq[T]) SumOf[N Numeric](sel func(T) N) N
- func (s Seq[T]) Take(n int) Seq[T]
- func (s Seq[T]) TakeLast(n int) Seq[T]
- func (s Seq[T]) TakeWhile(pred func(T) bool) Seq[T]
- func (s Seq[T]) TallyBy[K comparable](sel func(T) K) map[K]int
- func (s Seq[T]) ToChan(ctx context.Context) <-chan T
- func (s Seq[T]) ToList() List[T]
- func (s Seq[T]) TopNBy[K cmp.Ordered](n int, sel func(T) K) []T
- func (s Seq[T]) UntilDone(ctx context.Context) Try[T]
- func (s Seq[T]) WithIndex() Seq2[int, T]
- func (s Seq[T]) Zip[U any](other Seq[U]) Seq2[T, U]
- func (s Seq[T]) ZipWithNext() Seq2[T, T]
- type Seq2
- func (s Seq2[K, V]) All(pred func(K, V) bool) bool
- func (s Seq2[K, V]) Any(pred func(K, V) bool) bool
- func (s Seq2[K, V]) Count() int
- func (s Seq2[K, V]) Drop(n int) Seq2[K, V]
- func (s Seq2[K, V]) Filter(pred func(K, V) bool) Seq2[K, V]
- func (s Seq2[K, V]) FilterNot(pred func(K, V) bool) Seq2[K, V]
- func (s Seq2[K, V]) First() (K, V, bool)
- func (s Seq2[K, V]) Fold[A any](init A, f func(A, K, V) A) A
- func (s Seq2[K, V]) ForEach(f func(K, V))
- func (s Seq2[K, V]) Keys() Seq[K]
- func (s Seq2[K, V]) Map[K2, V2 any](f func(K, V) (K2, V2)) Seq2[K2, V2]
- func (s Seq2[K, V]) MapTo[U any](f func(K, V) U) Seq[U]
- func (s Seq2[K, V]) MapValues[V2 any](f func(K, V) V2) Seq2[K, V2]
- func (s Seq2[K, V]) Pull() (next func() (K, V, bool), stop func())
- func (s Seq2[K, V]) Seq2() iter.Seq2[K, V]
- func (s Seq2[K, V]) Swap() Seq2[V, K]
- func (s Seq2[K, V]) Take(n int) Seq2[K, V]
- func (s Seq2[K, V]) Values() Seq[V]
- type Try
- func (t Try[T]) Collect() ([]T, error)
- func (t Try[T]) CollectAll() ([]T, []error)
- func (t Try[T]) Count() (int, error)
- func (t Try[T]) Drop(n int) Try[T]
- func (t Try[T]) Err() error
- func (t Try[T]) Errs() Seq[error]
- func (t Try[T]) Filter(pred func(T) bool) Try[T]
- func (t Try[T]) FilterErr(pred func(T) (bool, error)) Try[T]
- func (t Try[T]) FlatMap[U any](f func(T) Try[U]) Try[U]
- func (t Try[T]) Fold[A any](init A, f func(A, T) A) (A, error)
- func (t Try[T]) ForEach(f func(T) error) error
- func (t Try[T]) Ignore() Seq[T]
- func (t Try[T]) Map[U any](f func(T) U) Try[U]
- func (t Try[T]) MapErr[U any](f func(T) (U, error)) Try[U]
- func (t Try[T]) Must() Seq[T]
- func (t Try[T]) OnEach(f func(T)) Try[T]
- func (t Try[T]) OnError(f func(error)) Try[T]
- func (t Try[T]) Pull() (next func() (T, error, bool), stop func())
- func (t Try[T]) Recover(f func(error) (T, bool)) Try[T]
- func (t Try[T]) Seq2() iter.Seq2[T, error]
- func (t Try[T]) Take(n int) Try[T]
- func (t Try[T]) TakeWhile(pred func(T) bool) Try[T]
- func (t Try[T]) UntilDone(ctx context.Context) Try[T]
- func (t Try[T]) WrapErr(f func(error) error) Try[T]
Examples ¶
- Package (LazyAcquisition)
- AssociateWith
- Average
- BottomN
- Chain
- Chunked
- ChunkedBy
- CollectMap
- Contains
- Cycle
- Dedupe
- Distinct
- Empty
- Empty2
- EmptyTry
- Equal
- Except
- Flatten
- FlattenSlices
- From
- From2
- FromChan
- FromErrs
- FromMap
- FromSlice
- Generate
- GenerateWhile
- IndexOf
- Intersect
- Join
- List.Append
- List.AsSeq
- List.At
- List.Clone
- List.FoldRight
- List.Get
- List.Len
- List.Slice
- Max
- Min
- MinMax
- NonZero
- Of
- Once1
- Product
- Range
- Repeat
- RepeatN
- Self
- Seq2.All
- Seq2.Any
- Seq2.Count
- Seq2.Drop
- Seq2.Filter
- Seq2.FilterNot
- Seq2.First
- Seq2.Fold
- Seq2.ForEach
- Seq2.Keys
- Seq2.Map
- Seq2.MapTo
- Seq2.MapValues
- Seq2.Pull
- Seq2.Seq2
- Seq2.Swap
- Seq2.Take
- Seq2.Values
- Seq.All
- Seq.Any
- Seq.Append
- Seq.Associate
- Seq.AverageOf
- Seq.BottomNBy
- Seq.Collect
- Seq.Concat
- Seq.Count
- Seq.CountWhere
- Seq.DedupeBy
- Seq.DistinctBy
- Seq.DistinctWith
- Seq.Drain
- Seq.Drop
- Seq.DropLast
- Seq.DropWhile
- Seq.ElementAt
- Seq.Filter
- Seq.FilterErr
- Seq.FilterIndexed
- Seq.FilterMap
- Seq.FilterNot
- Seq.Find
- Seq.FindIndex
- Seq.FindLast
- Seq.FindMap
- Seq.First
- Seq.FlatMap
- Seq.FlatMapSlice
- Seq.Fold
- Seq.FoldBy
- Seq.FoldErr
- Seq.FoldIndexed
- Seq.FoldWhile
- Seq.ForEach
- Seq.ForEachErr
- Seq.ForEachIndexed
- Seq.GroupBy
- Seq.IfEmpty
- Seq.IndexBy
- Seq.Intersperse
- Seq.IsEmpty
- Seq.JoinBy
- Seq.JoinToString
- Seq.Last
- Seq.Map
- Seq.MapErr
- Seq.MapIndexed
- Seq.MaxBy
- Seq.MaxOf
- Seq.MaxWith
- Seq.MinBy
- Seq.MinMaxOf
- Seq.MinOf
- Seq.MinWith
- Seq.None
- Seq.OnEach
- Seq.Once
- Seq.Partition
- Seq.Prepend
- Seq.ProductOf
- Seq.Pull
- Seq.Reduce
- Seq.Reversed
- Seq.Scan
- Seq.Seq
- Seq.Single
- Seq.SortedBy
- Seq.SortedByDesc
- Seq.SortedWith
- Seq.Step
- Seq.SumOf
- Seq.Take
- Seq.TakeLast
- Seq.TakeWhile
- Seq.TallyBy
- Seq.ToChan
- Seq.ToList
- Seq.TopNBy
- Seq.UntilDone
- Seq.WithIndex
- Seq.Zip
- Seq.ZipWithNext
- Sorted
- SortedDesc
- Sum
- Tally
- ToKeySet
- TopN
- Try.Collect
- Try.CollectAll
- Try.Count
- Try.Drop
- Try.Err
- Try.Errs
- Try.Filter
- Try.FilterErr
- Try.FlatMap
- Try.Fold
- Try.ForEach
- Try.Ignore
- Try.Map
- Try.MapErr
- Try.Must
- Try.OnEach
- Try.OnError
- Try.Pull
- Try.Recover
- Try.Seq2
- Try.Take
- Try.TakeWhile
- Try.UntilDone
- Try.WrapErr
- Union
- Unzip
- Windowed
Constants ¶
const Version = "1.4.0"
Version is the library's version, kept in lockstep with release tags: the release workflow refuses to publish a tag that disagrees with it.
Variables ¶
This section is empty.
Functions ¶
func AssociateWith ¶
func AssociateWith[T comparable, V any](s Seq[T], f func(T) V) map[T]V
AssociateWith maps each element to f(element); on duplicate elements the last value wins. ⚠ Full drain; map iteration order is undefined.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
m := catena.AssociateWith(catena.Of("go", "rust"), func(s string) int { return len(s) })
fmt.Println(m["go"], m["rust"])
}
Output: 2 4
func Average ¶
Average returns the mean, accumulating in float64; (0, false) on empty input. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
avg, ok := catena.Average(catena.Of(2.0, 4.0))
fmt.Println(avg, ok)
}
Output: 3 true
func BottomN ¶ added in v1.2.0
BottomN returns the n smallest elements, sorted ascending; equal elements retain encounter order. Memory is O(n) — the streaming alternative to Sorted(s).Take(n). ⚠ Full drain. Panics if n is negative.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.BottomN(catena.Of(5, 1, 9, 3), 2))
}
Output: [1 3]
func CollectMap ¶
func CollectMap[K comparable, V any](s Seq2[K, V]) map[K]V
CollectMap drains a pair sequence into a map; on duplicate keys the last value wins. ⚠ Full drain; map iteration order is undefined.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// To a map. On a duplicate key the last value wins, as with plain
// map assignment.
fmt.Println(catena.CollectMap(catena.Of("a", "b").WithIndex()))
}
Output: map[0:a 1:b]
func Contains ¶
func Contains[T comparable](s Seq[T], v T) bool
Contains reports whether v occurs in s; stops at the first match.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Contains(catena.Of(1, 2, 3), 2))
}
Output: true
func Equal ¶
func Equal[T comparable](a, b Seq[T]) bool
Equal reports whether a and b yield the same elements in the same order. Consumes both sequences up to and including the first difference — fully when they are equal. b is consumed through iter.Pull (its cleanup always runs).
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Equal(catena.Of(1, 2), catena.Of(1, 2)))
fmt.Println(catena.Equal(catena.Of(1, 2), catena.Of(1)))
}
Output: true false
func IndexOf ¶
func IndexOf[T comparable](s Seq[T], v T) int
IndexOf returns the index of the first occurrence of v; -1 if none.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.IndexOf(catena.Of("a", "b"), "b"))
fmt.Println(catena.IndexOf(catena.Of("a"), "z"))
}
Output: 1 -1
func Join ¶
Join concatenates a string sequence with sep between elements. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Join(catena.Of("a", "b", "c"), "-"))
}
Output: a-b-c
func Max ¶
Max returns the largest element; NaN orders below everything (cmp.Compare). ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
v, ok := catena.Max(catena.Of(3, 9, 1))
fmt.Println(v, ok)
}
Output: 9 true
func Min ¶
Min returns the smallest element; NaN orders below everything, so a NaN in the input is the minimum. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
v, ok := catena.Min(catena.Of(3, 9, 1))
fmt.Println(v, ok)
}
Output: 1 true
func MinMax ¶
MinMax returns the smallest and largest elements in one pass. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
lo, hi, ok := catena.MinMax(catena.Of(3, 9, 1))
fmt.Println(lo, hi, ok)
}
Output: 1 9 true
func Product ¶
Product multiplies the elements. Empty input yields 1, the multiplicative identity. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Product(catena.Of(2, 3, 4)))
}
Output: 24
func Self ¶
func Self[T any](v T) T
Self is the identity selector: catena.Flatten(s) is s.FlatMap(Self).
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// The identity selector, for the -By operators when the element is
// already the key.
fmt.Println(catena.Of(3, 1, 2).TallyBy(catena.Self[int])[3])
}
Output: 1
func Sum ¶
Sum adds the elements; integer overflow wraps like +. Empty input sums to 0. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Integer overflow wraps, exactly as + does.
fmt.Println(catena.Sum(catena.Of(1, 2, 3)))
}
Output: 6
func Tally ¶
func Tally[T comparable](s Seq[T]) map[T]int
Tally counts occurrences per value. ⚠ Full drain; map iteration order is undefined.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Tally(catena.Of("a", "b", "a"))["a"])
}
Output: 2
func ToKeySet ¶ added in v1.1.0
func ToKeySet[T comparable](s Seq[T]) map[T]struct{}
ToKeySet drains the sequence into a membership map, whose keys are the distinct elements. Named for what it returns rather than for the set it stands in for: ToSet is reserved for a real set type, should Go ever grow one. ⚠ Full drain; map iteration order is undefined.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
set := catena.ToKeySet(catena.Of(1, 2, 2))
_, has := set[2]
fmt.Println(len(set), has)
}
Output: 2 true
func TopN ¶
TopN returns the n largest elements, sorted descending; equal elements retain encounter order. Memory is O(n) — the streaming alternative to SortedDesc(s).Take(n). ⚠ Full drain. Panics if n is negative.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.TopN(catena.Of(5, 1, 9, 3), 2))
}
Output: [9 5]
func Unzip ¶
Unzip drains a pair sequence into its two sides; nil slices for empty input. ⚠ Full drain; buffers both sides.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Both sides in one pass — the safe way to get keys and values from
// a single-use Seq2.
idx, vals := catena.Unzip(catena.Of("a", "b").WithIndex())
fmt.Println(idx, vals)
}
Output: [0 1] [a b]
Types ¶
type Integer ¶
type Integer interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
Integer is the constraint for Range.
type List ¶
type List[T any] []T
List is an eager []T with the mirrored method set. []T(l) unwraps it with no copy, and AsSeq() views it lazily for free.
The mirror covers Seq's methods, not the constraint-bound package functions: those take a Seq, so reach them through AsSeq and come back with ToList, as catena.Sorted(l.AsSeq()).ToList(). Concat likewise takes a Seq, so it is l1.Concat(l2.AsSeq()).
Four mirrored operators cross back to lazy, because their Seq counterparts return a lazy type: WithIndex and ZipWithNext return Seq2, MapErr and FilterErr return Try.
func (List[T]) Append ¶
Append returns a new List with vals appended. Unlike the built-in append, the result ALWAYS has a fresh backing array and never aliases l — consistent with every other List transform. Callers who want the built-in's amortized behavior can use it directly: List is a []T.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Unlike the builtin, the result never aliases the receiver — every
// List transform returns fresh backing memory.
base := make(catena.List[int], 2, 10)
grown := base.Append(9)
grown[0] = 99
fmt.Println(base, grown)
}
Output: [0 0] [99 0 9]
func (List[T]) AsSeq ¶
AsSeq returns a lazy, re-iterable view of the list. The list is not copied; mutations are visible to later iterations.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Crossing to lazy is explicit, and free — it is a view, not a copy.
l := catena.List[int]{1, 2, 3, 4}
first, _ := l.AsSeq().Find(func(n int) bool { return n > 2 })
fmt.Println(first)
}
Output: 3
func (List[T]) Associate ¶
func (l List[T]) Associate[K comparable, V any](f func(T) (K, V)) map[K]V
Associate mirrors Seq.Associate eagerly.
func (List[T]) At ¶
At returns the element at index i, panicking exactly like l[i] on an out-of-range index. O(1).
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Panics exactly like l[i] — it IS the index expression.
l := catena.List[string]{"a", "b"}
fmt.Println(l.At(1))
}
Output: b
func (List[T]) Clone ¶
Clone returns a shallow copy with a fresh backing array.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
l := catena.List[int]{1, 2}
c := l.Clone()
c[0] = 99
fmt.Println(l, c)
}
Output: [1 2] [99 2]
func (List[T]) Collect ¶
func (l List[T]) Collect() []T
Collect mirrors Seq.Collect eagerly, implemented directly over the backing slice rather than through the lazy pipeline.
func (List[T]) Count ¶
Count mirrors Seq.Count eagerly, implemented directly over the backing slice rather than through the lazy pipeline.
func (List[T]) CountWhere ¶
CountWhere mirrors Seq.CountWhere eagerly.
func (List[T]) DedupeBy ¶
func (l List[T]) DedupeBy[K comparable](sel func(T) K) List[T]
DedupeBy mirrors Seq.DedupeBy eagerly.
func (List[T]) DistinctBy ¶
func (l List[T]) DistinctBy[K comparable](sel func(T) K) List[T]
DistinctBy mirrors Seq.DistinctBy eagerly.
func (List[T]) DistinctWith ¶
DistinctWith mirrors Seq.DistinctWith eagerly.
func (List[T]) ElementAt ¶
ElementAt mirrors Seq.ElementAt eagerly, implemented directly over the backing slice rather than through the lazy pipeline.
func (List[T]) FilterErr ¶
FilterErr mirrors Seq.FilterErr. The list is consumed eagerly, but the result is a lazy Try — one of the four List operators that cross back to lazy (see the List type).
func (List[T]) FilterIndexed ¶
FilterIndexed mirrors Seq.FilterIndexed eagerly.
func (List[T]) First ¶
First mirrors Seq.First eagerly, implemented directly over the backing slice rather than through the lazy pipeline.
func (List[T]) FlatMapSlice ¶
FlatMapSlice mirrors Seq.FlatMapSlice eagerly.
func (List[T]) FoldBy ¶
func (l List[T]) FoldBy[K comparable, A any](key func(T) K, init func(K) A, f func(A, T) A) map[K]A
FoldBy mirrors Seq.FoldBy eagerly.
func (List[T]) FoldIndexed ¶
FoldIndexed mirrors Seq.FoldIndexed eagerly.
func (List[T]) FoldRight ¶
FoldRight reduces right to left. It exists only on List: a right fold needs the whole sequence in memory, which a List already is.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Exists only on List: a right fold needs the whole sequence, which
// a List already is.
l := catena.List[string]{"a", "b", "c"}
fmt.Println(l.FoldRight("|", func(s, acc string) string { return "(" + s + acc + ")" }))
}
Output: (a(b(c|)))
func (List[T]) ForEachErr ¶
ForEachErr mirrors Seq.ForEachErr eagerly.
func (List[T]) ForEachIndexed ¶
ForEachIndexed mirrors Seq.ForEachIndexed eagerly.
func (List[T]) Get ¶
Get returns the element at index i; (zero, false) for a negative or out-of-range index. O(1).
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// The comma-ok form, for when the index may be out of range.
l := catena.List[string]{"a"}
v, ok := l.Get(0)
_, bad := l.Get(9)
fmt.Println(v, ok, bad)
}
Output: a true false
func (List[T]) GroupBy ¶
func (l List[T]) GroupBy[K comparable](sel func(T) K) map[K][]T
GroupBy mirrors Seq.GroupBy eagerly.
func (List[T]) IndexBy ¶
func (l List[T]) IndexBy[K comparable](sel func(T) K) map[K]T
IndexBy mirrors Seq.IndexBy eagerly.
func (List[T]) Intersperse ¶
Intersperse mirrors Seq.Intersperse eagerly.
func (List[T]) IsEmpty ¶
IsEmpty mirrors Seq.IsEmpty eagerly, implemented directly over the backing slice rather than through the lazy pipeline.
func (List[T]) JoinBy ¶
func (l List[T]) JoinBy[U any, K comparable, R any](other Seq[U], leftKey func(T) K, rightKey func(U) K, combine func(T, U) R) List[R]
JoinBy mirrors Seq.JoinBy eagerly.
func (List[T]) JoinToString ¶
JoinToString mirrors Seq.JoinToString eagerly.
func (List[T]) Last ¶
Last mirrors Seq.Last eagerly, implemented directly over the backing slice rather than through the lazy pipeline.
func (List[T]) Len ¶
Len returns the number of elements. O(1).
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
l := catena.List[int]{3, 1, 2}
fmt.Println(l.Len())
}
Output: 3
func (List[T]) Map ¶
Map mirrors Seq.Map eagerly, implemented directly over the backing slice rather than through the lazy pipeline.
func (List[T]) MapErr ¶
MapErr mirrors Seq.MapErr. The list is consumed eagerly, but the result is a lazy Try — one of the four List operators that cross back to lazy (see the List type).
func (List[T]) MapIndexed ¶
MapIndexed mirrors Seq.MapIndexed eagerly, implemented directly over the backing slice rather than through the lazy pipeline.
func (List[T]) Reversed ¶
Reversed mirrors Seq.Reversed eagerly, implemented directly over the backing slice rather than through the lazy pipeline.
func (List[T]) Slice ¶
Slice returns l[i:j] as a List, panicking exactly like the slice expression — and, exactly like it, sharing the backing array.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// It is the slice expression, aliasing included.
l := catena.List[int]{1, 2, 3, 4}
fmt.Println(l.Slice(1, 3))
}
Output: [2 3]
func (List[T]) SortedByDesc ¶
SortedByDesc mirrors Seq.SortedByDesc eagerly.
func (List[T]) SortedWith ¶
SortedWith mirrors Seq.SortedWith eagerly.
func (List[T]) TallyBy ¶
func (l List[T]) TallyBy[K comparable](sel func(T) K) map[K]int
TallyBy mirrors Seq.TallyBy eagerly.
func (List[T]) ToList ¶
ToList mirrors Seq.ToList eagerly, implemented directly over the backing slice rather than through the lazy pipeline.
func (List[T]) WithIndex ¶
WithIndex mirrors Seq.WithIndex. The list is consumed eagerly, but the result is a lazy Seq2 — one of the four List operators that cross back to lazy (see the List type).
func (List[T]) ZipWithNext ¶
ZipWithNext mirrors Seq.ZipWithNext. The list is consumed eagerly, but the result is a lazy Seq2 — one of the four List operators that cross back to lazy (see the List type).
type Numeric ¶
type Numeric interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64
}
Numeric is the constraint for arithmetic aggregations (Sum, Product, Average). Complex types are deliberately excluded: they are unordered and half the aggregation surface would be meaningless for them.
type Seq ¶
Seq is a lazy sequence: iter.Seq with methods. Range over it directly.
A Seq is a function, so consuming it twice runs its producer twice. Whether that works is a property of the producer alone — operators never change it. Every operator builds its state inside the returned closure, so a chain is re-iterable exactly when its root producer is, and the conformance suite re-iterates every operator to prove it. Once() is the single deliberate exception.
Re-iterable Of, FromSlice, FromMap, Empty, Empty2, EmptyTry, Once1,
Repeat, RepeatN, Range, Generate and GenerateWhile (iff
next is pure)
Single-use FromChan, and From, From2, FromErrs when the underlying
source is (anything over I/O)
Cycle re-iterable iff its source's first pass is
Consuming a single-use sequence a second time yields nothing rather than failing: wrap it in Once() during development to turn that into a panic. Printing a Seq shows a function pointer — print s.Collect() instead.
func Chain ¶
Chain yields each sequence's elements in order. It is the package-function form of Seq.Concat, for when you hold a slice of sequences and no receiver.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// The variadic form, for when the sequences are in a slice already.
fmt.Println(catena.Chain(catena.Of(1), catena.Of(2), catena.Of(3)).Collect())
}
Output: [1 2 3]
func Chunked ¶
Chunked yields consecutive chunks of n elements; the last chunk may be partial. Every chunk is a fresh slice (safe to retain). Panics if n <= 0.
Chunked is a package function, not a method: a method on Seq[T] returning Seq[[]T] is an instantiation cycle (each Seq[T] would require Seq[[]T], which would require Seq[[][]T], forever).
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Fixed-size batches; the last one may be short. Every chunk is a
// fresh slice, so keeping one is safe.
for c := range catena.Chunked(catena.Range(1, 8, 1), 3).Seq() {
fmt.Println(c)
}
}
Output: [1 2 3] [4 5 6] [7]
func ChunkedBy ¶
func ChunkedBy[T any, K comparable](s Seq[T], sel func(T) K) Seq[[]T]
ChunkedBy yields runs of consecutive elements sharing a key: a chunk closes when the key changes. Memory is bounded by the longest run. Every chunk is a fresh slice. A package function for the same instantiation- cycle reason as Chunked.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// A chunk per run of equal keys — the streaming answer to grouping
// already-sorted input, in memory bounded by the longest run.
for c := range catena.ChunkedBy(catena.Of(1, 1, 2, 3, 3), catena.Self[int]).Seq() {
fmt.Println(c)
}
}
Output: [1 1] [2] [3 3]
func Cycle ¶
Cycle yields s over and over, forever. The first pass is buffered and replayed (⚠ unbounded memory in len(s)), so Cycle is re-iterable iff that first pass of s is. An empty s yields an empty Cycle — it terminates rather than spinning.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Infinite — except over an empty source, which terminates rather
// than spinning.
fmt.Println(catena.Cycle(catena.Of("a", "b")).Take(5).Collect())
fmt.Println(catena.Cycle(catena.Empty[string]()).Collect())
}
Output: [a b a b a] []
func Dedupe ¶
func Dedupe[T comparable](s Seq[T]) Seq[T]
Dedupe yields elements that differ from their predecessor — consecutive duplicates only, O(1) memory.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Consecutive duplicates only. On sorted input it equals Distinct at
// a fraction of the cost; on unsorted input the two differ.
fmt.Println(catena.Dedupe(catena.Of(3, 3, 1, 1, 3)).Collect())
}
Output: [3 1 3]
func Distinct ¶
func Distinct[T comparable](s Seq[T]) Seq[T]
Distinct yields elements not seen before; the first occurrence wins.
Distinct is a package function, not a method, and so are the others in this file: they constrain the element type (comparable, cmp.Ordered, Numeric), and a method on Seq[T any] may require nothing of T — or, for the Flatten family, they constrain the receiver's shape. Every comparable-constrained function panics at runtime if T is an interface type holding a non-comparable value. The chain continues normally after them: catena.Distinct(s).Filter(f).
On input already sorted by the compared value, Dedupe is the O(1)-memory equivalent. ⚠ Retains one entry per distinct value — unbounded.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Distinct constrains the element type, so it is a package function
// rather than a method — a method on Seq[T any] may require nothing
// of T. First occurrence wins, and encounter order is preserved.
fmt.Println(catena.Distinct(catena.Of(3, 1, 3, 2, 1)).Collect())
}
Output: [3 1 2]
func Empty ¶
Empty returns the empty Seq. Re-iterable.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Empty[int]().Collect(), catena.Empty[int]().Count())
}
Output: [] 0
func Except ¶
func Except[T comparable](a, b Seq[T]) Seq[T]
Except yields the distinct elements of a that do not occur in b, in a's encounter order. ⚠ Buffers all of b before a is consumed, plus a seen- set of a's distinct values.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Except(catena.Of(1, 2, 3), catena.Of(2)).Collect())
}
Output: [1 3]
func Flatten ¶
Flatten yields every element of every inner sequence, in order.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
inner := catena.Of(catena.Of(1, 2), catena.Of(3))
fmt.Println(catena.Flatten(inner).Collect())
}
Output: [1 2 3]
func FlattenSlices ¶
FlattenSlices yields every element of every slice, in order.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.FlattenSlices(catena.Of([]int{1, 2}, []int{3})).Collect())
}
Output: [1 2 3]
func From ¶
From adapts any push-function sequence — iter.Seq, catena.Seq, or a third-party alias — with no conversion at the call site. Re-iterability depends on the source.
Example ¶
package main
import (
"fmt"
"slices"
"github.com/NerdMeNot/catena"
)
func main() {
// Takes the literal function type, so any iterator adapts without a
// conversion at the call site — including the standard library's.
fmt.Println(catena.From(slices.Values([]string{"a", "b"})).Collect())
}
Output: [a b]
func FromChan ¶
FromChan yields values received from ch until ch is closed or ctx is done. Single-use. No goroutine is started; a sequence that is never consumed never receives.
Example ¶
package main
import (
"context"
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
ch := make(chan int, 3)
for i := 1; i <= 3; i++ {
ch <- i
}
close(ch)
// Single-use, and it starts no goroutine: a sequence that is never
// consumed never receives.
fmt.Println(catena.FromChan(context.Background(), ch).Collect())
}
Output: [1 2 3]
func FromSlice ¶
FromSlice returns a re-iterable Seq over s. The slice is not copied; mutations to it are visible to later iterations.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// The slice is not copied: later mutations are visible to later
// iterations, which is what makes this free.
xs := []int{1, 2, 3}
s := catena.FromSlice(xs)
xs[0] = 99
fmt.Println(s.Collect())
}
Output: [99 2 3]
func Generate ¶
Generate yields seed, then next(seed), then next(next(seed)), forever. Infinite. Re-iterable iff next is pure.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// The seed is yielded first, then next applied repeatedly. Infinite.
fmt.Println(catena.Generate(1, func(n int) int { return n * 3 }).
Take(4).
Collect())
}
Output: [1 3 9 27]
func GenerateWhile ¶
GenerateWhile yields seed unconditionally, then successive next values until next reports false. Re-iterable iff next is pure.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// The seed is yielded unconditionally; a value produced alongside
// ok=false is not.
fmt.Println(catena.GenerateWhile(1, func(n int) (int, bool) {
return n * 3, n < 9
}).Collect())
}
Output: [1 3 9]
func Intersect ¶
func Intersect[T comparable](a, b Seq[T]) Seq[T]
Intersect yields the distinct elements of a that occur in b, in a's encounter order. ⚠ Buffers all of b before a is consumed, plus a seen- set of a's distinct values.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Intersect(catena.Of(1, 2, 3), catena.Of(3, 1)).Collect())
}
Output: [1 3]
func NonZero ¶
func NonZero[T comparable](s Seq[T]) Seq[T]
NonZero yields the elements that are not the zero value of T.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Drops the zero value of T — empty strings here, but equally 0,
// nil pointers, or a zero struct.
fmt.Println(catena.NonZero(catena.Of("go", "", "rust", "")).Collect())
}
Output: [go rust]
func Of ¶
Of returns a re-iterable Seq over the given values.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of("go", "rust", "zig").Collect())
}
Output: [go rust zig]
func Once1 ¶
Once1 returns a re-iterable Seq of exactly one value. (Once, without the suffix, is the single-use guard method on Seq.)
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Once1, not Once: Once is the single-use guard method on Seq.
fmt.Println(catena.Once1("only").Collect())
}
Output: [only]
func Range ¶
Range yields start, start+step, ... while the value is before stop (exclusive). Re-iterable. step == 0 panics at construction; a sign mismatch between step and the start→stop direction yields an empty sequence. Termination is overflow-guarded: a step past the type's edge stops rather than wrapping. Unsigned types cannot step downward.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Half-open, like a slice expression. A sign mismatch between step
// and direction yields nothing rather than panicking, so a computed
// step is safe.
fmt.Println(catena.Range(0, 10, 3).Collect())
fmt.Println(catena.Range(3, 0, -1).Collect())
fmt.Println(catena.Range(0, 10, -1).Collect())
}
Output: [0 3 6 9] [3 2 1] []
func Repeat ¶
Repeat yields v forever. Re-iterable. Infinite: pair with Take or a conditional terminal.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Infinite, so it must be bounded by something downstream.
fmt.Println(catena.Repeat("ha").Take(3).Collect())
}
Output: [ha ha ha]
func RepeatN ¶
RepeatN yields v exactly n times. Re-iterable. Panics if n is negative.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.RepeatN(0, 4).Collect())
}
Output: [0 0 0 0]
func Sorted ¶
Sorted yields the elements in ascending order, stably. NaN sorts first. ⚠ Buffers the entire input.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Sorted(catena.Of(3, 1, 2)).Collect())
}
Output: [1 2 3]
func SortedDesc ¶
SortedDesc yields the elements in descending order, stably. ⚠ Buffers the entire input.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.SortedDesc(catena.Of(3, 1, 2)).Collect())
}
Output: [3 2 1]
func Union ¶
func Union[T comparable](a, b Seq[T]) Seq[T]
Union yields the distinct elements of a, then the distinct elements of b not in a — set semantics, encounter order. ⚠ Retains one entry per distinct value — unbounded.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Set semantics: the result is deduplicated, in encounter order,
// left operand first.
fmt.Println(catena.Union(catena.Of(1, 2, 2), catena.Of(3, 1)).Collect())
}
Output: [1 2 3]
func Windowed ¶
Windowed yields sliding windows of exactly size elements, advancing by step; trailing elements that do not fill a window are dropped. step > size is valid and samples with gaps. ⚠ Buffers size elements. Every window is a fresh slice. Panics if size or step is <= 0. A package function for the same instantiation-cycle reason as Chunked.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Overlapping windows: size 3 advancing by 1. Trailing elements that
// cannot fill a window are dropped.
for w := range catena.Windowed(catena.Range(1, 6, 1), 3, 1).Seq() {
fmt.Println(w)
}
}
Output: [1 2 3] [2 3 4] [3 4 5]
func (Seq[T]) All ¶
All reports whether pred admits every element; stops at the first counterexample. Vacuously true on empty input.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Vacuously true on an empty sequence.
fmt.Println(catena.Of(2, 4).All(func(n int) bool { return n%2 == 0 }))
fmt.Println(catena.Empty[int]().All(func(n int) bool { return false }))
}
Output: true true
func (Seq[T]) Any ¶
Any reports whether pred admits any element; stops at the first match.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Stops at the first match, so it terminates on an infinite source.
fmt.Println(catena.Generate(1, func(n int) int { return n + 1 }).
Any(func(n int) bool { return n > 100 }))
}
Output: true
func (Seq[T]) Append ¶
Append yields s, then the given values.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of("a").Append("b", "c").Collect())
}
Output: [a b c]
func (Seq[T]) Associate ¶
func (s Seq[T]) Associate[K comparable, V any](f func(T) (K, V)) map[K]V
Associate builds a map from f's key/value pairs; on duplicate keys the last pair wins. ⚠ Full drain; map iteration order is undefined.
Example ¶
package main
import (
"fmt"
"strings"
"github.com/NerdMeNot/catena"
)
func main() {
m := catena.Of(1, 2).Associate(func(n int) (int, string) {
return n, strings.Repeat("*", n)
})
fmt.Println(m[1], m[2])
}
Output: * **
func (Seq[T]) AverageOf ¶
AverageOf returns the mean of the selected values, accumulating in float64 (naive summation — precision for large integer inputs is not guaranteed); (0, false) on empty input. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Accumulates in float64, and reports false on empty rather than
// dividing by zero.
avg, ok := catena.Of(1, 2, 4).AverageOf(catena.Self[int])
_, empty := catena.Empty[int]().AverageOf(catena.Self[int])
fmt.Printf("%.2f %v %v\n", avg, ok, empty)
}
Output: 2.33 true false
func (Seq[T]) BottomNBy ¶
BottomNBy returns the n elements with the smallest keys, sorted ascending by key; equal keys retain encounter order. Memory is O(n) — the streaming alternative to SortedBy().Take(n). catena.BottomN(s, n) is the no-selector form. ⚠ Full drain. Panics if n is negative.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of(5, 1, 9, 3, 7).BottomNBy(2, catena.Self[int]))
}
Output: [1 3]
func (Seq[T]) Collect ¶
func (s Seq[T]) Collect() []T
Collect drains the sequence into a slice; nil for empty. ToList is the same drain returning a List, which carries the eager operator set. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// nil for an empty sequence, matching slices.Collect.
fmt.Println(catena.Of(1, 2).Collect(), catena.Empty[int]().Collect() == nil)
}
Output: [1 2] true
func (Seq[T]) Concat ¶
Concat yields s, then each of the others in order.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of(1, 2).Concat(catena.Of(3), catena.Of(4, 5)).Collect())
}
Output: [1 2 3 4 5]
func (Seq[T]) Count ¶
Count returns the number of elements. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of("a", "b", "c").Count())
}
Output: 3
func (Seq[T]) CountWhere ¶
CountWhere returns the number of elements pred admits. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Fused filter and count: one stage rather than two.
fmt.Println(catena.Range(1, 11, 1).CountWhere(func(n int) bool { return n%3 == 0 }))
}
Output: 3
func (Seq[T]) DedupeBy ¶
func (s Seq[T]) DedupeBy[K comparable](sel func(T) K) Seq[T]
DedupeBy yields elements whose key differs from the previous element's key — consecutive duplicates only, O(1) memory. On key-sorted input it equals DistinctBy at a fraction of the cost.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Collapses CONSECUTIVE runs only, in O(1) memory — the streaming
// alternative to DistinctBy, and the right choice on an unbounded
// source where a seen-set would grow forever.
type reading struct {
Tick int
Zone string
}
readings := catena.Of(
reading{1, "cold"}, reading{2, "cold"},
reading{3, "warm"}, reading{4, "cold"},
)
fmt.Println(readings.
DedupeBy(func(r reading) string { return r.Zone }).
Collect())
}
Output: [{1 cold} {3 warm} {4 cold}]
func (Seq[T]) DistinctBy ¶
func (s Seq[T]) DistinctBy[K comparable](sel func(T) K) Seq[T]
DistinctBy yields elements whose key has not been seen before; the first occurrence wins. catena.Distinct(s) is the no-selector form, for a comparable T. On key-sorted input DedupeBy is the O(1)-memory equivalent. ⚠ Retains one key per distinct value — unbounded.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
type user struct {
Org, Name string
}
users := catena.Of(
user{"acme", "ada"},
user{"globex", "bob"},
user{"acme", "eve"},
)
// One user per org; the first occurrence wins.
fmt.Println(users.
DistinctBy(func(u user) string { return u.Org }).
Collect())
}
Output: [{acme ada} {globex bob}]
func (Seq[T]) DistinctWith ¶
DistinctWith yields elements no earlier element equals under eq. First occurrence wins. ⚠ Retains all distinct elements and compares in O(n²) — small inputs only.
Example ¶
package main
import (
"fmt"
"strings"
"github.com/NerdMeNot/catena"
)
func main() {
// For keys that are not comparable, or an equality of your own.
// Retains every distinct element and compares against all of them,
// so this is for small inputs.
fmt.Println(catena.Of("Go", "GO", "rust", "go").
DistinctWith(strings.EqualFold).
Collect())
}
Output: [Go rust]
func (Seq[T]) Drain ¶
func (s Seq[T]) Drain()
Drain consumes the sequence for its side effects. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Consume for side effects alone, discarding the elements.
count := 0
catena.Of(1, 2, 3).OnEach(func(int) { count++ }).Drain()
fmt.Println(count)
}
Output: 3
func (Seq[T]) Drop ¶
Drop skips the first n elements. Panics if n is negative.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of("a", "b", "c", "d").Drop(2).Collect())
}
Output: [c d]
func (Seq[T]) DropLast ¶
DropLast yields all but the final n elements, emitting with an n-element lag. Panics if n is negative. ⚠ Buffers n elements.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Range(1, 6, 1).DropLast(2).Collect())
}
Output: [1 2 3]
func (Seq[T]) DropWhile ¶
DropWhile skips elements until pred first returns false, then yields the rest.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Drops only the leading run; once the predicate fails, everything
// after is kept.
fmt.Println(catena.Of(0, 0, 3, 0, 5).
DropWhile(func(n int) bool { return n == 0 }).
Collect())
}
Output: [3 0 5]
func (Seq[T]) ElementAt ¶
ElementAt returns the element at index i; (zero, false) for a negative or out-of-range index.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
v, ok := catena.Of("a", "b", "c").ElementAt(1)
_, neg := catena.Of("a").ElementAt(-1)
fmt.Println(v, ok, neg)
}
Output: b true false
func (Seq[T]) Filter ¶
Filter yields the elements for which pred returns true.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of(1, 2, 3, 4, 5).
Filter(func(n int) bool { return n%2 == 0 }).
Collect())
}
Output: [2 4]
func (Seq[T]) FilterErr ¶
FilterErr yields elements pred admits, as a Try; a failed pred call yields (zero, err).
Example ¶
package main
import (
"fmt"
"strings"
"github.com/NerdMeNot/catena"
)
func main() {
// A predicate that can fail produces a Try, so the caller chooses
// what a failure means rather than the pipeline deciding.
ports := catena.Of("80", "443", "https", "8080")
valid := ports.FilterErr(func(s string) (bool, error) {
if strings.ContainsAny(s, "abcdefghijklmnopqrstuvwxyz") {
return false, fmt.Errorf("not a port: %q", s)
}
return len(s) > 2, nil
})
kept, errs := valid.CollectAll()
fmt.Println(kept, errs)
}
Output: [443 8080] [not a port: "https"]
func (Seq[T]) FilterIndexed ¶
FilterIndexed yields the elements for which pred(index, element) returns true. The index counts source elements from 0.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// The index counts source elements, not surviving ones.
fmt.Println(catena.Of("a", "b", "c", "d", "e").
FilterIndexed(func(i int, _ string) bool { return i%2 == 0 }).
Collect())
}
Output: [a c e]
func (Seq[T]) FilterMap ¶
FilterMap yields the mapped value for each element f reports true for — a fused Map + Filter.
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
func main() {
// Fused filter and map: one stage, and the comma-ok shape means the
// mapped value is discarded rather than computed twice.
fmt.Println(catena.Of("1", "x", "3").
FilterMap(func(s string) (int, bool) {
n, err := strconv.Atoi(s)
return n, err == nil
}).
Collect())
}
Output: [1 3]
func (Seq[T]) FilterNot ¶
FilterNot yields the elements for which pred returns false.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// The negated form, for when the predicate reads better positively.
fmt.Println(catena.Of("go", "", "rust", "", "zig").
FilterNot(func(s string) bool { return s == "" }).
Collect())
}
Output: [go rust zig]
func (Seq[T]) Find ¶
Find returns the first element pred admits.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
v, ok := catena.Of(1, 4, 9).Find(func(n int) bool { return n > 3 })
fmt.Println(v, ok)
}
Output: 4 true
func (Seq[T]) FindIndex ¶
FindIndex returns the index of the first element pred admits; -1 if none.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of("a", "b").FindIndex(func(s string) bool { return s == "b" }))
fmt.Println(catena.Of("a").FindIndex(func(s string) bool { return s == "z" }))
}
Output: 1 -1
func (Seq[T]) FindLast ¶
FindLast returns the final element pred admits. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
v, ok := catena.Of(1, 4, 9).FindLast(func(n int) bool { return n > 3 })
fmt.Println(v, ok)
}
Output: 9 true
func (Seq[T]) FindMap ¶
FindMap returns the first mapped value f reports true for — a fused Find + Map.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Fused find and map: the mapped value is returned, not the element.
v, ok := catena.Of("x", "12", "y").FindMap(func(s string) (int, bool) {
n := 0
_, err := fmt.Sscanf(s, "%d", &n)
return n, err == nil
})
fmt.Println(v, ok)
}
Output: 12 true
func (Seq[T]) First ¶
First returns the first element.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
v, ok := catena.Of(3, 1).First()
_, empty := catena.Empty[int]().First()
fmt.Println(v, ok, empty)
}
Output: 3 true false
func (Seq[T]) FlatMap ¶
FlatMap yields all elements of f(v) for each element v, in order.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of(1, 2).
FlatMap(func(n int) catena.Seq[int] { return catena.Of(n, -n) }).
Collect())
}
Output: [1 -1 2 -2]
func (Seq[T]) FlatMapSlice ¶
FlatMapSlice yields all elements of the slice f(v) for each element v.
Example ¶
package main
import (
"fmt"
"strings"
"github.com/NerdMeNot/catena"
)
func main() {
// The same, when the callback already has a slice in hand.
fmt.Println(catena.Of("a b", "c d").
FlatMapSlice(func(s string) []string { return strings.Fields(s) }).
Collect())
}
Output: [a b c d]
func (Seq[T]) Fold ¶
Fold reduces the sequence into an accumulator, left to right.
Reduce is the same operation using the first element as the initial accumulator; FoldBy folds per key in one streaming pass. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of(1, 2, 3).Fold(100, func(acc, n int) int { return acc + n }))
}
Output: 106
func (Seq[T]) FoldBy ¶
func (s Seq[T]) FoldBy[K comparable, A any]( key func(T) K, init func(K) A, f func(A, T) A, ) map[K]A
FoldBy folds each element into a per-key accumulator: streaming grouped aggregation with no intermediate per-key slices. init is called once per distinct key. Memory is bounded by the number of distinct keys, not elements. ⚠ Full drain; map iteration order is undefined.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Streaming aggregation per key. GroupBy would retain every element;
// this retains one accumulator per distinct key.
type sale struct {
Region string
Amount int
}
sales := catena.Of(
sale{"west", 100}, sale{"east", 40}, sale{"west", 20},
)
totals := sales.FoldBy(
func(s sale) string { return s.Region },
func(string) int { return 0 },
func(sum int, s sale) int { return sum + s.Amount },
)
fmt.Println(totals["west"], totals["east"])
}
Output: 120 40
func (Seq[T]) FoldErr ¶
FoldErr folds until f fails, returning the accumulator so far and the first error.
Example ¶
package main
import (
"errors"
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Stops at the first error and returns the accumulator so far.
acc, err := catena.Of(1, 2, 3).FoldErr(0, func(acc, n int) (int, error) {
if n == 3 {
return 0, errors.New("three is too many")
}
return acc + n, nil
})
fmt.Println(acc, err)
}
Output: 3 three is too many
func (Seq[T]) FoldIndexed ¶
FoldIndexed is Fold with the element index. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of(10, 20).FoldIndexed(0, func(i, acc, n int) int { return acc + i*n }))
}
Output: 20
func (Seq[T]) FoldWhile ¶
FoldWhile folds until f reports false; the accumulator from the stopping call is included in the result.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Stops when the callback says so; the accumulator from the stopping
// call is included.
fmt.Println(catena.Of(1, 2, 3, 4).FoldWhile(0, func(acc, n int) (int, bool) {
acc += n
return acc, acc < 5
}))
}
Output: 6
func (Seq[T]) ForEach ¶
func (s Seq[T]) ForEach(f func(T))
ForEach calls f on every element. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
catena.Of("a", "b").ForEach(func(s string) { fmt.Print(s) })
}
Output: ab
func (Seq[T]) ForEachErr ¶
ForEachErr calls f on every element, stopping at and returning the first non-nil error; nil if the sequence drains clean.
Example ¶
package main
import (
"errors"
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Stops at the first error the callback returns, and returns it.
err := catena.Of(1, 2, 3).ForEachErr(func(n int) error {
if n == 2 {
return errors.New("stopped at 2")
}
fmt.Println("handled", n)
return nil
})
fmt.Println("err:", err)
}
Output: handled 1 err: stopped at 2
func (Seq[T]) ForEachIndexed ¶
ForEachIndexed calls f(index, element) on every element. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
catena.Of("a", "b").ForEachIndexed(func(i int, s string) { fmt.Printf("%d=%s ", i, s) })
}
Output: 0=a 1=b
func (Seq[T]) GroupBy ¶
func (s Seq[T]) GroupBy[K comparable](sel func(T) K) map[K][]T
GroupBy collects elements into per-key buckets, each in encounter order. ⚠ Full drain; retains every element; map iteration order is undefined.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Retains every element. For an aggregate, FoldBy is bounded by keys.
byParity := catena.Range(1, 6, 1).GroupBy(func(n int) string {
if n%2 == 0 {
return "even"
}
return "odd"
})
fmt.Println(byParity["odd"], byParity["even"])
}
Output: [1 3 5] [2 4]
func (Seq[T]) IfEmpty ¶
IfEmpty yields s, or the given defaults if s yields nothing.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// A fallback for the whole sequence, not per element.
fmt.Println(catena.Of(1, 2).IfEmpty(0).Collect())
fmt.Println(catena.Empty[int]().IfEmpty(0).Collect())
}
Output: [1 2] [0]
func (Seq[T]) IndexBy ¶
func (s Seq[T]) IndexBy[K comparable](sel func(T) K) map[K]T
IndexBy maps each key to its element; on duplicate keys the last element wins. ⚠ Full drain; map iteration order is undefined.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// A lookup table; on a duplicate key the last element wins.
m := catena.Of("apple", "avocado", "blueberry").
IndexBy(func(s string) byte { return s[0] })
fmt.Println(string(m['a']), string(m['b']))
}
Output: avocado blueberry
func (Seq[T]) Intersperse ¶
Intersperse yields sep between consecutive elements.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of("a", "b", "c").Intersperse("-").Collect())
}
Output: [a - b - c]
func (Seq[T]) IsEmpty ¶
IsEmpty reports whether the sequence yields nothing. ⚠ It does so by consuming one element — on a single-pass source that element is lost.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Answers by consuming one element — on a single-pass source that
// element is gone.
fmt.Println(catena.Empty[int]().IsEmpty(), catena.Of(1).IsEmpty())
}
Output: true false
func (Seq[T]) JoinBy ¶
func (s Seq[T]) JoinBy[U any, K comparable, R any]( other Seq[U], leftKey func(T) K, rightKey func(U) K, combine func(T, U) R, ) Seq[R]
JoinBy is a relational inner join: it pairs each element of s with every element of other sharing the same key and yields combine for each pair. It is unrelated to Join and JoinToString, which concatenate strings. Unmatched elements on either side are dropped; duplicate keys produce the cross product per key. Output order is left encounter order, then right encounter order within a key. ⚠ Buffers all of other before the first emission.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// A relational inner join: unmatched rows on either side are dropped,
// and duplicate keys produce the cross product.
type order struct {
Customer int
Amount int
}
type customer struct {
ID int
Name string
}
orders := catena.Of(order{1, 30}, order{2, 10}, order{9, 99})
customers := catena.Of(customer{1, "ada"}, customer{2, "bob"})
fmt.Println(orders.JoinBy(customers,
func(o order) int { return o.Customer },
func(c customer) int { return c.ID },
func(o order, c customer) string { return fmt.Sprintf("%s:%d", c.Name, o.Amount) },
).Collect())
}
Output: [ada:30 bob:10]
func (Seq[T]) JoinToString ¶
JoinToString concatenates the selected strings with sep between elements. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
type user struct{ Name string }
users := catena.Of(user{"ada"}, user{"bob"})
fmt.Println(users.JoinToString(", ", func(u user) string { return u.Name }))
}
Output: ada, bob
func (Seq[T]) Last ¶
Last returns the final element. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
v, ok := catena.Of(3, 1).Last()
fmt.Println(v, ok)
}
Output: 1 true
func (Seq[T]) Map ¶
Map yields f applied to each element.
Example ¶
package main
import (
"fmt"
"strings"
"github.com/NerdMeNot/catena"
)
func main() {
// The element type changes mid-chain, which is what generic methods
// made possible.
fmt.Println(catena.Of(1, 2, 3).
Map(func(n int) string { return strings.Repeat("*", n) }).
Collect())
}
Output: [* ** ***]
func (Seq[T]) MapErr ¶
MapErr yields f applied to each element as a Try; a failed call yields (zero, err).
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
func main() {
// A mapping that can fail produces a Try; a failed call yields the
// zero value alongside the error, never a half-built one.
parsed := catena.Of("1", "two", "3").MapErr(strconv.Atoi)
vals, errs := parsed.CollectAll()
fmt.Println(vals, len(errs))
}
Output: [1 3] 1
func (Seq[T]) MapIndexed ¶
MapIndexed yields f(index, element), counting from 0.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of("a", "b", "c").
MapIndexed(func(i int, s string) string { return fmt.Sprintf("%d:%s", i, s) }).
Collect())
}
Output: [0:a 1:b 2:c]
func (Seq[T]) MaxBy ¶
MaxBy returns the element with the largest key; the earliest maximal element wins ties. NaN keys order below everything (cmp.Compare). MaxOf returns the key itself; catena.Max(s) is the no-selector form. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Returns the ELEMENT with the largest key; ties go to the first.
type run struct {
Name string
Secs int
}
runs := catena.Of(run{"ada", 42}, run{"bob", 51}, run{"eve", 51})
slowest, _ := runs.MaxBy(func(r run) int { return r.Secs })
fmt.Println(slowest.Name)
}
Output: bob
func (Seq[T]) MaxOf ¶
MaxOf returns the largest key — MaxBy returns the element that carries it. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Returns the KEY, where MaxBy returns the element — the -By/-Of
// distinction, which holds across the whole library.
longest, _ := catena.Of("go", "rust", "c").MaxOf(func(s string) int { return len(s) })
fmt.Println(longest)
}
Output: 4
func (Seq[T]) MaxWith ¶
MaxWith returns the largest element under cmp; the earliest maximal element wins ties. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// A comparator, for orderings no single key expresses.
v, _ := catena.Of("bb", "a", "cccc").
MaxWith(func(x, y string) int { return len(x) - len(y) })
fmt.Println(v)
}
Output: cccc
func (Seq[T]) MinBy ¶
MinBy returns the element with the smallest key; the earliest minimal element wins ties. MinOf returns the key itself; catena.Min(s) is the no-selector form. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
type run struct {
Name string
Secs int
}
runs := catena.Of(run{"ada", 42}, run{"bob", 51})
fastest, _ := runs.MinBy(func(r run) int { return r.Secs })
fmt.Println(fastest.Name)
}
Output: ada
func (Seq[T]) MinMaxOf ¶
MinMaxOf returns the smallest and largest keys in one pass. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Both ends in a single pass.
lo, hi, ok := catena.Of("go", "rust", "c").MinMaxOf(func(s string) int { return len(s) })
fmt.Println(lo, hi, ok)
}
Output: 1 4 true
func (Seq[T]) MinOf ¶
MinOf returns the smallest key — MinBy returns the element that carries it. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
shortest, _ := catena.Of("go", "rust", "c").MinOf(func(s string) int { return len(s) })
fmt.Println(shortest)
}
Output: 1
func (Seq[T]) MinWith ¶
MinWith returns the smallest element under cmp; the earliest minimal element wins ties. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
v, _ := catena.Of("bb", "a", "cccc").
MinWith(func(x, y string) int { return len(x) - len(y) })
fmt.Println(v)
}
Output: a
func (Seq[T]) None ¶
None reports whether pred admits no element; stops at the first match.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of(1, 3).None(func(n int) bool { return n%2 == 0 }))
}
Output: true
func (Seq[T]) OnEach ¶
OnEach calls f on every element and passes it through unchanged.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Side effects without changing the stream — logging, metrics,
// progress. Elements pass through untouched.
seen := 0
total := catena.Of(1, 2, 3).
OnEach(func(int) { seen++ }).
SumOf(catena.Self[int])
fmt.Println(total, seen)
}
Output: 6 3
func (Seq[T]) Once ¶
Once returns a sequence that panics if iterated more than once — a development guard for the single-pass contract, not a synchronization mechanism. This is the one operator whose state deliberately lives outside the iteration closure. (catena.Once1 is unrelated: it constructs a one-element sequence.)
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// A development guard for the single-pass contract: the second
// consumption panics instead of silently re-running the producer.
s := catena.Of(1, 2).Once()
fmt.Println(s.Collect())
defer func() { fmt.Println("recovered:", recover()) }()
s.Collect()
}
Output: [1 2] recovered: catena: Once: sequence consumed more than once
func (Seq[T]) Partition ¶
Partition splits elements by pred, preserving encounter order on both sides; nil slices for empty sides. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Both sides in one pass, each in encounter order.
even, odd := catena.Range(1, 6, 1).Partition(func(n int) bool { return n%2 == 0 })
fmt.Println(even, odd)
}
Output: [2 4] [1 3 5]
func (Seq[T]) Prepend ¶
Prepend yields the given values, then s.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of("c").Prepend("a", "b").Collect())
}
Output: [a b c]
func (Seq[T]) ProductOf ¶
ProductOf multiplies the selected values. Empty input yields 1, the multiplicative identity. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// The empty product is 1, the multiplicative identity.
fmt.Println(catena.Of(2, 3, 4).ProductOf(catena.Self[int]))
fmt.Println(catena.Empty[int]().ProductOf(catena.Self[int]))
}
Output: 24 1
func (Seq[T]) Pull ¶
Pull converts s to a pull-based iterator. THE CALLER MUST CALL stop, even if next has returned false, or resources held by s will leak.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Inverts control for hand-written loops. THE CALLER MUST CALL stop,
// or a producer holding a resource never releases it.
next, stop := catena.Of("a", "b").Pull()
defer stop()
v, ok := next()
fmt.Println(v, ok)
}
Output: a true
func (Seq[T]) Reduce ¶
Reduce folds the sequence using its first element as the initial accumulator; (zero, false) on empty input. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Uses the first element as the seed, so it reports false on empty
// rather than inventing a zero.
v, ok := catena.Of(3, 1, 2).Reduce(func(a, b int) int { return a * b })
_, empty := catena.Empty[int]().Reduce(func(a, b int) int { return a })
fmt.Println(v, ok, empty)
}
Output: 6 true false
func (Seq[T]) Reversed ¶
Reversed yields the elements in reverse order. ⚠ Buffers the entire input — hangs on infinite input.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of(1, 2, 3).Reversed().Collect())
}
Output: [3 2 1]
func (Seq[T]) Scan ¶
Scan yields the running accumulator: f(init, e0), f(that, e1), ... The initial value itself is not yielded.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// A running fold: the accumulator is emitted at each step. The
// initial value itself is not emitted, so the output is as long as
// the input.
fmt.Println(catena.Of(1, 2, 3, 4).
Scan(0, func(sum, n int) int { return sum + n }).
Collect())
}
Output: [1 3 6 10]
func (Seq[T]) Seq ¶
Seq converts to the stdlib iterator type. Free.
Example ¶
package main
import (
"fmt"
"slices"
"github.com/NerdMeNot/catena"
)
func main() {
// A free conversion to the standard iterator type, in both
// directions — Seq IS iter.Seq underneath.
fmt.Println(slices.Collect(catena.Of(1, 2, 3).Seq()))
}
Output: [1 2 3]
func (Seq[T]) Single ¶
Single returns the element iff the sequence has exactly one; it stops consuming upon seeing a second.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// True only for exactly one element; it stops as soon as a second
// arrives rather than counting the rest.
a, ok1 := catena.Of(7).Single()
_, ok2 := catena.Of(7, 8).Single()
fmt.Println(a, ok1, ok2)
}
Output: 7 true false
func (Seq[T]) SortedBy ¶
SortedBy yields the elements sorted ascending by key, stably. sel is called exactly once per element (decorate-sort-undecorate). catena.Sorted(s) is the no-selector form, for an ordered T. ⚠ Buffers the entire input — hangs on infinite input.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Stable, and the selector runs exactly once per element rather than
// once per comparison — so an expensive key is affordable. Stability
// shows here: kiwi and date are both 4 long, and kiwi came first.
words := catena.Of("kiwi", "fig", "banana", "date")
fmt.Println(words.SortedBy(func(s string) int { return len(s) }).Collect())
}
Output: [fig kiwi date banana]
func (Seq[T]) SortedByDesc ¶
SortedByDesc yields the elements sorted descending by key, stably. sel is called exactly once per element. ⚠ Buffers the entire input — hangs on infinite input.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
words := catena.Of("kiwi", "fig", "banana")
fmt.Println(words.SortedByDesc(func(s string) int { return len(s) }).Collect())
}
Output: [banana kiwi fig]
func (Seq[T]) SortedWith ¶
SortedWith yields the elements sorted by cmp, stably. ⚠ Buffers the entire input — hangs on infinite input.
Example ¶
package main
import (
"fmt"
"strings"
"github.com/NerdMeNot/catena"
)
func main() {
// A comparator, for orderings a single key cannot express.
fmt.Println(catena.Of("b", "A", "c").
SortedWith(func(x, y string) int { return strings.Compare(strings.ToLower(x), strings.ToLower(y)) }).
Collect())
}
Output: [A b c]
func (Seq[T]) Step ¶
Step yields the first element and every nth element after it. Panics if n <= 0.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// The first element always survives, then every nth after it.
fmt.Println(catena.Range(0, 10, 1).Step(3).Collect())
}
Output: [0 3 6 9]
func (Seq[T]) SumOf ¶
SumOf sums the selected values; integer overflow wraps like +. Empty input sums to 0. catena.Sum(s) is the no-selector form. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
type item struct{ Qty int }
items := catena.Of(item{2}, item{3})
fmt.Println(items.SumOf(func(i item) int { return i.Qty }))
}
Output: 5
func (Seq[T]) Take ¶
Take yields at most the first n elements, consuming exactly as many as it yields. Panics if n is negative.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Consumes exactly what it emits, so it bounds an infinite source.
fmt.Println(catena.Generate(1, func(n int) int { return n + 1 }).
Take(3).
Collect())
}
Output: [1 2 3]
func (Seq[T]) TakeLast ¶
TakeLast yields the final n elements. ⚠ Buffers n elements and fully drains the source before emitting — hangs on infinite input. Panics if n is negative.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Range(1, 8, 1).TakeLast(3).Collect())
}
Output: [5 6 7]
func (Seq[T]) TakeWhile ¶
TakeWhile yields elements until pred first returns false.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Stops at the first element that fails — unlike Filter, which would
// keep testing the rest.
fmt.Println(catena.Of(1, 2, 9, 3).
TakeWhile(func(n int) bool { return n < 5 }).
Collect())
}
Output: [1 2]
func (Seq[T]) TallyBy ¶
func (s Seq[T]) TallyBy[K comparable](sel func(T) K) map[K]int
TallyBy counts elements per key. ⚠ Full drain; map iteration order is undefined.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
counts := catena.Of("apple", "avocado", "blueberry").
TallyBy(func(s string) byte { return s[0] })
fmt.Println(counts['a'], counts['b'])
}
Output: 2 1
func (Seq[T]) ToChan ¶
ToChan starts a goroutine that sends every element on the returned unbuffered channel. The channel is closed when the sequence ends or ctx is done — the consumer must drain or cancel, or the goroutine leaks.
Example ¶
package main
import (
"context"
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// The fan-out mechanism: a Seq is not safe to consume from two
// goroutines, a channel is. Cancelling ctx closes the channel.
for v := range catena.Of(1, 2, 3).ToChan(context.Background()) {
fmt.Print(v, " ")
}
}
Output: 1 2 3
func (Seq[T]) ToList ¶
ToList drains the sequence into a List; nil for empty. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// The eager twin: a List has the same operations, evaluated at once.
l := catena.Of(3, 1, 2).ToList()
fmt.Println(l.Len(), l.At(0))
}
Output: 3 3
func (Seq[T]) TopNBy ¶
TopNBy returns the n elements with the largest keys, sorted descending by key; equal keys retain encounter order and the earliest elements win at the cut. Memory is O(n) — the streaming alternative to SortedByDesc().Take(n). catena.TopN(s, n) is the no-selector form, and BottomNBy is the same operation at the other end. ⚠ Full drain. Panics if n is negative.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// A bounded heap of n, not a sort: memory is O(n) rather than O(all),
// which is the difference between a working pipeline and an OOM on a
// large scan. Output is sorted descending, ties in encounter order.
fmt.Println(catena.Of(5, 1, 9, 3, 7).TopNBy(3, catena.Self[int]))
}
Output: [9 7 5]
func (Seq[T]) UntilDone ¶
UntilDone passes elements through until ctx is done, then yields (zero, ctx.Err()) and stops.
Example ¶
package main
import (
"context"
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Cancellation enters at the edge rather than threading a context
// through every stage. The context's error arrives as an element.
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := catena.Of(1, 2, 3).UntilDone(ctx).Collect()
fmt.Println(err)
}
Output: context canceled
func (Seq[T]) WithIndex ¶
WithIndex pairs each element with its index, counting from 0.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of("a", "b").WithIndex().
MapTo(func(i int, s string) string { return fmt.Sprintf("%d%s", i, s) }).
Collect())
}
Output: [0a 1b]
func (Seq[T]) Zip ¶
Zip pairs elements of s with elements of other, stopping at the shorter side. The receiver drives; other is consumed through iter.Pull (its cleanup always runs). other is pulled once per emitted pair; the receiver is consumed one element past the pair count when other is shorter.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Pairs elements positionally and stops at the shorter side.
names := catena.Of("ada", "bob", "eve")
scores := catena.Of(90, 85)
fmt.Println(names.Zip(scores).
MapTo(func(n string, s int) string { return fmt.Sprintf("%s=%d", n, s) }).
Collect())
}
Output: [ada=90 bob=85]
func (Seq[T]) ZipWithNext ¶
ZipWithNext yields each adjacent pair (element, next element). Empty and single-element input yield nothing.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Each element paired with its successor — deltas, gaps, transitions.
fmt.Println(catena.Of(3, 7, 12).ZipWithNext().
MapTo(func(a, b int) int { return b - a }).
Collect())
}
Output: [4 5]
type Seq2 ¶
Seq2 is a lazy pair sequence: iter.Seq2 with methods. It is a bridge back to Seq (via Keys, Values, MapTo), deliberately not a full peer surface.
Deliberately absent, so nobody goes looking:
- ToMap: needs K comparable on the receiver. Use catena.CollectMap.
- Collect: catena.Unzip is the one way to two slices.
- MapKeys: Swap().MapValues(f).Swap() for the rare need; Map otherwise.
func Empty2 ¶
Empty2 returns the empty Seq2. Re-iterable.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Empty2[string, int]().Count())
}
Output: 0
func From2 ¶
From2 adapts any push-function pair sequence. Re-iterable iff the underlying source is.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
pairs := catena.From2(func(yield func(string, int) bool) {
yield("a", 1)
yield("b", 2)
})
fmt.Println(pairs.MapTo(func(k string, v int) string {
return fmt.Sprintf("%s=%d", k, v)
}).Collect())
}
Output: [a=1 b=2]
func FromMap ¶
func FromMap[K comparable, V any](m map[K]V) Seq2[K, V]
FromMap returns a re-iterable Seq2 over m, in undefined (map) order.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
ages := map[string]int{"ada": 36}
fmt.Println(catena.CollectMap(catena.FromMap(ages)))
}
Output: map[ada:36]
func (Seq2[K, V]) All ¶
All reports whether pred admits every pair; stops at the first counterexample. Vacuously true on empty input.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of(2, 4).WithIndex().All(func(i, v int) bool { return v%2 == 0 }))
}
Output: true
func (Seq2[K, V]) Any ¶
Any reports whether pred admits any pair; stops at the first match.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of(1, 2).WithIndex().Any(func(i, v int) bool { return v == 2 }))
}
Output: true
func (Seq2[K, V]) Count ¶
Count returns the number of pairs. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Of("a", "b", "c").WithIndex().Count())
}
Output: 3
func (Seq2[K, V]) Drop ¶
Drop skips the first n pairs. Panics if n is negative.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
fmt.Println(catena.Range(1, 5, 1).WithIndex().Drop(2).Values().Collect())
}
Output: [3 4]
func (Seq2[K, V]) Filter ¶
Filter yields the pairs pred admits.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
pairs := catena.Of(1, 2, 3, 4).WithIndex().
Filter(func(i, v int) bool { return v%2 == 0 })
fmt.Println(pairs.Values().Collect())
}
Output: [2 4]
func (Seq2[K, V]) FilterNot ¶
FilterNot yields the pairs pred rejects.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
pairs := catena.Of(1, 2, 3).WithIndex().
FilterNot(func(i, v int) bool { return v == 2 })
fmt.Println(pairs.Values().Collect())
}
Output: [1 3]
func (Seq2[K, V]) First ¶
First returns the first pair.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
i, v, ok := catena.Of("a", "b").WithIndex().First()
fmt.Println(i, v, ok)
}
Output: 0 a true
func (Seq2[K, V]) Fold ¶
Fold reduces the pairs into an accumulator, left to right. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
total := catena.Of(10, 20, 30).WithIndex().
Fold(0, func(acc, i, v int) int { return acc + i*v })
fmt.Println(total)
}
Output: 80
func (Seq2[K, V]) ForEach ¶
func (s Seq2[K, V]) ForEach(f func(K, V))
ForEach calls f on every pair. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
catena.Of("a", "b").WithIndex().ForEach(func(i int, s string) {
fmt.Printf("%d=%s ", i, s)
})
}
Output: 0=a 1=b
func (Seq2[K, V]) Keys ¶
Keys yields the first element of each pair. Calling Keys and Values on the same single-pass Seq2 is a double consume — use Unzip.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// Keys and Values on the SAME single-pass Seq2 is a double consume;
// Unzip does both in one pass.
fmt.Println(catena.Of("a", "b").WithIndex().Keys().Collect())
}
Output: [0 1]
func (Seq2[K, V]) Map ¶
Map yields f applied to each pair.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
pairs := catena.Of("a", "b").WithIndex().
Map(func(i int, s string) (string, int) { return s, i * 10 })
fmt.Println(catena.CollectMap(pairs))
}
Output: map[a:0 b:10]
func (Seq2[K, V]) MapTo ¶
MapTo collapses each pair into one value — the intended exit back to Seq and its full API.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// The intended exit: collapse each pair into one value and continue
// in Seq, where the full API lives.
fmt.Println(catena.Of("a", "b").WithIndex().
MapTo(func(i int, s string) string { return fmt.Sprintf("%d%s", i, s) }).
Collect())
}
Output: [0a 1b]
func (Seq2[K, V]) MapValues ¶
MapValues yields each pair with its value replaced by f(k, v). f receives the key too (Kotlin-consistent).
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
// The callback receives the key as well as the value.
pairs := catena.Of("a", "b").WithIndex().
MapValues(func(i int, s string) string { return fmt.Sprintf("%d%s", i, s) })
fmt.Println(pairs.Values().Collect())
}
Output: [0a 1b]
func (Seq2[K, V]) Pull ¶
Pull converts s to a pull-based iterator. THE CALLER MUST CALL stop, even if next has returned false, or resources held by s will leak.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
next, stop := catena.Of("a", "b").WithIndex().Pull()
defer stop()
i, v, ok := next()
fmt.Println(i, v, ok)
}
Output: 0 a true
func (Seq2[K, V]) Seq2 ¶
Seq2 converts to the stdlib iterator type. Free.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
for i, v := range catena.Of("a", "b").WithIndex().Seq2() {
fmt.Printf("%d=%s ", i, v)
}
}
Output: 0=a 1=b
func (Seq2[K, V]) Swap ¶
Swap yields each pair with its sides exchanged.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
pairs := catena.Of("a", "b").WithIndex().Swap()
fmt.Println(pairs.Keys().Collect())
}
Output: [a b]
type Try ¶
Try is a lazy sequence of fallible elements: iter.Seq2[T, error] with methods. When err != nil the value must not be read.
Try carries a small operator set on purpose — it is not a second Seq. The intended shape is to stay in Try only while errors are still in play, then commit to a policy (Ignore, Must, Collect, CollectAll) and continue on Seq with the full API. Wanting Sorted or GroupBy on a Try means the errors have been carried one stage too far.
Its operators follow five uniform rules, referred to below as R1–R5:
R1 Intermediates never inspect errored elements: predicates and map
functions are not called on them; the element flows through.
R2 The positional intermediates (Take, Drop) count elements, errored
or not, so Take(n) consumes at most n of the source.
Ignore().Take(n) is the "n successes" spelling. Count is a
terminal and follows R5 instead.
R3 An errored element passes through TakeWhile without terminating
the sequence; only a successful element failing pred ends it.
R4 Operators that generate an error yield (zero, err).
R5 Single-error terminals (Collect, Fold, ForEach, Err, Count) stop
consuming at the first error.
func EmptyTry ¶
EmptyTry returns the empty Try. Re-iterable.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
vals, err := catena.EmptyTry[int]().Collect()
fmt.Println(vals, err)
}
Output: [] <nil>
func FromErrs ¶
FromErrs adapts any push-function fallible sequence. Re-iterable iff the underlying source is.
Example ¶
package main
import (
"fmt"
"github.com/NerdMeNot/catena"
)
func main() {
rows := catena.FromErrs(func(yield func(int, error) bool) {
yield(1, nil)
yield(0, fmt.Errorf("row 2: corrupt"))
})
vals, err := rows.Collect()
fmt.Println(vals, err)
}
Output: [1] row 2: corrupt
func (Try[T]) Collect ¶
Collect gathers successful elements until the first error, returning the partial slice and that error (R5); (all elements, nil) on a clean drain. Nil slice for empty.
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// Abort: the values gathered before the failure, plus the error.
vals, err := parseAges().Collect()
fmt.Println(vals, err != nil)
}
Output: [36] true
func (Try[T]) CollectAll ¶
CollectAll drains everything, gathering all successes and all errors. Positional correspondence between the two slices is lost. Nil slices when empty. ⚠ Full drain.
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// Gather: everything that worked and everything that did not, in one
// pass. The two slices do not correspond positionally.
vals, errs := parseAges().CollectAll()
fmt.Println(vals, len(errs))
}
Output: [36 41] 1
func (Try[T]) Count ¶
Count counts successful elements up to the first error, which is returned alongside the count so far (R5).
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// Successes counted up to the first error, which is returned with it.
n, err := parseAges().Count()
fmt.Println(n, err != nil)
}
Output: 1 true
func (Try[T]) Drop ¶
Drop skips the first n elements, errored or not (R2). Panics if n is negative.
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
rest, _ := parseAges().Drop(1).CollectAll()
fmt.Println(rest)
}
Output: [41]
func (Try[T]) Err ¶
Err consumes until the first error and returns it; nil on a clean drain (R5).
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// Just the first error, if any — for pipelines run entirely for
// their side effects.
fmt.Println(parseAges().Err() != nil)
}
Output: true
func (Try[T]) Errs ¶
Errs yields the errors, dropping successful elements — the dual of Ignore. Ignore and Errs on the same single-pass Try is a double consume; use CollectAll.
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// The dual of Ignore. Consuming both on one single-pass Try is a
// double consume — use CollectAll instead.
fmt.Println(parseAges().Errs().Count())
}
Output: 1
func (Try[T]) Filter ¶
Filter yields the successful elements pred admits; errored elements pass through unexamined.
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// The predicate is not called on errored elements, and they are not
// filtered out — dropping them would silently discard failures.
old := parseAges().Filter(func(n int) bool { return n > 40 })
vals, errs := old.CollectAll()
fmt.Println(vals, len(errs))
}
Output: [41] 1
func (Try[T]) FilterErr ¶
FilterErr yields the successful elements pred admits; a failed pred call yields (zero, err); errored elements pass through unexamined.
Example ¶
package main
import (
"errors"
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
valid := parseAges().FilterErr(func(n int) (bool, error) {
if n < 0 {
return false, errors.New("negative")
}
return n > 40, nil
})
vals, errs := valid.CollectAll()
fmt.Println(vals, len(errs))
}
Output: [41] 1
func (Try[T]) FlatMap ¶
FlatMap yields every element of f(v) for each successful element v, in order; an errored input element passes through un-mapped.
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// An errored input passes through un-mapped; inner errors flow in
// order alongside the outer ones.
pairs := parseAges().FlatMap(func(n int) catena.Try[int] {
return catena.Of(n, n+1).MapErr(func(v int) (int, error) { return v, nil })
})
vals, errs := pairs.CollectAll()
fmt.Println(vals, len(errs))
}
Output: [36 37 41 42] 1
func (Try[T]) Fold ¶
Fold reduces successful elements until the first error, returning the accumulator so far and that error (R5).
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// Stops at the first error, returning the accumulator so far.
sum, err := parseAges().Fold(0, func(acc, n int) int { return acc + n })
fmt.Println(sum, err != nil)
}
Output: 36 true
func (Try[T]) ForEach ¶
ForEach calls f on each successful element, stopping at and returning the first of an element error or a non-nil f return (R5).
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// Stops at the first of an element error or a callback error.
err := parseAges().ForEach(func(n int) error {
fmt.Println("handled", n)
return nil
})
fmt.Println(err != nil)
}
Output: handled 36 true
func (Try[T]) Ignore ¶
Ignore yields the successful elements, dropping errored ones.
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// Skip: drop the failures and carry on as a plain Seq.
fmt.Println(parseAges().Ignore().Collect())
}
Output: [36 41]
func (Try[T]) Map ¶
Map yields f applied to each successful element; errored elements pass through.
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// The callback never sees an errored element; it passes through
// untouched, so a failure is not silently mapped into a valid value.
doubled := parseAges().Map(func(n int) int { return n * 2 })
vals, errs := doubled.CollectAll()
fmt.Println(vals, len(errs))
}
Output: [72 82] 1
func (Try[T]) MapErr ¶
MapErr yields f applied to each successful element; a failed call yields (zero, err); errored elements pass through.
Example ¶
package main
import (
"errors"
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// A mapping that can itself fail. Errors from either stage flow on.
tenths := parseAges().MapErr(func(n int) (int, error) {
if n > 40 {
return 0, errors.New("too old")
}
return n * 10, nil
})
vals, errs := tenths.CollectAll()
fmt.Println(vals, len(errs))
}
Output: [360] 2
func (Try[T]) Must ¶
Must yields the successful elements and panics with the error value on the first error — recover() receives the error itself.
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// For pipelines where a failure is a programming bug. The panic
// value is the error itself, so recover() can inspect it.
defer func() { fmt.Println("recovered:", recover()) }()
parseAges().Must().Drain()
}
Output: recovered: strconv.Atoi: parsing "unknown": invalid syntax
func (Try[T]) OnEach ¶
OnEach calls f on every successful element and passes everything through.
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// Runs only on successes; errors pass by untouched.
seen := 0
parseAges().OnEach(func(int) { seen++ }).CollectAll()
fmt.Println(seen)
}
Output: 2
func (Try[T]) OnError ¶
OnError calls f on every error and passes everything through — a logging hook.
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// The logging tap, the mirror of OnEach.
logged := 0
parseAges().OnError(func(error) { logged++ }).CollectAll()
fmt.Println(logged)
}
Output: 1
func (Try[T]) Pull ¶
Pull converts t to a pull-based iterator. THE CALLER MUST CALL stop, even if next has returned false, or resources held by t will leak.
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
next, stop := parseAges().Pull()
defer stop()
v, err, ok := next()
fmt.Println(v, err, ok)
}
Output: 36 <nil> true
func (Try[T]) Recover ¶
Recover offers each error to f: reporting true replaces the element with (v, nil); reporting false passes the error through unchanged.
Example ¶
package main
import (
"fmt"
"strconv"
"strings"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// Repair chosen errors mid-stream: reporting true replaces the
// element, false lets the error continue.
fixed := parseAges().Recover(func(err error) (int, bool) {
return 0, strings.Contains(err.Error(), "unknown")
})
vals, errs := fixed.CollectAll()
fmt.Println(vals, len(errs))
}
Output: [36 0 41] 0
func (Try[T]) Seq2 ¶
Seq2 converts to the stdlib iterator type. Free.
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// A free conversion to the standard pair iterator.
for v, err := range parseAges().Seq2() {
if err != nil {
fmt.Println("error at", v)
break
}
fmt.Println("ok", v)
}
}
Output: ok 36 error at 0
func (Try[T]) Take ¶
Take yields at most the first n elements, errored or not (R2). Panics if n is negative.
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// Counts elements, errored or not — so it consumes at most n. For
// "n successes", use Ignore().Take(n).
first, _ := parseAges().Take(2).CollectAll()
successes := parseAges().Ignore().Take(2).Collect()
fmt.Println(first, successes)
}
Output: [36] [36 41]
func (Try[T]) TakeWhile ¶
TakeWhile yields elements until pred rejects a successful element; errored elements pass through and do not terminate (R3).
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// An errored element passes through without ending the sequence;
// only a successful element failing the predicate stops it.
kept, errs := parseAges().TakeWhile(func(n int) bool { return n < 40 }).CollectAll()
fmt.Println(kept, len(errs))
}
Output: [36] 1
func (Try[T]) UntilDone ¶
UntilDone passes elements through until ctx is done, then yields (zero, ctx.Err()) and stops.
Example ¶
package main
import (
"context"
"fmt"
"strconv"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := parseAges().UntilDone(ctx).Collect()
fmt.Println(err)
}
Output: context canceled
func (Try[T]) WrapErr ¶
WrapErr replaces each error with f(err) — the place to add positional context. If f returns nil (a caller bug), the original error is kept: an error is never converted into a zero-value success.
Example ¶
package main
import (
"fmt"
"strconv"
"strings"
"github.com/NerdMeNot/catena"
)
// parseAges is the fixture the Try examples share: two good values and one
// that fails, so every operator's treatment of an error is visible.
func parseAges() catena.Try[int] {
return catena.Of("36", "unknown", "41").MapErr(strconv.Atoi)
}
func main() {
// Add the context that only this stage has. Returning nil keeps the
// original error rather than turning a failure into a zero value.
wrapped := parseAges().WrapErr(func(err error) error {
return fmt.Errorf("parsing ages: %w", err)
})
_, err := wrapped.Collect()
fmt.Println(strings.HasPrefix(err.Error(), "parsing ages:"))
}
Output: true
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
01-basics
command
Basics: building a pipeline, what laziness means in practice, and how catena sequences interoperate with plain range loops and iter.Seq.
|
Basics: building a pipeline, what laziness means in practice, and how catena sequences interoperate with plain range loops and iter.Seq. |
|
02-grouping
command
Grouping: FoldBy is the library's centerpiece — streaming aggregation bounded by distinct keys, not elements.
|
Grouping: FoldBy is the library's centerpiece — streaming aggregation bounded by distinct keys, not elements. |
|
03-selection
command
Selection: finding extremes and top-k without sorting the world.
|
Selection: finding extremes and top-k without sorting the world. |
|
04-errors
command
Errors: one parse pipeline, three policies.
|
Errors: one parse pipeline, three policies. |
|
05-resources
command
Resources: a producer that owns something — here a real temp file — opens it lazily inside the iteration closure.
|
Resources: a producer that owns something — here a real temp file — opens it lazily inside the iteration closure. |
|
06-streaming
command
Streaming: sequences with no end, and the operators that stay safe on them — running state with Scan, moving averages with Windowed, batching with Chunked, and change detection with DedupeBy.
|
Streaming: sequences with no end, and the operators that stay safe on them — running state with Scan, moving averages with Windowed, batching with Chunked, and change detection with DedupeBy. |
|
07-join
command
Join: JoinBy is a relational inner join between two streams of structs — the right side is indexed by key, the left side streams past it, and the joined stream is a plain Seq you keep chaining on.
|
Join: JoinBy is a relational inner join between two streams of structs — the right side is indexed by key, the left side streams past it, and the joined stream is a plain Seq you keep chaining on. |
|
08-list
command
List and the Seq2 bridge: when the data is small and already in memory, eager evaluation with exact preallocation beats a lazy chain — and the two worlds convert explicitly, in both directions.
|
List and the Seq2 bridge: when the data is small and already in memory, eager evaluation with exact preallocation beats a lazy chain — and the two worlds convert explicitly, in both directions. |
|
internal
|
|
|
gen/assets
command
Command assets generates the catena mark as SVG.
|
Command assets generates the catena mark as SVG. |
|
gen/listgen
command
Command listgen generates list_gen.go: the eager List[T] mirror of the Seq[T] operation set.
|
Command listgen generates list_gen.go: the eager List[T] mirror of the Seq[T] operation set. |
|
gen/opdocs
command
Command opdocs generates the operator reference in docs/operators/.
|
Command opdocs generates the operator reference in docs/operators/. |