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.
Contracts every caller should know:
- Treat every Seq as single-pass. Re-iterability depends entirely on the producer (see the constructor table). 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 spec
// commits to documenting (§4.2 lazy acquisition, §7.7 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 §7.7 pattern.
// 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 §4.2 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 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
- 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.1.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 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). ⚠ 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 operation set. []T(l) unwraps it with no copy.
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. O(1)/exact-allocation override.
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]) FilterIndexed ¶
FilterIndexed mirrors Seq.FilterIndexed eagerly.
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 (§7.5).
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]) 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]) 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]) MapIndexed ¶
MapIndexed mirrors Seq.MapIndexed eagerly. O(1)/exact-allocation override.
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]) ZipWithNext ¶
ZipWithNext mirrors Seq.ZipWithNext eagerly.
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.
func Chain ¶
Chain yields each sequence's elements in order.
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)). 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. ⚠ 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.
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.
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). 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. 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. 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). ⚠ 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. ⚠ 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. ⚠ 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. ⚠ 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. 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). ⚠ 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. ⚠ 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. ⚠ 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. ⚠ 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.
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). ⚠ 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. ⚠ 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). ⚠ 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.
func Empty2 ¶
Empty2 returns the empty Seq2.
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.
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.
func EmptyTry ¶
EmptyTry returns the empty Try.
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.
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 (§10 of the spec).
|
Command listgen generates list_gen.go: the eager List[T] mirror of the Seq[T] operation set (§10 of the spec). |
|
gen/opdocs
command
Command opdocs generates the operator reference in docs/operators/.
|
Command opdocs generates the operator reference in docs/operators/. |