seq

package
v0.46.0 Latest Latest
Warning

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

Go to latest
Published: Mar 19, 2026 License: MIT Imports: 5 Imported by: 0

README

seq

Fluent chains on Go's iter.Seq — lazy, re-evaluating, stdlib-compatible.

Use seq when you want laziness, early termination, or iter.Seq interop. Prefer slice for eager in-memory collection work. For memoized lazy sequences, use stream.

// Before: manual loop to filter an iterator
var active []string
for k := range maps.Keys(configs) {
    if isActive(k) {
        active = append(active, k)
    }
}

// After: fluent chain on the iterator
active := seq.FromIter(maps.Keys(configs)).KeepIf(isActive).Collect()

What It Looks Like

// Lazy filter + limit — stops after finding 5 matches
top5 := seq.From(items).KeepIf(Item.IsActive).Take(5).Collect()
// Infinite sequence — always use Take or TakeWhile before a terminal op
naturals := seq.Generate(0, func(n int) int { return n + 1 })
first10 := naturals.Take(10).Collect()
// Range works directly — no .Iter() needed
for v := range seq.From(data).KeepIf(isValid) {
    fmt.Println(v)
}
// .Iter() when passing to functions expecting iter.Seq
slices.Collect(seq.From(data).KeepIf(isValid).Iter())
// Cross-type map (standalone — Go methods can't introduce type params)
names := seq.Map(users, User.Name).Collect()
// FlatMap — expand each item into a sub-sequence and flatten
allTags := seq.FlatMap(items, Item.Tags).Collect()
// Zip — pair corresponding elements from two sequences
ranked := seq.Zip(seq.From(names), seq.From(scores)).Collect()
// Scan — running totals as a lazy sequence
totals := seq.Scan(seq.From(amounts), 0.0, sumFloat64).Collect()
// FilterMap — filter and transform in one pass
parsed := seq.FilterMap(lines, parseLine).Collect()
// Reduce — combine without an initial value
sum := seq.From(amounts).Reduce(add).OrZero()
// Unique — deduplicate lazily
distinct := seq.Unique(seq.From(ids)).Collect()
// Intersperse — insert separator between elements
csv := seq.From(fields).Intersperse(",").Collect()
// Chunk — process in batches
batches := seq.Chunk(seq.From(records), 100)
for batch := range batches {
    processBatch(batch)
}
// Contains — short-circuit membership check
if seq.Contains(seq.From(allowed), userRole) {
    grant()
}
// FromChannel — bridge a channel to a lazy Seq
events := seq.FromChannel(ctx, eventCh).KeepIf(Event.IsImportant).Take(10).Collect()
// ToChannel — bridge a Seq pipeline to a channel
out := seq.From(items).Convert(transform).ToChannel(ctx, 0)
for v := range out {
    process(v)
}

Re-Evaluation

Seq pipelines re-evaluate on every terminal call. There is no caching:

evens := seq.From(numbers).KeepIf(isEven)
a := evens.Collect()  // runs the filter
b := evens.Collect()  // runs the filter again

This means seq pipelines are lightweight descriptions — no hidden state, no memoization overhead. But if the source is expensive or has side effects, each terminal call pays the full cost. Replayability depends on the source — FromIter wraps the given iter.Seq as-is, and stateful or single-use sources may not produce the same results on re-invocation.

For cached evaluation, use stream instead.

Behavior Notes

The zero value of Seq[T] is nil. It is not safe for direct range — use Empty, From, or other constructors. All constructors and Seq-returning operations return non-nil Seqs safe for range. Lazy operations are nil-safe on the receiver and return empty (non-nil) Seqs, enabling safe chaining. From(nil) and From([]T{}) both return empty Seqs. Collect() on a nil Seq returns nil.

Every and None return true on empty or nil input (vacuous truth). Find and Reduce return option.Option[T] — not-ok if no match/element is found.

Convert is a same-type transform (method). Map is a cross-type transform (standalone, because Go methods can't introduce additional type parameters).

Non-termination: Collect, Each, Fold, and Reduce on infinite sequences will not terminate. Contains terminates only if a match is found. Always use Take or TakeWhile to bound infinite sequences before calling a terminal operation.

Zip left-consumption bias: Zip drives iteration from the first sequence. If the second sequence is shorter, one extra element from the first is consumed before exhaustion is detected. For side-effectful or single-use sources, be aware of this asymmetry.

All callback-taking functions panic on nil callbacks. FlatMap treats nil inner Seqs as empty. Chunk panics on size <= 0.

Channel adapters: FromChannel and ToChannel are the only seq operations that accept context.Context. FromChannel captures the context at construction time — cancellation scope is fixed, not per-iteration. ToChannel spawns a goroutine that closes the returned channel when done. Cancellation is cooperative: context is checked at yield/send boundaries, not preemptively. A blocked upstream Seq cannot be interrupted by cancellation. See the godoc for full semantics.

Stateful lazy operations: Unique, UniqueBy, Chunk, and Intersperse allocate state (seen maps, buffers, flags) inside the iteration closure. Each iteration starts fresh — safe for repeated use. However, the source sequence re-evaluates on each iteration.

Memory growth: Unique and UniqueBy maintain a seen-set that grows with the number of distinct elements/keys. On infinite or high-cardinality streams, memory may grow without bound. On infinite repeating streams, they stall once all distinct values have been emitted — requesting more elements than distinct values exist will never terminate (e.g., Unique(cycle(1,2,3)).Take(4)). Chunk buffers at most size elements.

When to Use Seq vs Stream vs Slice

seq stream slice
Evaluation Lazy, re-evaluates each terminal call Lazy, memoized (cached) Eager (immediate)
Persistence Re-invokes source on each terminal call Persistent — forced cells are shared Persistent — slices are values
Memory No intermediate collections during lazy chaining Retains forced cells Full slice per step
Best for Stdlib interop, laziness, early termination Shared evaluation, infinite sequences Finite in-memory collections
Interop iter.Seq[T] (Go stdlib) .Seq() bridge to iter.Seq []T (Go native)

Operations

Create: From, FromIter, Of, Generate, Repeat, Unfold, FromNext, FromChannel, Empty

Lazy (return Seq): KeepIf, RemoveIf, Convert (same-type), Intersperse, Take, Drop, TakeWhile, DropWhile, Map (cross-type, standalone), FilterMap (standalone), FlatMap (standalone), Concat (standalone), Enumerate (standalone), Zip (standalone), Scan (standalone), Unique (standalone), UniqueBy (standalone), Chunk (standalone)

Terminal (force evaluation): Collect, Find (returns option.Option[T]), Reduce (returns option.Option[T]), Any, Every, None, Each, Fold (standalone), Contains (standalone), ToChannel (spawns goroutine)

Unwrap: Iter — return to iter.Seq[T] for stdlib interop

See pkg.go.dev for complete API documentation, the main README for installation, and stream for memoized lazy sequences.

Documentation

Overview

Package seq provides lazy iterator operations on iter.Seq[T] with method chaining.

Seq[T] wraps iter.Seq[T] to enable fluent pipelines. Unlike stream.Stream (memoized), Seq pipelines re-evaluate on each Collect or range. Use .Iter() to unwrap back to iter.Seq[T] for interop with stdlib and other libraries.

Range works directly — no .Iter() needed for for-range loops.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Contains

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

Contains returns true if target is in the sequence. Short-circuits on first match. On infinite sequences, terminates only if a match is found. Note: for float types, NaN != NaN, so Contains(s, NaN) is always false. Standalone because the comparable constraint cannot be expressed on the Seq[T any] receiver.

func Fold

func Fold[T, R any](s Seq[T], initial R, fn func(R, T) R) R

Fold reduces a Seq to a single value by applying fn to an accumulator and each element. Requires a finite sequence. Standalone because Go methods cannot introduce additional type parameters. Panics if fn is nil.

Types

type Seq

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

Seq is a lazy iterator over iter.Seq[T] with method chaining. Range works directly:

for v := range seq.From(data).KeepIf(pred) { ... }

Unlike stream.Stream (memoized), Seq pipelines re-evaluate on each Collect or range — standard iter.Seq behavior.

The zero value (nil) is NOT safe for direct range — it will panic. Use Empty, From, or other constructors. All constructors and Seq-returning operations return non-nil Seqs safe for range.

Use .Iter() when a function expects iter.Seq[T].

func Chunk

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

Chunk groups elements into slices of at most size elements. The last chunk may have fewer than size elements. Each emitted slice is a stable snapshot with independent backing storage. Buffers up to size elements. Works with infinite sequences. Panics if size <= 0.

func Concat

func Concat[T any](a, b Seq[T]) Seq[T]

Concat returns a Seq that yields all elements of a followed by all elements of b.

func Empty

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

Empty returns a Seq that yields no elements. Safe for direct range.

func Enumerate

func Enumerate[T any](s Seq[T]) Seq[pair.Pair[int, T]]

Enumerate pairs each element with its zero-based index, lazily. The index resets on each iteration. Safe for repeated use.

func FilterMap

func FilterMap[T, R any](s Seq[T], fn func(T) (R, bool)) Seq[R]

FilterMap applies fn to each element and keeps only the results where fn returns true. Combines filtering and type-changing transformation in a single lazy pass. Fully streaming with O(1) state. Works with infinite sequences. Panics if fn is nil.

func FlatMap

func FlatMap[T, R any](s Seq[T], fn func(T) Seq[R]) Seq[R]

FlatMap applies fn to each element of s and concatenates the resulting Seqs. Standalone because Go methods cannot introduce additional type parameters. Panics if fn is nil.

func From

func From[T any](ts []T) Seq[T]

From creates a Seq from a slice. Returns an empty Seq for nil or empty input.

func FromChannel

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

FromChannel creates a Seq that yields values received from ch. Iteration blocks on each receive. The sequence ends when ch is closed.

Cancellation is best-effort: if cancellation races with ready channel receives, cancellation does not necessarily win immediately. The sequence may yield additional values until the ctx.Done() case is selected by Go's pseudo-random select.

The provided ctx is captured in the returned Seq for its lifetime. Unlike most Go APIs, cancellation scope is fixed at construction time, not iteration time.

Like FromNext, the returned Seq is stateful — re-iteration continues from whatever channel state exists, not from the beginning.

Panics if ctx or ch is nil.

func FromIter

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

FromIter wraps an existing iter.Seq[T] as a Seq for method chaining. If s is nil, returns an empty Seq safe for range.

func FromNext

func FromNext[T any](next func() (T, bool)) Seq[T]

FromNext creates a Seq from a pull-style iterator function. next is called repeatedly; each call returns (value, ok). When ok is false the sequence ends. next is not called again after returning false.

Because next is typically stateful (a cursor), iterating the returned Seq a second time will see whatever state next is in — usually exhausted. Use .Collect() to materialize if you need multiple passes. Panics if next is nil.

func Generate

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

Generate creates an infinite Seq: seed, fn(seed), fn(fn(seed)), ... Panics if fn is nil.

func Map

func Map[T, R any](s Seq[T], fn func(T) R) Seq[R]

Map applies fn to each element, returning a Seq of a different type. Standalone because Go methods cannot introduce additional type parameters. Panics if fn is nil.

func Of

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

Of creates a Seq from variadic arguments.

func Repeat

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

Repeat creates an infinite Seq that yields v forever.

func Scan

func Scan[T, R any](s Seq[T], initial R, fn func(R, T) R) Seq[R]

Scan reduces a Seq like Fold, but yields all intermediate accumulator values. It includes the initial value as the first element (scanl semantics), so the result has len(s)+1 elements for a finite Seq. Standalone because Go methods cannot introduce additional type parameters. Panics if fn is nil.

func Unfold

func Unfold[T, S any](seed S, fn func(S) (T, S, bool)) Seq[T]

Unfold creates a Seq by repeatedly applying fn to a seed state. fn returns (element, nextState, continue). When continue is false, the sequence ends without emitting element. Fully lazy — fn is not called until iteration begins (unlike stream.Unfold, which eagerly evaluates the first element at construction time). Panics if fn is nil.

func Unique

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

Unique removes duplicate elements lazily, preserving first occurrence. Memory grows with the number of distinct elements seen — on infinite or high-cardinality streams, memory may grow without bound. On infinite repeating streams, Unique stalls once all distinct values have been emitted — requesting more elements than distinct values exist will never terminate. Note: for float types, NaN != NaN, so NaN values are never deduplicated. Standalone because the comparable constraint cannot be expressed on the Seq[T any] receiver.

func UniqueBy

func UniqueBy[T any, K comparable](s Seq[T], fn func(T) K) Seq[T]

UniqueBy removes duplicate elements lazily by extracted key, preserving first occurrence. Memory grows with the number of distinct keys seen — on infinite or high-cardinality streams, memory may grow without bound. Note: for float key types, NaN != NaN, so NaN keys are never deduplicated. Standalone because the comparable constraint on K cannot be expressed on the Seq[T any] receiver. Panics if fn is nil.

func Zip

func Zip[A, B any](a Seq[A], b Seq[B]) Seq[pair.Pair[A, B]]

Zip returns a Seq of pairs from corresponding elements of a and b. Truncates to the shorter sequence. Note: a is the driving side — if b is shorter, one extra element of a is consumed before truncation is detected. For side-effectful or single-use sources, be aware of this left-consumption bias.

func (Seq[T]) Any

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

Any returns true if fn returns true for at least one element. Short-circuits on first match. Panics if fn is nil.

func (Seq[T]) Collect

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

Collect materializes the Seq into a slice. Requires a finite sequence.

func (Seq[T]) Convert

func (s Seq[T]) Convert(fn func(T) T) Seq[T]

Convert applies fn to each element, returning a Seq of results. Same-type transform — use standalone Map for cross-type mapping. Panics if fn is nil.

func (Seq[T]) Drop

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

Drop returns a Seq that skips the first n elements. If n <= 0, yields all elements.

func (Seq[T]) DropWhile

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

DropWhile returns a Seq that skips elements while fn returns true, then yields the rest. Panics if fn is nil.

func (Seq[T]) Each

func (s Seq[T]) Each(fn func(T))

Each applies fn to every element for side effects. Requires a finite sequence. Panics if fn is nil.

func (Seq[T]) Every

func (s Seq[T]) Every(fn func(T) bool) bool

Every returns true if fn returns true for all elements. Returns true for an empty Seq (vacuous truth). Short-circuits on first mismatch. Panics if fn is nil.

func (Seq[T]) Find

func (s Seq[T]) Find(fn func(T) bool) option.Option[T]

Find returns the first element where fn returns true. Short-circuits on first match. Panics if fn is nil.

func (Seq[T]) Intersperse

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

Intersperse inserts sep between every adjacent pair of elements. Empty and single-element sequences pass through unchanged. Fully streaming with O(1) state. Works with infinite sequences.

func (Seq[T]) Iter

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

Iter returns the underlying iter.Seq[T] for interop with stdlib and other libraries. Returns a no-op iterator if s is nil (zero value).

func (Seq[T]) KeepIf

func (s Seq[T]) KeepIf(fn func(T) bool) Seq[T]

KeepIf returns a Seq containing only elements where fn returns true. Panics if fn is nil.

func (Seq[T]) None

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

None returns true if fn returns false for all elements. Returns true for an empty Seq (vacuous truth). Panics if fn is nil.

func (Seq[T]) Reduce

func (s Seq[T]) Reduce(fn func(T, T) T) option.Option[T]

Reduce combines elements left-to-right using the first element as the initial value. Returns not-ok if the sequence is empty. For a single-element sequence, returns that element without calling fn. Requires a finite sequence. Panics if fn is nil (even for empty or single-element sequences). Note: this differs from slice.Reduce, which tolerates nil fn when len <= 1.

func (Seq[T]) RemoveIf

func (s Seq[T]) RemoveIf(fn func(T) bool) Seq[T]

RemoveIf returns a Seq containing only elements where fn returns false. It is the complement of KeepIf. Panics if fn is nil.

func (Seq[T]) Take

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

Take returns a Seq yielding at most n elements. If n <= 0, yields nothing.

func (Seq[T]) TakeWhile

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

TakeWhile returns a Seq yielding elements while fn returns true. Stops at the first element where fn returns false. Panics if fn is nil.

func (Seq[T]) ToChannel

func (s Seq[T]) ToChannel(ctx context.Context, buf int) <-chan T

ToChannel sends values from s into a new channel, returning it. A goroutine is spawned to drive iteration; it closes the returned channel when iteration ends.

Cancellation is cooperative: the goroutine checks ctx at each yield/send boundary. If s blocks internally before yielding the next value (e.g., a nested FromChannel on a slow source), ctx cancellation cannot interrupt it — the goroutine remains blocked until s yields or terminates. The caller must drain or cancel to avoid goroutine leaks. If both ctx.Done() and the channel send are selectable, sends may continue until the cancellation branch is selected. Buffered channels or an actively receiving consumer can therefore observe additional post-cancel values.

If ctx is already canceled, no goroutine is spawned and a closed empty channel is returned.

If s is nil (zero value), returns a closed empty channel. buf sets the channel buffer size (0 for unbuffered). Panics if ctx is nil or buf < 0.

ToChannel assumes s follows the iter.Seq protocol: no concurrent yield calls, no retaining yield after return.

Jump to

Keyboard shortcuts

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