slice

package
v0.42.0 Latest Latest
Warning

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

Go to latest
Published: Mar 17, 2026 License: MIT Imports: 10 Imported by: 0

README

slice

Replace loop scaffolding with type-safe collection chains.

  • InterchangeableMapper[T] is []T. Index, range, append, len all work. Pass to or return from any function expecting []T.
  • Generics — 100% type-safe. No any, no reflection, no type assertions.
  • Method expressions — pass User.IsActive directly. No wrapper closures.
  • Comma-okFind, IndexWhere return option with .Get()(value, ok).
// Before: 3 lines of scaffolding, 2 closing braces, 1 line of intent
var names []string
for _, u := range users {
    if u.IsActive() {
        names = append(names, u.Name)
    }
}

// After: intent only
names := slice.From(users).KeepIf(User.IsActive).ToString(User.GetName)

Six lines become one.

What It Looks Like

// Ranking
top5 := slice.SortByDesc(players, Player.Score).Take(5)
// Cross-type mapping (both types inferred)
users := slice.Map(ids, FetchUser)
// Multi-field extraction in one pass
prices, quantities, discounts, taxes := slice.Unzip4(orders,
    Order.Price, Order.Quantity, Order.Discount, Order.Tax,
)

Order.Price is a method expression — Go turns the method into a func(Order) float64, which is exactly what Unzip expects. This works when your types have accessor methods. Without them, named functions work in the same position:

// With accessor methods
prices, qtys := slice.Unzip2(orders, Order.Price, Order.Quantity)

// Without — named functions instead
getPrice := func(o Order) float64 { return o.Price() }
getQty := func(o Order) int { return o.Qty() }
prices, qtys := slice.Unzip2(orders, getPrice, getQty)

Method expressions read as intent at the call site — Order.Price vs reading a function body. They pay off when multiple sites extract the same field. For one-off use, named functions avoid the extra method.

// Set construction for O(1) lookup — works with any comparable type
allowed := slice.ToSet(cfg.AllowedRoles)
if allowed[user.Role] {
    grant(user)
}
// Expand each department into its members — one-to-many, then flatten
allEmployees := slice.From(departments).FlatMap(Department.Employees)
// Reduce to map
byMAC := slice.Fold(devices, make(map[string]Device), addDevice)
// Number items starting from 1 — state is the counter, output is the labeled string
// number returns the next counter and a formatted label.
number := func(n int, item Item) (int, string) {
    return n + 1, fmt.Sprintf("%d. %s", n, item.Name)
}
_, numbered := slice.MapAccum(items, 1, number)
// ["1. Apples", "2. Bread", "3. Milk"]

It's Just a Slice

Mapper[T] is []T. Use it anywhere you'd use a slice:

func activeNames(users []User) []string {
    names := slice.From(users).KeepIf(User.IsActive).ToString(User.Name)
    names.Each(lof.Println)
    return names  // return as []string — no conversion needed
}
result := slice.From(users).KeepIf(User.IsActive)
fmt.Println(result[0])         // index
fmt.Println(len(result))       // len
result = append(result, extra) // append
for _, u := range result {     // range
    process(u)
}
  • Keep []T for your slice arguments in function signatures, not Mapper[T] — use From() at the point of use. This keeps fluentfp as an implementation detail; callers don't need to import it. It can be useful to return Mapper[T] from many functions, however, knowing that it's still usable by the consumer as a regular slice.
  • From() is a zero-cost type conversion — no array copy. The Go spec guarantees that converting between types with identical underlying types only changes the type, not the representation. The 24-byte slice header (pointer, length, capacity) is shared; the backing array is the same. (append to either may mutate the other if capacity remains.)
  • Mutation boundaries: Most operations (KeepIf, Convert, ToString, etc.) allocate fresh slices — the result is independent of the input. View operations (From alone, Take, TakeLast, TakeWhile, Drop, DropLast, DropWhile, DropLastWhile, Chunk) share the backing array. Use .Clone() at boundaries where shared state could be mutated. See design.md § Boundaries and Defensive Copying for practical guidance.
  • Nil-safe: From(nil).KeepIf(...).ToString(...) returns an empty slice — Go's range over nil is zero iterations

Other Go FP libraries can't do this:

  • go-linq, fuego: 6+ lines of []any[]string with type assertions to get results out
  • gofp: conversion loops on both ends — []User[]any in, []any[]string out

See comparison for the full library comparison.

Operations

From creates Mapper[T]. For cross-type mapping, use the standalone Map(ts, fn) which infers all types and returns Mapper[R] for chaining. String ([]string), Int ([]int), and Float64 ([]float64) are separate defined types with additional methods.

  • Filter: KeepIf, RemoveIf, Take, TakeLast, TakeWhile, Drop, DropLast, DropWhile, DropLastWhile, NonZero, NonEmpty (String), Partition (method + standalone)
  • Search: Find, FindLast, IndexOf, IndexWhere, LastIndexOf, LastIndexWhere, FindAs, Any, Every, None, First, Last, Single, Sample, Contains (standalone, comparable), ContainsAny (String)
  • Transform: Convert, Sort (method, cmp.Ordered), FlatMap (method + standalone), Map (standalone), FilterMap (standalone), Flatten (standalone), Reverse, Shuffle, Intersperse, Enumerate (standalone), ToString, ToInt, other To*, Clone, Unique (String method + standalone comparable), UniqueBy, Samples, SortBy, SortByDesc, IsSorted, IsSortedBy
  • Combine: Zip, ZipWith
  • Set ops: Intersect, Difference, Union, FromSet
  • Aggregate: Fold, Reduce, Scan, MapAccum, Len, Max (int, float64), Min (int, float64), MaxBy, MinBy, Sum (int, float64), ToSet, ToSetBy, Each, Unzip2/3/4, GroupBy, GroupSame, Associate, KeyBy, KeyByInt, KeyByString, Join (String)
  • Generate: Range, RangeFrom, RangeStep (return Int for numeric chaining), RepeatN
  • View: Chunk, Window (sliding windows sharing backing array — overlapping windows alias the same memory; mutating one affects adjacent windows; use .Clone() for independent copies)
  • Parallel (no error return): PMap, PFlatMap, PKeepIf, PEach — bounded concurrent operations for callbacks that do not return errors. Panics in fn are recovered, converted to *rslt.PanicError with a stack captured during recovery, and re-panicked on the calling goroutine after all workers exit. If multiple workers panic, one arbitrary panic is re-thrown; others are suppressed. Usually only worth using when per-item workload is large enough to amortize the overhead caused by creation and scheduling of goroutines.
  • Parallel (error-aware): FanOut, FanOutAll, FanOutEach — bounded concurrency for callbacks that take context.Context and return errors. Use FanOut for value-producing operations where partial success is acceptable, FanOutAll for all-or-nothing operations with early cancellation, and FanOutEach for side-effecting callbacks that return only error. If item costs vary widely, use the corresponding weighted variant (FanOutWeighted, FanOutWeightedAll, FanOutEachWeighted). See rslt for CollectAll, CollectOk, and CollectOkAndErr.

Fold, not Reduce: Fold takes an initial value and allows the return type to differ from the element type (func(R, T) R). Reduce conventionally implies no initial value and same-type accumulation. The name matches the semantics.

Scan is Fold that collects all intermediate accumulator values. It includes the initial value as the first element (Haskell scanl semantics), so Scan(ts, z, f) returns len(ts)+1 elements. Law: last(Scan(ts, z, f)) == Fold(ts, z, f).

Zip and ZipWith truncate to the shorter input — safe for pipelines where lengths are data-dependent. This differs from pair.Zip, which panics on length mismatch. Use pair.Zip when equal lengths are a structural invariant you want enforced.

FanOut

FanOut runs a function on every element of a slice concurrently, limited to n at a time. Each element gets its own goroutine. Results come back in input order — output[i] corresponds to input[i].

results := slice.FanOut(ctx, 10, cities, City)
infos, err := rslt.CollectAll(results)

FanOutEach is the side-effect variant for operations that don't produce values — it returns []error instead of Mapper[rslt.Result[R]].

How it works
  1. A dispatch loop iterates the input slice, acquiring a semaphore slot before launching each goroutine.
  2. Each goroutine calls fn(ctx, item), stores the result, releases its semaphore slot, and exits.
  3. After the loop, all goroutines are joined. No goroutine outlives FanOut.

The semaphore is a buffered channel of size n. When all slots are taken, the dispatch loop blocks until a running goroutine finishes and releases one.

Cancellation

FanOut passes ctx to every callback. When ctx is cancelled:

  • Dispatch stops. The loop checks ctx before acquiring each semaphore slot. Unscheduled items get Err(ctx.Err()) without launching a goroutine.
  • In-flight callbacks continue until fn returns. FanOut does not kill goroutines — it waits for them. If fn checks ctx (e.g., via http.NewRequestWithContext), it can exit early. If fn ignores ctx, it runs to completion.

FanOut returns only after every started goroutine has finished — no goroutine leaks.

All-or-nothing: FanOutAll

When every item must succeed or the whole operation fails, use FanOutAll. On the first error or panic, it cancels a derived context — this skips unstarted work and lets in-flight callbacks stop early if they honor ctx, but it still waits for started callbacks to return. Returns ([]R, error) directly:

infos, err := slice.FanOutAll(ctx, 10, cities, City)

FanOutAll derives a child context internally — the caller's context is never cancelled. FanOutWeightedAll provides the same semantics with a cost budget instead of a fixed concurrency limit.

Fail-fast vs partial results

FanOut does not cancel siblings on failure — every item runs to completion. This is intentional: many workloads want partial results. Use rslt.CollectOk(results) to gather successes and discard failures, or rslt.CollectOkAndErr(results) to get both halves.

If any failure should fail the whole batch, use FanOutAll instead — on the first error or panic it cancels a derived context, skips unstarted work, lets in-flight callbacks stop early if they honor ctx, and still waits for started callbacks to return.

Panic recovery

If fn panics, that item's result becomes Err wrapping a *rslt.PanicError (with the panic value and stack trace). Other items are unaffected. Detect panics with errors.As:

var pe *rslt.PanicError
if errors.As(err, &pe) {
    log.Printf("panic: %v\n%s", pe.Value, pe.Stack)
}
Choosing a parallel operation

Choose in two steps:

  1. What callback signature do you have?

    • func(T) R, func(T) bool, or func(T): PMap / PKeepIf / PEach
    • func(context.Context, T) (R, error) or func(context.Context, T) error: the FanOut* family
  2. If you're using FanOut*, what result/error contract do you want?

    • Return values and keep per-item failures: FanOut / FanOutWeighted + collector
    • Return values and fail the whole batch on first error: FanOutAll / FanOutWeightedAll
    • Side effects only: FanOutEach / FanOutEachWeighted
Callback signature Result/error contract Operation
func(T) R / func(T) bool / func(T) PMap / PKeepIf / PEach
func(context.Context, T) (R, error) All must succeed FanOutAll / FanOutWeightedAll
func(context.Context, T) (R, error) Partial success OK FanOut / FanOutWeighted + collector
func(context.Context, T) error Side effects only FanOutEach / FanOutEachWeighted

No error return — use PMap, PKeepIf, or PEach when your callback does not return an error. Panics in fn are recovered, converted to *rslt.PanicError with a stack captured during recovery, and re-panicked on the calling goroutine after all workers exit. If multiple workers panic, one arbitrary panic is re-thrown; others are suppressed. Remaining workers continue until fn returns. If fn may block indefinitely, use FanOut or FanOutAll instead — they accept context.Context for timeout and cancellation. This is a good fit for transforms and filters where failure is not part of the callback's contract:

// Normalize 10K strings
normalized := slice.PMap(inputs, 8, strings.ToLower)

// Compute SHA-256 digests concurrently
digests := slice.PMap(blobs, 4, sha256.Sum256)

All-or-nothing — every item must succeed or the whole batch fails. On the first error or panic, FanOutAll cancels a derived context — this skips unstarted work and lets in-flight callbacks stop early if they honor ctx, but it still waits for started callbacks to return:

// Fetch all prices before computing portfolio (partial is useless)
prices, err := slice.FanOutAll(ctx, 5, tickers, fetchPrice)

// Parse and validate all manifests before deployment
manifests, err := slice.FanOutAll(ctx, 4, files, parseAndValidateManifest)

Partial success — gather what you can, log failures separately. Use FanOut with a collector:

// Download avatars (missing ones OK)
avatarResults := slice.FanOut(ctx, 10, users, downloadAvatar)
avatars, errs := rslt.CollectOkAndErr(avatarResults)
// Decode untrusted images (some may be corrupt)
imageResults := slice.FanOut(ctx, 8, blobs, decodeImage)
images := rslt.CollectOk(imageResults)

Side effects onlyFanOutEach returns []error instead of values:

// Send notifications (log failures, keep going)
errs := slice.FanOutEach(ctx, 20, users, sendNotification)

Scheduling model is the next question after callback signature and error contract. PMap uses a fixed set of workers processing chunks of the input, which has lower scheduling overhead for many small, uniform tasks. FanOut schedules work per item under a concurrency limit, which handles variable-latency work better and enables cancellation and per-item error handling. If you've chosen the FanOut family and item costs vary widely, use the corresponding weighted variant to bound total in-flight cost instead of item count:

// Upload files bounded by total in-flight MB
sizeMB := func(f File) int { return f.SizeMB }
uploads, err := slice.FanOutWeightedAll(ctx, 100, files, sizeMB, uploadFile)

For heavily skewed no-error workloads, benchmark PMap; if chunking becomes a bottleneck, you may need a custom worker pool.

See pkg.go.dev for complete API documentation, the main README for installation and performance characteristics, and the showcase for real-world rewrites.

Documentation

Overview

Package slice provides fluent slice types that can chain functional collection operations.

Mapper[T] is a fluent slice that can chain operations like ToString (map), KeepIf (filter), etc.

Entries[K, V] is a fluent map type for chaining map operations.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Asc added in v0.35.0

func Asc[T any, S cmp.Ordered](key func(T) S) func(T, T) int

Asc builds an ascending comparator from a key extractor.

func Associate added in v0.41.0

func Associate[T any, K comparable, V any](ts []T, fn func(T) (K, V)) map[K]V

Associate builds a map by applying fn to each element to produce a key-value pair. If multiple elements produce the same key, the last one wins. Returns nil for nil or empty input.

func Chunk added in v0.28.0

func Chunk[T any](ts []T, size int) [][]T

Chunk splits ts into sub-slices of at most size elements. The last chunk may have fewer than size elements.

Each chunk is a sub-slice of ts, so element mutations through a chunk are visible through ts (and vice versa). Capacity is clipped to chunk length, so appending to a chunk allocates a new backing array rather than corrupting adjacent chunks or the source.

Panics if size <= 0.

func Contains added in v0.28.0

func Contains[T comparable](ts []T, target T) bool

Contains returns true if ts contains target.

func Desc added in v0.35.0

func Desc[T any, S cmp.Ordered](key func(T) S) func(T, T) int

Desc builds a descending comparator from a key extractor.

func FanOutAll added in v0.41.0

func FanOutAll[T, R any](ctx context.Context, n int, ts []T, fn func(context.Context, T) (R, error)) ([]R, error)

FanOutAll applies fn to each element concurrently (at most n goroutines), returning all values if every call succeeds, or the first observed failure otherwise. On success, output order matches input order. On first failure (error or panic), the derived context is cancelled so cooperative work can stop early. Already-running callbacks continue until fn returns.

The returned error is the first failure observed (by time, not index). Sibling context.Canceled errors from cancellation do not mask the root cause. Panics in fn are captured as *rslt.PanicError with the original stack trace.

Derives a child context internally — the caller's context is never cancelled.

Panics if n <= 0, ctx is nil, or fn is nil.

func FanOutEach added in v0.41.0

func FanOutEach[T any](ctx context.Context, n int, ts []T, fn func(context.Context, T) error) []error

FanOutEach applies fn to each element of ts concurrently with at most n goroutines. It is the side-effect variant of FanOut for operations that don't produce values.

Returns []error with len == len(ts). Nil entries indicate success. Panics from fn are wrapped as *rslt.PanicError in the error slice, detectable via errors.As.

Panics if n <= 0, ctx is nil, or fn is nil.

func FanOutEachWeighted added in v0.41.0

func FanOutEachWeighted[T any](ctx context.Context, capacity int, ts []T, cost func(T) int, fn func(context.Context, T) error) []error

FanOutEachWeighted applies fn to each element of ts concurrently, bounded by a total cost budget. It is the side-effect variant of FanOutWeighted.

Returns []error with len == len(ts). Nil entries indicate success. Panics from fn are wrapped as *rslt.PanicError in the error slice, detectable via errors.As.

Same cost pre-validation semantics as FanOutWeighted: cost is evaluated eagerly, should be pure, and panics in cost propagate immediately.

Panics if capacity <= 0, cost is nil, ctx is nil, or fn is nil. Per-item: panics if cost(t) <= 0 or cost(t) > capacity.

func FanOutWeightedAll added in v0.41.0

func FanOutWeightedAll[T, R any](ctx context.Context, capacity int, ts []T, cost func(T) int, fn func(context.Context, T) (R, error)) ([]R, error)

FanOutWeightedAll applies fn to each element concurrently, bounded by a total cost budget, returning all values if every call succeeds, or the first observed failure otherwise. On first failure (error or panic), the derived context is cancelled so cooperative work can stop early.

Same error/cancellation semantics as FanOutAll. Same cost pre-validation semantics as FanOutWeighted: cost is evaluated eagerly, should be pure, and panics in cost propagate immediately.

Derives a child context internally — the caller's context is never cancelled.

Panics if capacity <= 0, cost is nil, ctx is nil, or fn is nil. Per-item: panics if cost(t) <= 0 or cost(t) > capacity.

func FindAs added in v0.21.0

func FindAs[R, T any](ts []T) option.Option[R]

FindAs returns the first element that type-asserts to R, or not-ok if none match. Useful for finding a specific concrete type in a slice of interfaces.

func Fold added in v0.6.0

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

Fold reduces a slice to a single value by applying fn to each element. It starts with initial and applies fn(accumulator, element) for each element from left to right. Returns initial if the slice is empty.

func IndexOf added in v0.41.0

func IndexOf[T comparable](ts []T, target T) option.Option[int]

IndexOf returns the index of the first occurrence of target, or not-ok if absent. Uses == comparison; for predicate-based search, use IndexWhere.

func IsSortedBy added in v0.41.0

func IsSortedBy[T any, K cmp.Ordered](ts []T, fn func(T) K) bool

IsSortedBy reports whether ts is sorted in ascending order by the key extracted via fn.

func KeyBy added in v0.38.0

func KeyBy[T any, K comparable](ts []T, fn func(T) K) map[K]T

KeyBy indexes elements by a key derived from fn, returning a map from key to element. If multiple elements produce the same key, the last one wins. For common key types, prefer the method forms KeyByString and KeyByInt on Mapper.

func LastIndexOf added in v0.41.0

func LastIndexOf[T comparable](ts []T, target T) option.Option[int]

LastIndexOf returns the index of the last occurrence of target, or not-ok if absent. Uses == comparison; for predicate-based search, use LastIndexWhere.

func MaxBy added in v0.41.0

func MaxBy[T any, K cmp.Ordered](ts []T, key func(T) K) (_ option.Option[T])

MaxBy returns the element with the largest key, or not-ok if the slice is empty. Keys are compared using cmp.Compare, which places NaN before -Inf for float types. If multiple elements share the largest key, the first one is returned. The key function is called exactly once per element.

func MinBy added in v0.41.0

func MinBy[T any, K cmp.Ordered](ts []T, key func(T) K) (_ option.Option[T])

MinBy returns the element with the smallest key, or not-ok if the slice is empty. Keys are compared using cmp.Compare, which places NaN before -Inf for float types. If multiple elements share the smallest key, the first one is returned. The key function is called exactly once per element.

func Partition added in v0.36.0

func Partition[T any](ts []T, fn func(T) bool) (Mapper[T], Mapper[T])

Partition splits ts into two slices: elements where fn returns true, and elements where it returns false. Input order is preserved in both results. Single pass. Both results are independent slices.

func Reduce added in v0.41.0

func Reduce[T any](ts []T, 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 slice is empty. For a single-element slice, returns that element without calling fn. Unlike Fold, Reduce requires the result type to match the element type. Nil fn does not panic when the slice has fewer than two elements (fn is never invoked).

func ToSet added in v0.23.0

func ToSet[T comparable](ts []T) map[T]bool

ToSet returns a map with each element as a key set to true. Requires comparable elements.

func ToSetBy added in v0.28.0

func ToSetBy[T any, K comparable](ts []T, fn func(T) K) map[K]bool

ToSetBy returns a map with each element's key (extracted by fn) set to true. Useful when elements aren't directly comparable but have a comparable key field.

func Unzip2 added in v0.6.0

func Unzip2[T, A, B any](ts []T, fa func(T) A, fb func(T) B) (Mapper[A], Mapper[B])

Unzip2 extracts two slices from ts in a single pass by applying the extraction functions. This is more efficient than calling two separate mapping operations when you need multiple fields.

func Unzip3 added in v0.6.0

func Unzip3[T, A, B, C any](ts []T, fa func(T) A, fb func(T) B, fc func(T) C) (Mapper[A], Mapper[B], Mapper[C])

Unzip3 extracts three slices from ts in a single pass by applying the extraction functions.

func Unzip4 added in v0.6.0

func Unzip4[T, A, B, C, D any](ts []T, fa func(T) A, fb func(T) B, fc func(T) C, fd func(T) D) (Mapper[A], Mapper[B], Mapper[C], Mapper[D])

Unzip4 extracts four slices from ts in a single pass by applying the extraction functions.

func Window added in v0.41.0

func Window[T any](ts []T, size int) [][]T

Window returns sliding windows of size elements. Each window is a sub-slice sharing the backing array of ts — overlapping windows alias the same memory. Mutating an element in one window affects all windows that include that position. Capacity is clipped to window size, so append on a window will not corrupt adjacent windows or the source. Use Clone() on individual windows if you need fully independent copies. Panics if size <= 0. Returns empty for empty/nil input or when len(ts) < size.

Types

type Any

type Any = Mapper[any]

Convenience aliases for common Mapper instantiations.

type Bool

type Bool = Mapper[bool]

type Byte

type Byte = Mapper[byte]

type Entries added in v0.35.0

type Entries[K comparable, V any] = base.Entries[K, V]

type Error

type Error = Mapper[error]

type Float32

type Float32 = Mapper[float32]

type Float64

type Float64 = base.Float64

type Group added in v0.35.0

type Group[K comparable, T any] struct {
	Key   K
	Items []T
}

Group holds a grouping key and its collected items.

func (Group[K, T]) GetItems added in v0.41.0

func (g Group[K, T]) GetItems() []T

GetItems returns the group's items.

func (Group[K, T]) GetKey added in v0.41.0

func (g Group[K, T]) GetKey() K

GetKey returns the group's key.

func (Group[K, T]) Len added in v0.41.0

func (g Group[K, T]) Len() int

Len returns the number of items in the group.

type Int

type Int = base.Int

func Range added in v0.41.0

func Range(end int) Int

Range returns [0, 1, ..., end-1]. Returns empty for end <= 0.

func RangeFrom added in v0.41.0

func RangeFrom(start, end int) Int

RangeFrom returns [start, start+1, ..., end-1]. Returns empty for start >= end. Panics if the range is too large to allocate.

func RangeStep added in v0.41.0

func RangeStep(start, end, step int) Int

RangeStep generates values starting at start, incrementing by step, stopping before reaching end (half-open interval). Panics if step is zero or math.MinInt (negation overflow). Returns empty when direction mismatches step sign.

type Mapper

type Mapper[T any] = base.Mapper[T]

Type aliases for types defined in internal/base. All methods defined on the base types are available through these aliases.

func Difference added in v0.41.0

func Difference[T comparable](a, b []T) Mapper[T]

Difference returns elements in a that are not in b, deduplicated, preserving first-occurrence order from a.

func Enumerate added in v0.41.0

func Enumerate[T any](ts []T) Mapper[pair.Pair[int, T]]

Enumerate pairs each element with its zero-based index. Preserves input order. Returns nil for nil input.

func FanOut added in v0.41.0

func FanOut[T, R any](ctx context.Context, n int, ts []T, fn func(context.Context, T) (R, error)) Mapper[rslt.Result[R]]

FanOut applies fn to each element of ts concurrently with at most n goroutines. Each element gets its own goroutine (semaphore-bounded), enabling per-item scheduling suited for I/O-bound workloads with variable latency.

Returns Mapper[rslt.Result[R]] with len == len(ts), where output[i] corresponds to ts[i].

Panics in fn are recovered and returned as *rslt.PanicError in the result slot, detectable via errors.As. Panics are not re-thrown.

Cancellation guarantees:

  1. FanOut returns only after all started callbacks have returned. No goroutine leaks.
  2. At most n callbacks execute concurrently. Semaphore enforced.
  3. When ctx is cancelled, the scheduler stops launching new work promptly. Unscheduled items get Err(ctx.Err()). Due to check-then-act races, at most one additional callback may start after cancellation occurs.
  4. In-flight callbacks continue until fn returns.
  5. Callbacks may observe an already-cancelled context.
  6. An already-cancelled ctx before FanOut entry: zero callbacks run.
  7. fn errors do NOT cancel siblings. The caller controls fail-fast by cancelling ctx in fn.

Panics if n <= 0, ctx is nil, or fn is nil.

func FanOutWeighted added in v0.41.0

func FanOutWeighted[T, R any](ctx context.Context, capacity int, ts []T, cost func(T) int, fn func(context.Context, T) (R, error)) Mapper[rslt.Result[R]]

FanOutWeighted applies fn to each element of ts concurrently, bounded by a total cost budget rather than a fixed item count. Each item's cost is determined by the cost function, and at most capacity units of cost run concurrently.

Returns Mapper[rslt.Result[R]] with len == len(ts), where output[i] corresponds to ts[i].

Panics in fn are recovered and returned as *rslt.PanicError in the result slot, detectable via errors.As. Panics are not re-thrown.

Same cancellation guarantees as FanOut. Partial acquire rollback: if ctx cancels after acquiring some tokens for an item, the scheduler releases them and fills remaining items with ctx.Err().

Items are scheduled in input order. A high-cost item blocks later items from starting even if capacity is available for them (head-of-line blocking).

All item costs are evaluated eagerly before any goroutines start. cost should be pure and inexpensive. If cost panics, the panic propagates immediately (it is not recovered into a result slot). This pre-validation ensures invalid costs are detected before work begins, preserving cleanup guarantees.

Panics if capacity <= 0, cost is nil, ctx is nil, or fn is nil. Per-item: panics if cost(t) <= 0 or cost(t) > capacity.

func FilterMap added in v0.41.0

func FilterMap[T, R any](ts []T, fn func(T) (R, bool)) Mapper[R]

FilterMap applies fn to each element and keeps only the results where fn returns true. It combines filtering and type-changing transformation in a single pass. The inclusion decision and transformed output are derived from a single callback invocation per element. Preserves input order among kept elements. Returns nil for nil input.

func FlatMap added in v0.41.0

func FlatMap[T, R any](ts []T, fn func(T) []R) Mapper[R]

FlatMap applies fn to each element of ts and flattens the results into a single slice. It is a standalone function because Go methods cannot introduce new type parameters — the target type R must be inferred from the function argument rather than bound on the receiver.

func Flatten added in v0.41.0

func Flatten[T any](tss [][]T) Mapper[T]

Flatten concatenates nested slices into a single flat slice, preserving element order.

func From

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

From creates a Mapper for fluent operations on a slice.

func FromSet added in v0.30.0

func FromSet[T comparable](m map[T]bool) Mapper[T]

FromSet extracts the members of a set (keys where value is true) as a Mapper. Order is not guaranteed (map iteration order).

func GroupBy added in v0.30.0

func GroupBy[T any, K comparable](ts []T, fn func(T) K) Mapper[Group[K, T]]

GroupBy groups elements by the key returned by fn. Groups preserve first-seen key order; items within each group preserve input order.

func GroupSame added in v0.41.0

func GroupSame[T comparable](ts []T) Mapper[Group[T, T]]

GroupSame groups elements by their own value, returning a group per distinct value. Equivalent to GroupBy with an identity key extractor.

Example
package main

import (
	"fmt"
	"strings"

	"github.com/binaryphile/fluentfp/slice"
)

func main() {
	type G = slice.Group[string, string]

	// formatGroup formats a group as "value(count)".
	formatGroup := func(g G) string {
		return fmt.Sprintf("%s(%d)", g.Key, g.Len())
	}

	statuses := slice.Mapper[string]{"running", "exited", "running", "running"}
	formatted := slice.GroupSame(statuses).ToString(formatGroup)
	fmt.Println(strings.Join(formatted, ", "))
}
Output:

running(3), exited(1)

func Intersect added in v0.41.0

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

Intersect returns elements present in both a and b, deduplicated, preserving first-occurrence order from a.

func Map added in v0.30.0

func Map[T, R any](ts []T, fn func(T) R) Mapper[R]

Map returns the result of applying fn to each member of ts. It is a standalone function because Go methods cannot introduce new type parameters — the target type R must be inferred from the function argument rather than bound on the receiver.

func MapAccum added in v0.25.0

func MapAccum[T, R, S any](ts []T, init S, fn func(S, T) (S, R)) (S, Mapper[R])

MapAccum threads state through a slice, producing both a final state and a mapped output. fn receives the accumulated state and current element, returning new state and an output value. Returns init and an empty slice if ts is empty.

func NonEmpty added in v0.41.0

func NonEmpty(ts []string) Mapper[string]

NonEmpty removes empty strings from ts.

func NonZero added in v0.41.0

func NonZero[T comparable](ts []T) Mapper[T]

NonZero removes zero-value elements from ts.

func PFlatMap added in v0.41.0

func PFlatMap[T, R any](ts []T, workers int, fn func(T) []R) Mapper[R]

PFlatMap applies fn to each element of ts concurrently using the specified number of worker goroutines, then flattens the results into a single slice. Order is preserved. The fn must be safe for concurrent use.

It is a standalone function because Go methods cannot introduce new type parameters — the target type R must be inferred from the function argument rather than bound on the receiver.

Panics in fn are recovered, converted to *rslt.PanicError with a stack captured during recovery, and re-panicked on the calling goroutine after all workers exit.

Panics if workers <= 0.

func PMap added in v0.41.0

func PMap[T, R any](ts []T, workers int, fn func(T) R) Mapper[R]

PMap returns the result of applying fn to each member of ts, using the specified number of worker goroutines. Order is preserved. The fn must be safe for concurrent use.

Panics in fn are recovered, converted to *rslt.PanicError with a stack captured during recovery, and re-panicked on the calling goroutine after all workers exit.

Panics if workers <= 0.

Example
package main

import (
	"fmt"
	"runtime"

	"github.com/binaryphile/fluentfp/slice"
)

func main() {
	double := func(n int) int { return n * 2 }
	result := slice.PMap(slice.From([]int{1, 2, 3, 4, 5}), runtime.GOMAXPROCS(0), double)
	fmt.Println([]int(result))
}
Output:

[2 4 6 8 10]

func RepeatN added in v0.41.0

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

RepeatN returns n copies of v. Returns empty for n <= 0.

func Scan added in v0.41.0

func Scan[T, R any](ts []T, initial R, fn func(R, T) R) Mapper[R]

Scan reduces a slice like Fold, but collects all intermediate accumulator values. It includes the initial value as the first element (Haskell scanl semantics), so the result has len(ts)+1 elements. Returns [initial] for an empty or nil slice. Law: the last element equals Fold(ts, initial, fn).

func SortBy added in v0.23.0

func SortBy[T any, K cmp.Ordered](ts []T, fn func(T) K) Mapper[T]

SortBy returns a sorted copy of ts, ordered ascending by the key extracted via fn. The sort is not stable; elements with equal keys may be reordered. fn may be called multiple times per element; it should be pure and inexpensive.

func SortByDesc added in v0.23.0

func SortByDesc[T any, K cmp.Ordered](ts []T, fn func(T) K) Mapper[T]

SortByDesc returns a sorted copy of ts, ordered descending by the key extracted via fn. The sort is not stable; elements with equal keys may be reordered. fn may be called multiple times per element; it should be pure and inexpensive.

func Union added in v0.41.0

func Union[T comparable](a, b []T) Mapper[T]

Union returns the deduplicated combination of a and b, preserving first-occurrence order (all of a first, then extras from b).

func Unique added in v0.41.0

func Unique[T comparable](ts []T) Mapper[T]

Unique removes duplicate elements, preserving first occurrence. For non-comparable types or key-based deduplication, use UniqueBy. Note: for float types, NaN != NaN, so NaN values are never deduplicated. Returns nil for nil input.

func UniqueBy added in v0.28.0

func UniqueBy[T any, K comparable](ts []T, fn func(T) K) Mapper[T]

UniqueBy returns a new slice with duplicate elements removed, where duplicates are determined by the key returned by fn. Preserves first occurrence and maintains order.

func Zip added in v0.41.0

func Zip[A, B any](as []A, bs []B) Mapper[pair.Pair[A, B]]

Zip combines corresponding elements from two slices into pairs. Truncates to the length of the shorter slice. Note: pair.Zip panics on length mismatch; slice.Zip truncates instead, which is safer as a default in collection pipelines.

func ZipWith added in v0.41.0

func ZipWith[A, B, R any](as []A, bs []B, fn func(A, B) R) Mapper[R]

ZipWith combines corresponding elements from two slices using fn. Truncates to the length of the shorter slice (same as Zip).

type Rune

type Rune = Mapper[rune]

type String

type String = base.String

Jump to

Keyboard shortcuts

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