catena

package module
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 31, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

README

catena

Lazy, fully typed sequence pipelines for Go. Kotlin-stdlib and LINQ ergonomics on top of iter.Seq, built on Go 1.27 generic methods.

CI Release Go Reference

// Order and UserID are your own types; orders is a []Order.
top := catena.FromSlice(orders).
    Filter(func(o Order) bool { return o.Paid }).
    TopNBy(10, func(o Order) int { return o.Amount })

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

Requires Go 1.27+ for generic methods. Adding catena therefore raises your module's own go directive to 1.27, and everyone building your code — teammates and CI included — needs a 1.27 toolchain. No runtime dependencies: the one require line is a test-only property-testing library, and go list -deps on the package resolves to the standard library alone.

Catena is Latin for "chain".


Why

Go finally has the two pieces this needed — iter.Seq as a common iterator currency, and generic methods so a chain can change element type mid-flight. What was missing was a library willing to be precise about the things that make or break iterator code in practice.

Nothing is hidden. No reflect, no any in value position, no hidden buffering, and exactly one goroutine in the library — ToChan's, declared in the first line of its doc comment. An operator that buffers says so too, with its bound; a terminal that would hang on an infinite sequence is marked; an invalid argument panics at construction, naming the operator you actually called. The only panics during iteration are Once's and Must's, which is what those two are for.

The contracts are tested, not promised. Every exported operator is registered in a conformance harness, compared against a hand-written loop, and put through the invariants its class allows: re-iteration, early termination over an infinite source, the producer's cleanup running on an early break, and a bound on how much of the source it may consume to produce k elements. A completeness check parses the package and fails CI for any operator nothing registers. Library statement coverage is 100%, and the badge is not a snapshot: CI recomputes it on every push and fails the build below 100%, so a regression turns the build red rather than the badge stale.

Slow only where Go is slow. Measured against the same pipelines hand-built from raw iter.Seq closures, catena beats the raw mechanism on multi-stage pipelines and on terminals, and costs 4–13% more on single-stage shapes — where the baseline is inlined into its consumer and a call across a package boundary cannot be. What remains against a plain loop is the iterator protocol itself, and every number is published rather than waved at.

In practice

Pipelines stay lazy until a terminal consumes them, and early termination propagates through every stage — Take(2) here computes exactly two squares, out of an infinite sequence:

catena.Generate(1, func(n int) int { return n * 2 }). // 1, 2, 4, 8, ...
    Map(func(n int) int { return n * n }).
    Take(2).
    Collect() // [1 4]

When elements can fail, the pipeline carries the errors and the consumer picks the policy — abort, gather, or skip:

nums := catena.FromSlice(lines).MapErr(strconv.Atoi)

// pick one:
vals, err   := nums.Collect()          // stop at the first bad line
vals, errs  := nums.CollectAll()       // keep everything, report everything
vals        := nums.Ignore().Collect() // skip bad lines

Producers that own a resource open it lazily inside the pipeline, so an unconsumed pipeline holds nothing and a downstream Take(3) still closes the rows — proven against the real database/sql stack in bake_test.go.

The operators worth switching for

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

Use Instead of And it costs
FoldBy GroupBy + fold per bucket 2.97 MB → 1.2 KB (bounded by keys, not elements)
TopNBy(10, sel) SortedDesc().Take(10) 1.7 MB → 1 KB, 32× faster (bounded heap)
DedupeBy DistinctBy on sorted input unbounded seen-set → O(1)

Performance, before you choose

Measured three ways — a hand-written loop, the same pipeline hand-built from raw iter.Seq closures, and catena (Apple M5 Max, Go 1.27, 100k elements, bench_baseline_test.go):

Shape Hand loop Raw closures catena
4-stage pipeline (filter→map→filter→sum) 39µs 350µs 262µs
Sum 107µs 178µs 70µs
Contains 116µs 70µs

Two facts fall out of that. catena adds essentially nothing on top of Go's iterator mechanism — on these three shapes it beats the raw-closure baseline outright, and across the full table its worst showing is 13% behind on a single-stage Map, where the baseline inlines into its caller and a library cannot. So what you give up is close to what iter.Seq itself costs. And that cost is real: against a plain loop doing trivial arithmetic a pipeline runs about 6.6× slower — roughly 2.2ns per element across the whole four-stage chain, in closure calls the compiler cannot flatten. If your loop body does anything beyond arithmetic — parses, allocates, touches a map or a socket — the per-element work buries it and the two converge.

The full benchmarks show the code both ways, so you can judge what the nanoseconds buy.

The shape of the library

Type What it is
Seq[T] lazy iter.Seq[T] with 84 methods — 37 chainable, 47 terminal; range works directly
Seq2[K, V] the stdlib pair currency; a bridge back to Seq, not a peer
Try[T] iter.Seq2[T, error]; error policy chosen by the consumer
List[T] eager []T with the mirrored operation set — generated, and conformance-checked to agree with AsSeq()

Interop is one conversion and zero cost, in both directions: slices.Sorted(s.Seq()) to hand a pipeline to the stdlib, catena.From(maps.Keys(m)) to take one back. Operations that constrain the element type (Distinct, Sorted, Sum, Max, …) are package functions, because a method on Seq[T any] may require nothing of T — so are Chunked, ChunkedBy and Windowed, which return Seq[[]T] and would be an instantiation cycle as methods. The chain continues normally after either kind.

Documentation

catena.nerdmenot.in — the documentation site: every guide and the full operator reference, versioned per release.

Operator reference — all 183 operators across 14 pages, each with its signature, its memory and termination behaviour, and a worked example that go test runs and verifies.

Guides, in reading order:

  1. Getting started — the five-minute tour
  2. Concepts — the four types, eager vs lazy, and the contract system
  3. Error handlingTry and resource-owning producers
  4. Operator catalog — the index, with memory and drain classes
  5. Performance — the three-way benchmarks, honestly
  6. Releasing — how a release is cut and verified

Also: runnable examples · API reference

Status

v1.2.0 is the current release. v1.1.0 was the first supported one and froze the API: within v1, operators keep their names, signatures and documented behaviour, and anything removed or changed incompatibly waits for a major version. Earlier tags were withdrawn and are retracted in go.mod — don't use them. Changes are tracked in CHANGELOG.md.

Licensed under Apache-2.0.

Documentation

Overview

Package catena is a lazy, fully-typed sequence library for Go 1.27+, built on iter.Seq and generic methods.

The four types:

  • Seq[T]: lazy, single-pass by contract, possibly infinite.
  • Seq2[K, V]: the stdlib pairing currency; a bridge back to Seq, not a peer.
  • Try[T]: Seq2[T, error]; error policy is chosen by the consumer (Collect stops at the first error, CollectAll drains, Ignore skips).
  • List[T]: eager []T with the mirrored operation set.

Finding an operator: most are methods, but operations that constrain the element type (Distinct, Sorted, Sum, Max, Contains, Union, …) are package functions, because a method on Seq[T any] may require nothing of T. So are Chunked, ChunkedBy and Windowed, which return Seq[[]T] — a method doing that is an instantiation cycle — and the Flatten family, which constrains the receiver's shape. Spelling is catena.Distinct(s), not s.Distinct(); the chain continues normally afterwards, as catena.Distinct(s).Filter(f). If the compiler reports "has no field or method Distinct", this is why.

Contracts every caller should know:

  • Treat every Seq as single-pass. Re-iterability depends entirely on the producer (see the table on Seq); operators preserve it either way. Once() is a development guard.
  • Nil sequences are empty: every method and package function accepts a nil receiver or argument and treats it as an empty sequence.
  • Invalid construction arguments (negative counts, zero step) panic at construction time with a "catena: " prefixed message. Nil callbacks are not defended and panic at first use.
  • Operators marked as buffering state their bound; terminals marked as full-drain hang on infinite input.
  • Map-returning terminals return Go maps: iteration order is undefined.
  • When a Try element carries a non-nil error, do not read the value.
  • Not safe for concurrent use; ToChan is the fan-out mechanism.
  • comparable-constrained functions panic at runtime if T is an interface type holding a non-comparable value (Go 1.20 semantics).
Example (LazyAcquisition)
package main

// Runnable examples: the resource-owning producer patterns the library
// documents rather than ships — lazy acquisition and the reader adapter —
// plus the operators that justify the library.

import (
	"bufio"
	"database/sql"
	"fmt"
	"strings"

	"github.com/NerdMeNot/catena"
)

// Lines adapts an io.Reader into a Try of its lines — the reader-adapter
// pattern the library documents rather than ships.
// The reader is wrapped lazily inside the closure: a sequence that is
// never consumed never reads, and early termination stops the scan.
func Lines(open func() (*strings.Reader, error)) catena.Try[string] {
	return func(yield func(string, error) bool) {
		r, err := open()
		if err != nil {
			yield("", err)
			return
		}
		sc := bufio.NewScanner(r)
		for sc.Scan() {
			if !yield(sc.Text(), nil) {
				return
			}
		}
		if err := sc.Err(); err != nil {
			yield("", err)
		}
	}
}

func main() {
	opened := 0
	logs := Lines(func() (*strings.Reader, error) {
		opened++
		return strings.NewReader("GET /a\nPOST /b\nGET /c\nGET /d"), nil
	})

	// Building the pipeline opens nothing.
	gets := logs.Ignore().
		Filter(func(l string) bool { return strings.HasPrefix(l, "GET ") }).
		Take(2)
	fmt.Println("opened after building:", opened)

	fmt.Println(gets.Collect())
	fmt.Println("opened after consuming:", opened)
}

// Rows is the producer pattern for *sql.Rows: lazy acquisition, so
// an unconsumed Try holds no resource, and defer runs on early
// termination through any number of stages. bake_test.go exercises it
// against a real database/sql driver.
func Rows[T any](open func() (*sql.Rows, error), scan func(*sql.Rows) (T, error)) catena.Try[T] {
	return func(yield func(T, error) bool) {
		var zero T
		rows, err := open()
		if err != nil {
			yield(zero, err)
			return
		}
		defer rows.Close()
		for rows.Next() {
			v, err := scan(rows)
			if err != nil {
				v = zero
			}
			if !yield(v, err) {
				return
			}
		}
		if err := rows.Err(); err != nil {
			yield(zero, err)
		}
	}
}
Output:
opened after building: 0
[GET /a GET /c]
opened after consuming: 1

Index

Examples

Constants

View Source
const Version = "1.4.0"

Version is the library's version, kept in lockstep with release tags: the release workflow refuses to publish a tag that disagrees with it.

Variables

This section is empty.

Functions

func AssociateWith

func AssociateWith[T comparable, V any](s Seq[T], f func(T) V) map[T]V

AssociateWith maps each element to f(element); on duplicate elements the last value wins. ⚠ Full drain; map iteration order is undefined.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	m := catena.AssociateWith(catena.Of("go", "rust"), func(s string) int { return len(s) })
	fmt.Println(m["go"], m["rust"])
}
Output:
2 4

func Average

func Average[T Numeric](s Seq[T]) (float64, bool)

Average returns the mean, accumulating in float64; (0, false) on empty input. ⚠ Full drain.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	avg, ok := catena.Average(catena.Of(2.0, 4.0))
	fmt.Println(avg, ok)
}
Output:
3 true

func BottomN added in v1.2.0

func BottomN[T cmp.Ordered](s Seq[T], n int) []T

BottomN returns the n smallest elements, sorted ascending; equal elements retain encounter order. Memory is O(n) — the streaming alternative to Sorted(s).Take(n). ⚠ Full drain. Panics if n is negative.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	fmt.Println(catena.BottomN(catena.Of(5, 1, 9, 3), 2))
}
Output:
[1 3]

func CollectMap

func CollectMap[K comparable, V any](s Seq2[K, V]) map[K]V

CollectMap drains a pair sequence into a map; on duplicate keys the last value wins. ⚠ Full drain; map iteration order is undefined.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// To a map. On a duplicate key the last value wins, as with plain
	// map assignment.
	fmt.Println(catena.CollectMap(catena.Of("a", "b").WithIndex()))
}
Output:
map[0:a 1:b]

func Contains

func Contains[T comparable](s Seq[T], v T) bool

Contains reports whether v occurs in s; stops at the first match.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	fmt.Println(catena.Contains(catena.Of(1, 2, 3), 2))
}
Output:
true

func Equal

func Equal[T comparable](a, b Seq[T]) bool

Equal reports whether a and b yield the same elements in the same order. Consumes both sequences up to and including the first difference — fully when they are equal. b is consumed through iter.Pull (its cleanup always runs).

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	fmt.Println(catena.Equal(catena.Of(1, 2), catena.Of(1, 2)))
	fmt.Println(catena.Equal(catena.Of(1, 2), catena.Of(1)))
}
Output:
true
false

func IndexOf

func IndexOf[T comparable](s Seq[T], v T) int

IndexOf returns the index of the first occurrence of v; -1 if none.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	fmt.Println(catena.IndexOf(catena.Of("a", "b"), "b"))
	fmt.Println(catena.IndexOf(catena.Of("a"), "z"))
}
Output:
1
-1

func Join

func Join(s Seq[string], sep string) string

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

func Max[T cmp.Ordered](s Seq[T]) (T, bool)

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

func Min[T cmp.Ordered](s Seq[T]) (T, bool)

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

func MinMax[T cmp.Ordered](s Seq[T]) (min, max T, ok bool)

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

func Product[T Numeric](s Seq[T]) T

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

func Sum[T Numeric](s Seq[T]) T

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

func TopN[T cmp.Ordered](s Seq[T], n int) []T

TopN returns the n largest elements, sorted descending; equal elements retain encounter order. Memory is O(n) — the streaming alternative to SortedDesc(s).Take(n). ⚠ Full drain. Panics if n is negative.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	fmt.Println(catena.TopN(catena.Of(5, 1, 9, 3), 2))
}
Output:
[9 5]

func Unzip

func Unzip[K, V any](s Seq2[K, V]) ([]K, []V)

Unzip drains a pair sequence into its two sides; nil slices for empty input. ⚠ Full drain; buffers both sides.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// Both sides in one pass — the safe way to get keys and values from
	// a single-use Seq2.
	idx, vals := catena.Unzip(catena.Of("a", "b").WithIndex())
	fmt.Println(idx, vals)
}
Output:
[0 1] [a b]

Types

type Integer

type Integer interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 |
		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}

Integer is the constraint for Range.

type List

type List[T any] []T

List is an eager []T with the mirrored method set. []T(l) unwraps it with no copy, and AsSeq() views it lazily for free.

The mirror covers Seq's methods, not the constraint-bound package functions: those take a Seq, so reach them through AsSeq and come back with ToList, as catena.Sorted(l.AsSeq()).ToList(). Concat likewise takes a Seq, so it is l1.Concat(l2.AsSeq()).

Four mirrored operators cross back to lazy, because their Seq counterparts return a lazy type: WithIndex and ZipWithNext return Seq2, MapErr and FilterErr return Try.

func (List[T]) All

func (l List[T]) All(pred func(T) bool) bool

All mirrors Seq.All eagerly.

func (List[T]) Any

func (l List[T]) Any(pred func(T) bool) bool

Any mirrors Seq.Any eagerly.

func (List[T]) Append

func (l List[T]) Append(vals ...T) List[T]

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

func (l List[T]) AsSeq() Seq[T]

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

func (l List[T]) At(i int) T

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]) AverageOf

func (l List[T]) AverageOf[N Numeric](sel func(T) N) (float64, bool)

AverageOf mirrors Seq.AverageOf eagerly.

func (List[T]) BottomNBy

func (l List[T]) BottomNBy[K cmp.Ordered](n int, sel func(T) K) []T

BottomNBy mirrors Seq.BottomNBy eagerly.

func (List[T]) Clone

func (l List[T]) Clone() List[T]

Clone returns a shallow copy with a fresh backing array.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	l := catena.List[int]{1, 2}
	c := l.Clone()
	c[0] = 99
	fmt.Println(l, c)
}
Output:
[1 2] [99 2]

func (List[T]) Collect

func (l List[T]) Collect() []T

Collect mirrors Seq.Collect eagerly, implemented directly over the backing slice rather than through the lazy pipeline.

func (List[T]) Concat

func (l List[T]) Concat(others ...Seq[T]) List[T]

Concat mirrors Seq.Concat eagerly.

func (List[T]) Count

func (l List[T]) Count() int

Count mirrors Seq.Count eagerly, implemented directly over the backing slice rather than through the lazy pipeline.

func (List[T]) CountWhere

func (l List[T]) CountWhere(pred func(T) bool) int

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

func (l List[T]) DistinctWith(eq func(a, b T) bool) List[T]

DistinctWith mirrors Seq.DistinctWith eagerly.

func (List[T]) Drain

func (l List[T]) Drain()

Drain mirrors Seq.Drain eagerly.

func (List[T]) Drop

func (l List[T]) Drop(n int) List[T]

Drop mirrors Seq.Drop eagerly.

func (List[T]) DropLast

func (l List[T]) DropLast(n int) List[T]

DropLast mirrors Seq.DropLast eagerly.

func (List[T]) DropWhile

func (l List[T]) DropWhile(pred func(T) bool) List[T]

DropWhile mirrors Seq.DropWhile eagerly.

func (List[T]) ElementAt

func (l List[T]) ElementAt(i int) (T, bool)

ElementAt mirrors Seq.ElementAt eagerly, implemented directly over the backing slice rather than through the lazy pipeline.

func (List[T]) Filter

func (l List[T]) Filter(pred func(T) bool) List[T]

Filter mirrors Seq.Filter eagerly.

func (List[T]) FilterErr

func (l List[T]) FilterErr(pred func(T) (bool, error)) Try[T]

FilterErr mirrors Seq.FilterErr. The list is consumed eagerly, but the result is a lazy Try — one of the four List operators that cross back to lazy (see the List type).

func (List[T]) FilterIndexed

func (l List[T]) FilterIndexed(pred func(int, T) bool) List[T]

FilterIndexed mirrors Seq.FilterIndexed eagerly.

func (List[T]) FilterMap

func (l List[T]) FilterMap[U any](f func(T) (U, bool)) List[U]

FilterMap mirrors Seq.FilterMap eagerly.

func (List[T]) FilterNot

func (l List[T]) FilterNot(pred func(T) bool) List[T]

FilterNot mirrors Seq.FilterNot eagerly.

func (List[T]) Find

func (l List[T]) Find(pred func(T) bool) (T, bool)

Find mirrors Seq.Find eagerly.

func (List[T]) FindIndex

func (l List[T]) FindIndex(pred func(T) bool) int

FindIndex mirrors Seq.FindIndex eagerly.

func (List[T]) FindLast

func (l List[T]) FindLast(pred func(T) bool) (T, bool)

FindLast mirrors Seq.FindLast eagerly.

func (List[T]) FindMap

func (l List[T]) FindMap[U any](f func(T) (U, bool)) (U, bool)

FindMap mirrors Seq.FindMap eagerly.

func (List[T]) First

func (l List[T]) First() (T, bool)

First mirrors Seq.First eagerly, implemented directly over the backing slice rather than through the lazy pipeline.

func (List[T]) FlatMap

func (l List[T]) FlatMap[U any](f func(T) Seq[U]) List[U]

FlatMap mirrors Seq.FlatMap eagerly.

func (List[T]) FlatMapSlice

func (l List[T]) FlatMapSlice[U any](f func(T) []U) List[U]

FlatMapSlice mirrors Seq.FlatMapSlice eagerly.

func (List[T]) Fold

func (l List[T]) Fold[A any](init A, f func(A, T) A) A

Fold mirrors Seq.Fold 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]) FoldErr

func (l List[T]) FoldErr[A any](init A, f func(A, T) (A, error)) (A, error)

FoldErr mirrors Seq.FoldErr eagerly.

func (List[T]) FoldIndexed

func (l List[T]) FoldIndexed[A any](init A, f func(int, A, T) A) A

FoldIndexed mirrors Seq.FoldIndexed eagerly.

func (List[T]) FoldRight

func (l List[T]) FoldRight[A any](init A, f func(T, A) A) A

FoldRight reduces right to left. It exists only on List: a right fold needs the whole sequence in memory, which a List already is.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// Exists only on List: a right fold needs the whole sequence, which
	// a List already is.
	l := catena.List[string]{"a", "b", "c"}
	fmt.Println(l.FoldRight("|", func(s, acc string) string { return "(" + s + acc + ")" }))
}
Output:
(a(b(c|)))

func (List[T]) FoldWhile

func (l List[T]) FoldWhile[A any](init A, f func(A, T) (A, bool)) A

FoldWhile mirrors Seq.FoldWhile eagerly.

func (List[T]) ForEach

func (l List[T]) ForEach(f func(T))

ForEach mirrors Seq.ForEach eagerly.

func (List[T]) ForEachErr

func (l List[T]) ForEachErr(f func(T) error) error

ForEachErr mirrors Seq.ForEachErr eagerly.

func (List[T]) ForEachIndexed

func (l List[T]) ForEachIndexed(f func(int, T))

ForEachIndexed mirrors Seq.ForEachIndexed eagerly.

func (List[T]) Get

func (l List[T]) Get(i int) (T, bool)

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]) IfEmpty

func (l List[T]) IfEmpty(defaults ...T) List[T]

IfEmpty mirrors Seq.IfEmpty 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

func (l List[T]) Intersperse(sep T) List[T]

Intersperse mirrors Seq.Intersperse eagerly.

func (List[T]) IsEmpty

func (l List[T]) IsEmpty() bool

IsEmpty mirrors Seq.IsEmpty eagerly, implemented directly over the backing slice rather than through the lazy pipeline.

func (List[T]) JoinBy

func (l List[T]) JoinBy[U any, K comparable, R any](other Seq[U], leftKey func(T) K, rightKey func(U) K, combine func(T, U) R) List[R]

JoinBy mirrors Seq.JoinBy eagerly.

func (List[T]) JoinToString

func (l List[T]) JoinToString(sep string, sel func(T) string) string

JoinToString mirrors Seq.JoinToString eagerly.

func (List[T]) Last

func (l List[T]) Last() (T, bool)

Last mirrors Seq.Last eagerly, implemented directly over the backing slice rather than through the lazy pipeline.

func (List[T]) Len

func (l List[T]) Len() int

Len returns the number of elements. O(1).

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	l := catena.List[int]{3, 1, 2}
	fmt.Println(l.Len())
}
Output:
3

func (List[T]) Map

func (l List[T]) Map[U any](f func(T) U) List[U]

Map mirrors Seq.Map eagerly, implemented directly over the backing slice rather than through the lazy pipeline.

func (List[T]) MapErr

func (l List[T]) MapErr[U any](f func(T) (U, error)) Try[U]

MapErr mirrors Seq.MapErr. The list is consumed eagerly, but the result is a lazy Try — one of the four List operators that cross back to lazy (see the List type).

func (List[T]) MapIndexed

func (l List[T]) MapIndexed[U any](f func(int, T) U) List[U]

MapIndexed mirrors Seq.MapIndexed eagerly, implemented directly over the backing slice rather than through the lazy pipeline.

func (List[T]) MaxBy

func (l List[T]) MaxBy[K cmp.Ordered](sel func(T) K) (T, bool)

MaxBy mirrors Seq.MaxBy eagerly.

func (List[T]) MaxOf

func (l List[T]) MaxOf[K cmp.Ordered](sel func(T) K) (K, bool)

MaxOf mirrors Seq.MaxOf eagerly.

func (List[T]) MaxWith

func (l List[T]) MaxWith(cmp func(a, b T) int) (T, bool)

MaxWith mirrors Seq.MaxWith eagerly.

func (List[T]) MinBy

func (l List[T]) MinBy[K cmp.Ordered](sel func(T) K) (T, bool)

MinBy mirrors Seq.MinBy eagerly.

func (List[T]) MinMaxOf

func (l List[T]) MinMaxOf[K cmp.Ordered](sel func(T) K) (min, max K, ok bool)

MinMaxOf mirrors Seq.MinMaxOf eagerly.

func (List[T]) MinOf

func (l List[T]) MinOf[K cmp.Ordered](sel func(T) K) (K, bool)

MinOf mirrors Seq.MinOf eagerly.

func (List[T]) MinWith

func (l List[T]) MinWith(cmp func(a, b T) int) (T, bool)

MinWith mirrors Seq.MinWith eagerly.

func (List[T]) None

func (l List[T]) None(pred func(T) bool) bool

None mirrors Seq.None eagerly.

func (List[T]) OnEach

func (l List[T]) OnEach(f func(T)) List[T]

OnEach mirrors Seq.OnEach eagerly.

func (List[T]) Partition

func (l List[T]) Partition(pred func(T) bool) (yes, no []T)

Partition mirrors Seq.Partition eagerly.

func (List[T]) Prepend

func (l List[T]) Prepend(vals ...T) List[T]

Prepend mirrors Seq.Prepend eagerly.

func (List[T]) ProductOf

func (l List[T]) ProductOf[N Numeric](sel func(T) N) N

ProductOf mirrors Seq.ProductOf eagerly.

func (List[T]) Reduce

func (l List[T]) Reduce(f func(T, T) T) (T, bool)

Reduce mirrors Seq.Reduce eagerly.

func (List[T]) Reversed

func (l List[T]) Reversed() List[T]

Reversed mirrors Seq.Reversed eagerly, implemented directly over the backing slice rather than through the lazy pipeline.

func (List[T]) Scan

func (l List[T]) Scan[A any](init A, f func(A, T) A) List[A]

Scan mirrors Seq.Scan eagerly.

func (List[T]) Single

func (l List[T]) Single() (T, bool)

Single mirrors Seq.Single eagerly.

func (List[T]) Slice

func (l List[T]) Slice(i, j int) List[T]

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]) SortedBy

func (l List[T]) SortedBy[K cmp.Ordered](sel func(T) K) List[T]

SortedBy mirrors Seq.SortedBy eagerly.

func (List[T]) SortedByDesc

func (l List[T]) SortedByDesc[K cmp.Ordered](sel func(T) K) List[T]

SortedByDesc mirrors Seq.SortedByDesc eagerly.

func (List[T]) SortedWith

func (l List[T]) SortedWith(cmp func(a, b T) int) List[T]

SortedWith mirrors Seq.SortedWith eagerly.

func (List[T]) Step

func (l List[T]) Step(n int) List[T]

Step mirrors Seq.Step eagerly.

func (List[T]) SumOf

func (l List[T]) SumOf[N Numeric](sel func(T) N) N

SumOf mirrors Seq.SumOf eagerly.

func (List[T]) Take

func (l List[T]) Take(n int) List[T]

Take mirrors Seq.Take eagerly.

func (List[T]) TakeLast

func (l List[T]) TakeLast(n int) List[T]

TakeLast mirrors Seq.TakeLast eagerly.

func (List[T]) TakeWhile

func (l List[T]) TakeWhile(pred func(T) bool) List[T]

TakeWhile mirrors Seq.TakeWhile eagerly.

func (List[T]) TallyBy

func (l List[T]) TallyBy[K comparable](sel func(T) K) map[K]int

TallyBy mirrors Seq.TallyBy eagerly.

func (List[T]) ToList

func (l List[T]) ToList() List[T]

ToList mirrors Seq.ToList eagerly, implemented directly over the backing slice rather than through the lazy pipeline.

func (List[T]) TopNBy

func (l List[T]) TopNBy[K cmp.Ordered](n int, sel func(T) K) []T

TopNBy mirrors Seq.TopNBy eagerly.

func (List[T]) WithIndex

func (l List[T]) WithIndex() Seq2[int, T]

WithIndex mirrors Seq.WithIndex. The list is consumed eagerly, but the result is a lazy Seq2 — one of the four List operators that cross back to lazy (see the List type).

func (List[T]) ZipWithNext

func (l List[T]) ZipWithNext() Seq2[T, T]

ZipWithNext mirrors Seq.ZipWithNext. The list is consumed eagerly, but the result is a lazy Seq2 — one of the four List operators that cross back to lazy (see the List type).

type Numeric

type Numeric interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 |
		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
		~float32 | ~float64
}

Numeric is the constraint for arithmetic aggregations (Sum, Product, Average). Complex types are deliberately excluded: they are unordered and half the aggregation surface would be meaningless for them.

type Seq

type Seq[T any] iter.Seq[T]

Seq is a lazy sequence: iter.Seq with methods. Range over it directly.

A Seq is a function, so consuming it twice runs its producer twice. Whether that works is a property of the producer alone — operators never change it. Every operator builds its state inside the returned closure, so a chain is re-iterable exactly when its root producer is, and the conformance suite re-iterates every operator to prove it. Once() is the single deliberate exception.

Re-iterable   Of, FromSlice, FromMap, Empty, Empty2, EmptyTry, Once1,
              Repeat, RepeatN, Range, Generate and GenerateWhile (iff
              next is pure)
Single-use    FromChan, and From, From2, FromErrs when the underlying
              source is (anything over I/O)
Cycle         re-iterable iff its source's first pass is

Consuming a single-use sequence a second time yields nothing rather than failing: wrap it in Once() during development to turn that into a panic. Printing a Seq shows a function pointer — print s.Collect() instead.

func Chain

func Chain[T any](seqs ...Seq[T]) Seq[T]

Chain yields each sequence's elements in order. It is the package-function form of Seq.Concat, for when you hold a slice of sequences and no receiver.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// The variadic form, for when the sequences are in a slice already.
	fmt.Println(catena.Chain(catena.Of(1), catena.Of(2), catena.Of(3)).Collect())
}
Output:
[1 2 3]

func Chunked

func Chunked[T any](s Seq[T], n int) Seq[[]T]

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

func Cycle[T any](s Seq[T]) Seq[T]

Cycle yields s over and over, forever. The first pass is buffered and replayed (⚠ unbounded memory in len(s)), so Cycle is re-iterable iff that first pass of s is. An empty s yields an empty Cycle — it terminates rather than spinning.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// Infinite — except over an empty source, which terminates rather
	// than spinning.
	fmt.Println(catena.Cycle(catena.Of("a", "b")).Take(5).Collect())
	fmt.Println(catena.Cycle(catena.Empty[string]()).Collect())
}
Output:
[a b a b a]
[]

func Dedupe

func Dedupe[T comparable](s Seq[T]) Seq[T]

Dedupe yields elements that differ from their predecessor — consecutive duplicates only, O(1) memory.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// Consecutive duplicates only. On sorted input it equals Distinct at
	// a fraction of the cost; on unsorted input the two differ.
	fmt.Println(catena.Dedupe(catena.Of(3, 3, 1, 1, 3)).Collect())
}
Output:
[3 1 3]

func Distinct

func Distinct[T comparable](s Seq[T]) Seq[T]

Distinct yields elements not seen before; the first occurrence wins.

Distinct is a package function, not a method, and so are the others in this file: they constrain the element type (comparable, cmp.Ordered, Numeric), and a method on Seq[T any] may require nothing of T — or, for the Flatten family, they constrain the receiver's shape. Every comparable-constrained function panics at runtime if T is an interface type holding a non-comparable value. The chain continues normally after them: catena.Distinct(s).Filter(f).

On input already sorted by the compared value, Dedupe is the O(1)-memory equivalent. ⚠ Retains one entry per distinct value — unbounded.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// Distinct constrains the element type, so it is a package function
	// rather than a method — a method on Seq[T any] may require nothing
	// of T. First occurrence wins, and encounter order is preserved.
	fmt.Println(catena.Distinct(catena.Of(3, 1, 3, 2, 1)).Collect())
}
Output:
[3 1 2]

func Empty

func Empty[T any]() Seq[T]

Empty returns the empty Seq. Re-iterable.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	fmt.Println(catena.Empty[int]().Collect(), catena.Empty[int]().Count())
}
Output:
[] 0

func Except

func Except[T comparable](a, b Seq[T]) Seq[T]

Except yields the distinct elements of a that do not occur in b, in a's encounter order. ⚠ Buffers all of b before a is consumed, plus a seen- set of a's distinct values.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	fmt.Println(catena.Except(catena.Of(1, 2, 3), catena.Of(2)).Collect())
}
Output:
[1 3]

func Flatten

func Flatten[T any](s Seq[Seq[T]]) Seq[T]

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

func FlattenSlices[T any](s Seq[[]T]) Seq[T]

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

func From[T any](seq func(func(T) bool)) Seq[T]

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

func FromChan[T any](ctx context.Context, ch <-chan T) Seq[T]

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

func FromSlice[T any](s []T) Seq[T]

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

func Generate[T any](seed T, next func(T) T) Seq[T]

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

func GenerateWhile[T any](seed T, next func(T) (T, bool)) Seq[T]

GenerateWhile yields seed unconditionally, then successive next values until next reports false. Re-iterable iff next is pure.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// The seed is yielded unconditionally; a value produced alongside
	// ok=false is not.
	fmt.Println(catena.GenerateWhile(1, func(n int) (int, bool) {
		return n * 3, n < 9
	}).Collect())
}
Output:
[1 3 9]

func Intersect

func Intersect[T comparable](a, b Seq[T]) Seq[T]

Intersect yields the distinct elements of a that occur in b, in a's encounter order. ⚠ Buffers all of b before a is consumed, plus a seen- set of a's distinct values.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	fmt.Println(catena.Intersect(catena.Of(1, 2, 3), catena.Of(3, 1)).Collect())
}
Output:
[1 3]

func NonZero

func NonZero[T comparable](s Seq[T]) Seq[T]

NonZero yields the elements that are not the zero value of T.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// Drops the zero value of T — empty strings here, but equally 0,
	// nil pointers, or a zero struct.
	fmt.Println(catena.NonZero(catena.Of("go", "", "rust", "")).Collect())
}
Output:
[go rust]

func Of

func Of[T any](vals ...T) Seq[T]

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

func Once1[T any](v T) Seq[T]

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

func Range[I Integer](start, stop, step I) Seq[I]

Range yields start, start+step, ... while the value is before stop (exclusive). Re-iterable. step == 0 panics at construction; a sign mismatch between step and the start→stop direction yields an empty sequence. Termination is overflow-guarded: a step past the type's edge stops rather than wrapping. Unsigned types cannot step downward.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// Half-open, like a slice expression. A sign mismatch between step
	// and direction yields nothing rather than panicking, so a computed
	// step is safe.
	fmt.Println(catena.Range(0, 10, 3).Collect())
	fmt.Println(catena.Range(3, 0, -1).Collect())
	fmt.Println(catena.Range(0, 10, -1).Collect())
}
Output:
[0 3 6 9]
[3 2 1]
[]

func Repeat

func Repeat[T any](v T) Seq[T]

Repeat yields v forever. Re-iterable. Infinite: pair with Take or a conditional terminal.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// Infinite, so it must be bounded by something downstream.
	fmt.Println(catena.Repeat("ha").Take(3).Collect())
}
Output:
[ha ha ha]

func RepeatN

func RepeatN[T any](v T, n int) Seq[T]

RepeatN yields v exactly n times. Re-iterable. Panics if n is negative.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	fmt.Println(catena.RepeatN(0, 4).Collect())
}
Output:
[0 0 0 0]

func Sorted

func Sorted[T cmp.Ordered](s Seq[T]) Seq[T]

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

func SortedDesc[T cmp.Ordered](s Seq[T]) Seq[T]

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

func Windowed[T any](s Seq[T], size, step int) Seq[[]T]

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

func (s Seq[T]) All(pred func(T) bool) bool

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

func (s Seq[T]) Any(pred func(T) bool) bool

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

func (s Seq[T]) Append(vals ...T) Seq[T]

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

func (s Seq[T]) AverageOf[N Numeric](sel func(T) N) (float64, bool)

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

func (s Seq[T]) BottomNBy[K cmp.Ordered](n int, sel func(T) K) []T

BottomNBy returns the n elements with the smallest keys, sorted ascending by key; equal keys retain encounter order. Memory is O(n) — the streaming alternative to SortedBy().Take(n). catena.BottomN(s, n) is the no-selector form. ⚠ Full drain. Panics if n is negative.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	fmt.Println(catena.Of(5, 1, 9, 3, 7).BottomNBy(2, catena.Self[int]))
}
Output:
[1 3]

func (Seq[T]) Collect

func (s Seq[T]) Collect() []T

Collect drains the sequence into a slice; nil for empty. ToList is the same drain returning a List, which carries the eager operator set. ⚠ Full drain.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// nil for an empty sequence, matching slices.Collect.
	fmt.Println(catena.Of(1, 2).Collect(), catena.Empty[int]().Collect() == nil)
}
Output:
[1 2] true

func (Seq[T]) Concat

func (s Seq[T]) Concat(others ...Seq[T]) Seq[T]

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

func (s Seq[T]) Count() int

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

func (s Seq[T]) CountWhere(pred func(T) bool) int

CountWhere returns the number of elements pred admits. ⚠ Full drain.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// Fused filter and count: one stage rather than two.
	fmt.Println(catena.Range(1, 11, 1).CountWhere(func(n int) bool { return n%3 == 0 }))
}
Output:
3

func (Seq[T]) DedupeBy

func (s Seq[T]) DedupeBy[K comparable](sel func(T) K) Seq[T]

DedupeBy yields elements whose key differs from the previous element's key — consecutive duplicates only, O(1) memory. On key-sorted input it equals DistinctBy at a fraction of the cost.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// Collapses CONSECUTIVE runs only, in O(1) memory — the streaming
	// alternative to DistinctBy, and the right choice on an unbounded
	// source where a seen-set would grow forever.
	type reading struct {
		Tick int
		Zone string
	}
	readings := catena.Of(
		reading{1, "cold"}, reading{2, "cold"},
		reading{3, "warm"}, reading{4, "cold"},
	)

	fmt.Println(readings.
		DedupeBy(func(r reading) string { return r.Zone }).
		Collect())
}
Output:
[{1 cold} {3 warm} {4 cold}]

func (Seq[T]) DistinctBy

func (s Seq[T]) DistinctBy[K comparable](sel func(T) K) Seq[T]

DistinctBy yields elements whose key has not been seen before; the first occurrence wins. catena.Distinct(s) is the no-selector form, for a comparable T. On key-sorted input DedupeBy is the O(1)-memory equivalent. ⚠ Retains one key per distinct value — unbounded.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	type user struct {
		Org, Name string
	}
	users := catena.Of(
		user{"acme", "ada"},
		user{"globex", "bob"},
		user{"acme", "eve"},
	)

	// One user per org; the first occurrence wins.
	fmt.Println(users.
		DistinctBy(func(u user) string { return u.Org }).
		Collect())
}
Output:
[{acme ada} {globex bob}]

func (Seq[T]) DistinctWith

func (s Seq[T]) DistinctWith(eq func(a, b T) bool) Seq[T]

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

func (s Seq[T]) Drop(n int) Seq[T]

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

func (s Seq[T]) DropLast(n int) Seq[T]

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

func (s Seq[T]) DropWhile(pred func(T) bool) Seq[T]

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

func (s Seq[T]) ElementAt(i int) (T, bool)

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

func (s Seq[T]) Filter(pred func(T) bool) Seq[T]

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

func (s Seq[T]) FilterErr(pred func(T) (bool, error)) Try[T]

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

func (s Seq[T]) FilterIndexed(pred func(int, T) bool) Seq[T]

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

func (s Seq[T]) FilterMap[U any](f func(T) (U, bool)) Seq[U]

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

func (s Seq[T]) FilterNot(pred func(T) bool) Seq[T]

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

func (s Seq[T]) Find(pred func(T) bool) (T, bool)

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

func (s Seq[T]) FindIndex(pred func(T) bool) int

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

func (s Seq[T]) FindLast(pred func(T) bool) (T, bool)

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

func (s Seq[T]) FindMap[U any](f func(T) (U, bool)) (U, bool)

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

func (s Seq[T]) First() (T, bool)

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

func (s Seq[T]) FlatMap[U any](f func(T) Seq[U]) Seq[U]

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

func (s Seq[T]) FlatMapSlice[U any](f func(T) []U) Seq[U]

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

func (s Seq[T]) Fold[A any](init A, f func(A, T) A) A

Fold reduces the sequence into an accumulator, left to right.

Reduce is the same operation using the first element as the initial accumulator; FoldBy folds per key in one streaming pass. ⚠ Full drain.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	fmt.Println(catena.Of(1, 2, 3).Fold(100, func(acc, n int) int { return acc + n }))
}
Output:
106

func (Seq[T]) FoldBy

func (s Seq[T]) FoldBy[K comparable, A any](
	key func(T) K, init func(K) A, f func(A, T) A,
) map[K]A

FoldBy folds each element into a per-key accumulator: streaming grouped aggregation with no intermediate per-key slices. init is called once per distinct key. Memory is bounded by the number of distinct keys, not elements. ⚠ Full drain; map iteration order is undefined.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// Streaming aggregation per key. GroupBy would retain every element;
	// this retains one accumulator per distinct key.
	type sale struct {
		Region string
		Amount int
	}
	sales := catena.Of(
		sale{"west", 100}, sale{"east", 40}, sale{"west", 20},
	)
	totals := sales.FoldBy(
		func(s sale) string { return s.Region },
		func(string) int { return 0 },
		func(sum int, s sale) int { return sum + s.Amount },
	)
	fmt.Println(totals["west"], totals["east"])
}
Output:
120 40

func (Seq[T]) FoldErr

func (s Seq[T]) FoldErr[A any](init A, f func(A, T) (A, error)) (A, error)

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

func (s Seq[T]) FoldIndexed[A any](init A, f func(int, A, T) A) A

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

func (s Seq[T]) FoldWhile[A any](init A, f func(A, T) (A, bool)) A

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

func (s Seq[T]) ForEachErr(f func(T) error) error

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

func (s Seq[T]) ForEachIndexed(f func(int, T))

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

func (s Seq[T]) IfEmpty(defaults ...T) Seq[T]

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

func (s Seq[T]) Intersperse(sep T) Seq[T]

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

func (s Seq[T]) IsEmpty() bool

IsEmpty reports whether the sequence yields nothing. ⚠ It does so by consuming one element — on a single-pass source that element is lost.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// Answers by consuming one element — on a single-pass source that
	// element is gone.
	fmt.Println(catena.Empty[int]().IsEmpty(), catena.Of(1).IsEmpty())
}
Output:
true false

func (Seq[T]) JoinBy

func (s Seq[T]) JoinBy[U any, K comparable, R any](
	other Seq[U],
	leftKey func(T) K, rightKey func(U) K,
	combine func(T, U) R,
) Seq[R]

JoinBy is a relational inner join: it pairs each element of s with every element of other sharing the same key and yields combine for each pair. It is unrelated to Join and JoinToString, which concatenate strings. Unmatched elements on either side are dropped; duplicate keys produce the cross product per key. Output order is left encounter order, then right encounter order within a key. ⚠ Buffers all of other before the first emission.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// A relational inner join: unmatched rows on either side are dropped,
	// and duplicate keys produce the cross product.
	type order struct {
		Customer int
		Amount   int
	}
	type customer struct {
		ID   int
		Name string
	}
	orders := catena.Of(order{1, 30}, order{2, 10}, order{9, 99})
	customers := catena.Of(customer{1, "ada"}, customer{2, "bob"})

	fmt.Println(orders.JoinBy(customers,
		func(o order) int { return o.Customer },
		func(c customer) int { return c.ID },
		func(o order, c customer) string { return fmt.Sprintf("%s:%d", c.Name, o.Amount) },
	).Collect())
}
Output:
[ada:30 bob:10]

func (Seq[T]) JoinToString

func (s Seq[T]) JoinToString(sep string, sel func(T) string) string

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

func (s Seq[T]) Last() (T, bool)

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

func (s Seq[T]) Map[U any](f func(T) U) Seq[U]

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

func (s Seq[T]) MapErr[U any](f func(T) (U, error)) Try[U]

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

func (s Seq[T]) MapIndexed[U any](f func(int, T) U) Seq[U]

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

func (s Seq[T]) MaxBy[K cmp.Ordered](sel func(T) K) (T, bool)

MaxBy returns the element with the largest key; the earliest maximal element wins ties. NaN keys order below everything (cmp.Compare). MaxOf returns the key itself; catena.Max(s) is the no-selector form. ⚠ Full drain.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// Returns the ELEMENT with the largest key; ties go to the first.
	type run struct {
		Name string
		Secs int
	}
	runs := catena.Of(run{"ada", 42}, run{"bob", 51}, run{"eve", 51})
	slowest, _ := runs.MaxBy(func(r run) int { return r.Secs })
	fmt.Println(slowest.Name)
}
Output:
bob

func (Seq[T]) MaxOf

func (s Seq[T]) MaxOf[K cmp.Ordered](sel func(T) K) (K, bool)

MaxOf returns the largest key — MaxBy returns the element that carries it. ⚠ Full drain.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// Returns the KEY, where MaxBy returns the element — the -By/-Of
	// distinction, which holds across the whole library.
	longest, _ := catena.Of("go", "rust", "c").MaxOf(func(s string) int { return len(s) })
	fmt.Println(longest)
}
Output:
4

func (Seq[T]) MaxWith

func (s Seq[T]) MaxWith(cmp func(a, b T) int) (T, bool)

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

func (s Seq[T]) MinBy[K cmp.Ordered](sel func(T) K) (T, bool)

MinBy returns the element with the smallest key; the earliest minimal element wins ties. MinOf returns the key itself; catena.Min(s) is the no-selector form. ⚠ Full drain.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	type run struct {
		Name string
		Secs int
	}
	runs := catena.Of(run{"ada", 42}, run{"bob", 51})
	fastest, _ := runs.MinBy(func(r run) int { return r.Secs })
	fmt.Println(fastest.Name)
}
Output:
ada

func (Seq[T]) MinMaxOf

func (s Seq[T]) MinMaxOf[K cmp.Ordered](sel func(T) K) (min, max K, ok bool)

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

func (s Seq[T]) MinOf[K cmp.Ordered](sel func(T) K) (K, bool)

MinOf returns the smallest key — MinBy returns the element that carries it. ⚠ Full drain.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	shortest, _ := catena.Of("go", "rust", "c").MinOf(func(s string) int { return len(s) })
	fmt.Println(shortest)
}
Output:
1

func (Seq[T]) MinWith

func (s Seq[T]) MinWith(cmp func(a, b T) int) (T, bool)

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

func (s Seq[T]) None(pred func(T) bool) bool

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

func (s Seq[T]) OnEach(f func(T)) Seq[T]

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

func (s Seq[T]) Once() Seq[T]

Once returns a sequence that panics if iterated more than once — a development guard for the single-pass contract, not a synchronization mechanism. This is the one operator whose state deliberately lives outside the iteration closure. (catena.Once1 is unrelated: it constructs a one-element sequence.)

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// A development guard for the single-pass contract: the second
	// consumption panics instead of silently re-running the producer.
	s := catena.Of(1, 2).Once()
	fmt.Println(s.Collect())

	defer func() { fmt.Println("recovered:", recover()) }()
	s.Collect()
}
Output:
[1 2]
recovered: catena: Once: sequence consumed more than once

func (Seq[T]) Partition

func (s Seq[T]) Partition(pred func(T) bool) (yes, no []T)

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

func (s Seq[T]) Prepend(vals ...T) Seq[T]

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

func (s Seq[T]) ProductOf[N Numeric](sel func(T) N) N

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

func (s Seq[T]) Pull() (next func() (T, bool), stop func())

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

func (s Seq[T]) Reduce(f func(T, T) T) (T, bool)

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

func (s Seq[T]) Reversed() Seq[T]

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

func (s Seq[T]) Scan[A any](init A, f func(A, T) A) Seq[A]

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

func (s Seq[T]) Seq() iter.Seq[T]

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

func (s Seq[T]) Single() (T, bool)

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

func (s Seq[T]) SortedBy[K cmp.Ordered](sel func(T) K) Seq[T]

SortedBy yields the elements sorted ascending by key, stably. sel is called exactly once per element (decorate-sort-undecorate). catena.Sorted(s) is the no-selector form, for an ordered T. ⚠ Buffers the entire input — hangs on infinite input.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// Stable, and the selector runs exactly once per element rather than
	// once per comparison — so an expensive key is affordable. Stability
	// shows here: kiwi and date are both 4 long, and kiwi came first.
	words := catena.Of("kiwi", "fig", "banana", "date")
	fmt.Println(words.SortedBy(func(s string) int { return len(s) }).Collect())
}
Output:
[fig kiwi date banana]

func (Seq[T]) SortedByDesc

func (s Seq[T]) SortedByDesc[K cmp.Ordered](sel func(T) K) Seq[T]

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

func (s Seq[T]) SortedWith(cmp func(a, b T) int) Seq[T]

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

func (s Seq[T]) Step(n int) Seq[T]

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

func (s Seq[T]) SumOf[N Numeric](sel func(T) N) N

SumOf sums the selected values; integer overflow wraps like +. Empty input sums to 0. catena.Sum(s) is the no-selector form. ⚠ Full drain.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	type item struct{ Qty int }
	items := catena.Of(item{2}, item{3})
	fmt.Println(items.SumOf(func(i item) int { return i.Qty }))
}
Output:
5

func (Seq[T]) Take

func (s Seq[T]) Take(n int) Seq[T]

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

func (s Seq[T]) TakeLast(n int) Seq[T]

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

func (s Seq[T]) TakeWhile(pred func(T) bool) Seq[T]

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

func (s Seq[T]) ToChan(ctx context.Context) <-chan T

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

func (s Seq[T]) ToList() List[T]

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

func (s Seq[T]) TopNBy[K cmp.Ordered](n int, sel func(T) K) []T

TopNBy returns the n elements with the largest keys, sorted descending by key; equal keys retain encounter order and the earliest elements win at the cut. Memory is O(n) — the streaming alternative to SortedByDesc().Take(n). catena.TopN(s, n) is the no-selector form, and BottomNBy is the same operation at the other end. ⚠ Full drain. Panics if n is negative.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	// A bounded heap of n, not a sort: memory is O(n) rather than O(all),
	// which is the difference between a working pipeline and an OOM on a
	// large scan. Output is sorted descending, ties in encounter order.
	fmt.Println(catena.Of(5, 1, 9, 3, 7).TopNBy(3, catena.Self[int]))
}
Output:
[9 7 5]

func (Seq[T]) UntilDone

func (s Seq[T]) UntilDone(ctx context.Context) Try[T]

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

func (s Seq[T]) WithIndex() Seq2[int, T]

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

func (s Seq[T]) Zip[U any](other Seq[U]) Seq2[T, U]

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

func (s Seq[T]) ZipWithNext() Seq2[T, T]

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

type Seq2[K, V any] iter.Seq2[K, V]

Seq2 is a lazy pair sequence: iter.Seq2 with methods. It is a bridge back to Seq (via Keys, Values, MapTo), deliberately not a full peer surface.

Deliberately absent, so nobody goes looking:

  • ToMap: needs K comparable on the receiver. Use catena.CollectMap.
  • Collect: catena.Unzip is the one way to two slices.
  • MapKeys: Swap().MapValues(f).Swap() for the rare need; Map otherwise.

func Empty2

func Empty2[K, V any]() Seq2[K, V]

Empty2 returns the empty Seq2. Re-iterable.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	fmt.Println(catena.Empty2[string, int]().Count())
}
Output:
0

func From2

func From2[K, V any](seq func(func(K, V) bool)) Seq2[K, V]

From2 adapts any push-function pair sequence. Re-iterable iff the underlying source is.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	pairs := catena.From2(func(yield func(string, int) bool) {
		yield("a", 1)
		yield("b", 2)
	})
	fmt.Println(pairs.MapTo(func(k string, v int) string {
		return fmt.Sprintf("%s=%d", k, v)
	}).Collect())
}
Output:
[a=1 b=2]

func FromMap

func FromMap[K comparable, V any](m map[K]V) Seq2[K, V]

FromMap returns a re-iterable Seq2 over m, in undefined (map) order.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	ages := map[string]int{"ada": 36}
	fmt.Println(catena.CollectMap(catena.FromMap(ages)))
}
Output:
map[ada:36]

func (Seq2[K, V]) All

func (s Seq2[K, V]) All(pred func(K, V) bool) bool

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

func (s Seq2[K, V]) Any(pred func(K, V) bool) bool

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

func (s Seq2[K, V]) Count() int

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

func (s Seq2[K, V]) Drop(n int) Seq2[K, V]

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

func (s Seq2[K, V]) Filter(pred func(K, V) bool) Seq2[K, V]

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

func (s Seq2[K, V]) FilterNot(pred func(K, V) bool) Seq2[K, V]

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

func (s Seq2[K, V]) First() (K, V, bool)

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

func (s Seq2[K, V]) Fold[A any](init A, f func(A, K, V) A) A

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

func (s Seq2[K, V]) Keys() Seq[K]

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

func (s Seq2[K, V]) Map[K2, V2 any](f func(K, V) (K2, V2)) Seq2[K2, V2]

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

func (s Seq2[K, V]) MapTo[U any](f func(K, V) U) Seq[U]

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

func (s Seq2[K, V]) MapValues[V2 any](f func(K, V) V2) Seq2[K, V2]

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

func (s Seq2[K, V]) Pull() (next func() (K, V, bool), stop func())

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

func (s Seq2[K, V]) Seq2() iter.Seq2[K, V]

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

func (s Seq2[K, V]) Swap() Seq2[V, K]

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]

func (Seq2[K, V]) Take

func (s Seq2[K, V]) Take(n int) Seq2[K, V]

Take yields at most 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, 9, 1).WithIndex().Take(3).Values().Collect())
}
Output:
[1 2 3]

func (Seq2[K, V]) Values

func (s Seq2[K, V]) Values() Seq[V]

Values yields the second element of each pair.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	fmt.Println(catena.Of("a", "b").WithIndex().Values().Collect())
}
Output:
[a b]

type Try

type Try[T any] iter.Seq2[T, error]

Try is a lazy sequence of fallible elements: iter.Seq2[T, error] with methods. When err != nil the value must not be read.

Try carries a small operator set on purpose — it is not a second Seq. The intended shape is to stay in Try only while errors are still in play, then commit to a policy (Ignore, Must, Collect, CollectAll) and continue on Seq with the full API. Wanting Sorted or GroupBy on a Try means the errors have been carried one stage too far.

Its operators follow five uniform rules, referred to below as R1–R5:

R1  Intermediates never inspect errored elements: predicates and map
    functions are not called on them; the element flows through.
R2  The positional intermediates (Take, Drop) count elements, errored
    or not, so Take(n) consumes at most n of the source.
    Ignore().Take(n) is the "n successes" spelling. Count is a
    terminal and follows R5 instead.
R3  An errored element passes through TakeWhile without terminating
    the sequence; only a successful element failing pred ends it.
R4  Operators that generate an error yield (zero, err).
R5  Single-error terminals (Collect, Fold, ForEach, Err, Count) stop
    consuming at the first error.

func EmptyTry

func EmptyTry[T any]() Try[T]

EmptyTry returns the empty Try. Re-iterable.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	vals, err := catena.EmptyTry[int]().Collect()
	fmt.Println(vals, err)
}
Output:
[] <nil>

func FromErrs

func FromErrs[T any](seq func(func(T, error) bool)) Try[T]

FromErrs adapts any push-function fallible sequence. Re-iterable iff the underlying source is.

Example
package main

import (
	"fmt"

	"github.com/NerdMeNot/catena"
)

func main() {
	rows := catena.FromErrs(func(yield func(int, error) bool) {
		yield(1, nil)
		yield(0, fmt.Errorf("row 2: corrupt"))
	})
	vals, err := rows.Collect()
	fmt.Println(vals, err)
}
Output:
[1] row 2: corrupt

func (Try[T]) Collect

func (t Try[T]) Collect() ([]T, error)

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

func (t Try[T]) CollectAll() ([]T, []error)

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

func (t Try[T]) Count() (int, error)

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

func (t Try[T]) Drop(n int) Try[T]

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

func (t Try[T]) Err() error

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

func (t Try[T]) Errs() Seq[error]

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

func (t Try[T]) Filter(pred func(T) bool) Try[T]

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

func (t Try[T]) FilterErr(pred func(T) (bool, error)) Try[T]

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

func (t Try[T]) FlatMap[U any](f func(T) Try[U]) Try[U]

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

func (t Try[T]) Fold[A any](init A, f func(A, T) A) (A, error)

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

func (t Try[T]) ForEach(f func(T) error) error

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

func (t Try[T]) Ignore() Seq[T]

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

func (t Try[T]) Map[U any](f func(T) U) Try[U]

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

func (t Try[T]) MapErr[U any](f func(T) (U, error)) Try[U]

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

func (t Try[T]) Must() Seq[T]

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

func (t Try[T]) OnEach(f func(T)) Try[T]

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

func (t Try[T]) OnError(f func(error)) Try[T]

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

func (t Try[T]) Pull() (next func() (T, error, bool), stop func())

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

func (t Try[T]) Recover(f func(error) (T, bool)) Try[T]

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

func (t Try[T]) Seq2() iter.Seq2[T, error]

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

func (t Try[T]) Take(n int) Try[T]

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

func (t Try[T]) TakeWhile(pred func(T) bool) Try[T]

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

func (t Try[T]) UntilDone(ctx context.Context) Try[T]

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

func (t Try[T]) WrapErr(f func(error) error) Try[T]

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

Directories

Path Synopsis
examples
01-basics command
Basics: building a pipeline, what laziness means in practice, and how catena sequences interoperate with plain range loops and iter.Seq.
Basics: building a pipeline, what laziness means in practice, and how catena sequences interoperate with plain range loops and iter.Seq.
02-grouping command
Grouping: FoldBy is the library's centerpiece — streaming aggregation bounded by distinct keys, not elements.
Grouping: FoldBy is the library's centerpiece — streaming aggregation bounded by distinct keys, not elements.
03-selection command
Selection: finding extremes and top-k without sorting the world.
Selection: finding extremes and top-k without sorting the world.
04-errors command
Errors: one parse pipeline, three policies.
Errors: one parse pipeline, three policies.
05-resources command
Resources: a producer that owns something — here a real temp file — opens it lazily inside the iteration closure.
Resources: a producer that owns something — here a real temp file — opens it lazily inside the iteration closure.
06-streaming command
Streaming: sequences with no end, and the operators that stay safe on them — running state with Scan, moving averages with Windowed, batching with Chunked, and change detection with DedupeBy.
Streaming: sequences with no end, and the operators that stay safe on them — running state with Scan, moving averages with Windowed, batching with Chunked, and change detection with DedupeBy.
07-join command
Join: JoinBy is a relational inner join between two streams of structs — the right side is indexed by key, the left side streams past it, and the joined stream is a plain Seq you keep chaining on.
Join: JoinBy is a relational inner join between two streams of structs — the right side is indexed by key, the left side streams past it, and the joined stream is a plain Seq you keep chaining on.
08-list command
List and the Seq2 bridge: when the data is small and already in memory, eager evaluation with exact preallocation beats a lazy chain — and the two worlds convert explicitly, in both directions.
List and the Seq2 bridge: when the data is small and already in memory, eager evaluation with exact preallocation beats a lazy chain — and the two worlds convert explicitly, in both directions.
internal
gen/assets command
Command assets generates the catena mark as SVG.
Command assets generates the catena mark as SVG.
gen/listgen command
Command listgen generates list_gen.go: the eager List[T] mirror of the Seq[T] operation set.
Command listgen generates list_gen.go: the eager List[T] mirror of the Seq[T] operation set.
gen/opdocs command
Command opdocs generates the operator reference in docs/operators/.
Command opdocs generates the operator reference in docs/operators/.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL