Documentation
¶
Overview ¶
Package funq provides type-safe, composable utilities for functional programming in Go.
The package rests on three pillars:
- Flow is an immutable sequence with Map / Filter / Take and other transformations. It is eager or lazy depending on its source (values, generator functions, or iter.Seq), not on how many times a pipeline built on it runs. A pipeline cannot be relied on to run only once: a terminal operation that needs the elements may re-run it. See the Flow documentation for the re-computation and concurrency caveats and for Cache, which materializes the result.
- Optional models a value that may be absent, with Map / FlatMap / OrElse and conversions to and from Flow, (value, error), and JSON.
- Compose and Groove build function pipelines one stage at a time via Chain.Then and Track.Jam. Compose is for plain (Fp) steps that cannot fail. Groove is for error-returning (Fe) steps. It short-circuits on the first error (railway-oriented programming).
A few operations — Chunk, Contains, Distinct, and Zip — are free functions rather than Flow methods because their signatures cannot be expressed as methods. See each function's documentation for why. Those that reduce to a func(Flow[T]) U — Distinct as it stands, Chunk once given its size — drop back into a chain through Flow.To: f.To(Distinct), f.To(Chunk[int](2)).
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
result := funq.FromFn(10, func(i int) int { return i + 1 }).
Filter(func(v int) bool { return v%2 == 0 }). // Keep even numbers: [2, 4, 6, 8, 10]
Map(func(v int) int { return v * 3 }). // Multiply by 3: [6, 12, 18, 24, 30]
Drop(1). // Drop first: [12, 18, 24, 30]
Take(3). // Take first 3: [12, 18, 24]
Slice()
fmt.Println(result)
}
Output: [12 18 24]
Index ¶
- func Chunk[T any](n int) func(Flow[T]) Flow[[]T]
- func Contains[T comparable](f Flow[T], v T) bool
- func False[T any](T) bool
- func Identity[T any](t T) T
- func True[T any](T) bool
- type Chain
- type Fe
- type Flow
- func (f Flow[T]) All(pred func(T) bool) bool
- func (f Flow[T]) Any(pred func(T) bool) bool
- func (f Flow[T]) Cache() Flow[T]
- func (f Flow[T]) Concat(others ...Flow[T]) Flow[T]
- func (f Flow[T]) Count() int
- func (f Flow[T]) DistinctBy[K comparable](key func(T) K) Flow[T]
- func (f Flow[T]) Drop(n int) Flow[T]
- func (f Flow[T]) DropWhile(pred func(T) bool) Flow[T]
- func (f Flow[T]) Filter(pred func(T) bool) Flow[T]
- func (f Flow[T]) Find(pred func(T) bool) Optional[T]
- func (f Flow[T]) First() Optional[T]
- func (f Flow[T]) FlatMap[U any](fn func(T) Flow[U]) Flow[U]
- func (f Flow[T]) Fold[U any](init U, fn func(U, T) U) U
- func (f Flow[T]) ForEach(fn func(T))
- func (f Flow[T]) ForEachIndexed(fn func(int, T))
- func (f Flow[T]) GroupBy[K comparable](key func(T) K) map[K][]T
- func (f Flow[T]) IsEmpty() bool
- func (f Flow[T]) Last() Optional[T]
- func (f Flow[T]) Map[U any](fn func(T) U) Flow[U]
- func (f Flow[T]) MapIndexed[U any](fn func(int, T) U) Flow[U]
- func (f Flow[T]) MaxBy[K cmp.Ordered](key func(T) K) Optional[T]
- func (f Flow[T]) MinBy[K cmp.Ordered](key func(T) K) Optional[T]
- func (f Flow[T]) Partition(pred func(T) bool) (matched, rest Flow[T])
- func (f Flow[T]) Reduce(fn func(T, T) T) Optional[T]
- func (f Flow[T]) Reverse() Flow[T]
- func (f Flow[T]) Seq() iter.Seq[T]
- func (f Flow[T]) Slice() []T
- func (f Flow[T]) SortBy[K cmp.Ordered](key func(T) K) Flow[T]
- func (f Flow[T]) SortFunc(cmp func(a, b T) int) Flow[T]
- func (f Flow[T]) Take(n int) Flow[T]
- func (f Flow[T]) TakeWhile(pred func(T) bool) Flow[T]
- func (f Flow[T]) To[U any](fn func(Flow[T]) U) U
- func (f Flow[T]) ToMap[K comparable](key func(T) K) map[K]T
- type Fp
- func And[T any](preds ...Fp[T, bool]) Fp[T, bool]
- func AtLeast[T cmp.Ordered](r T) Fp[T, bool]
- func AtMost[T cmp.Ordered](r T) Fp[T, bool]
- func Between[T cmp.Ordered](x, y T) Fp[T, bool]
- func Const[T, U any](u U) Fp[T, U]
- func Equal[T comparable](x T) Fp[T, bool]
- func GreaterThan[T cmp.Ordered](r T) Fp[T, bool]
- func IgnoreError[T, U any](f Fe[T, U], orElse U) Fp[T, U]
- func Implies[T any](l, r Fp[T, bool]) Fp[T, bool]
- func LessThan[T cmp.Ordered](r T) Fp[T, bool]
- func Nand[T any](l, r Fp[T, bool]) Fp[T, bool]
- func Nor[T any](l, r Fp[T, bool]) Fp[T, bool]
- func Not[T any](pred Fp[T, bool]) Fp[T, bool]
- func OneOf[T comparable](vs ...T) Fp[T, bool]
- func Or[T any](preds ...Fp[T, bool]) Fp[T, bool]
- func PanicOnError[T, U any](f Fe[T, U]) Fp[T, U]
- func Xnor[T any](l, r Fp[T, bool]) Fp[T, bool]
- func Xor[T any](l, r Fp[T, bool]) Fp[T, bool]
- type Optional
- func (o Optional[T]) AsFlow() Flow[T]
- func (o Optional[T]) Filter(pred func(T) bool) Optional[T]
- func (o Optional[T]) FlatMap[U any](fn func(T) Optional[U]) Optional[U]
- func (o Optional[T]) ForEach(fn func(T))
- func (o Optional[T]) Get() (T, bool)
- func (o Optional[T]) IsEmpty() bool
- func (o Optional[T]) IsPresent() bool
- func (o Optional[T]) IsZero() bool
- func (o Optional[T]) Map[U any](fn func(T) U) Optional[U]
- func (o Optional[T]) MarshalJSON() ([]byte, error)
- func (o Optional[T]) MustGet() T
- func (o Optional[T]) Or(other Optional[T]) Optional[T]
- func (o Optional[T]) OrElse(fallback T) T
- func (o Optional[T]) OrElseGet(fn func() T) T
- func (o Optional[T]) OrErr(err error) (T, error)
- func (o Optional[T]) Ptr() *T
- func (o Optional[T]) Seq() iter.Seq[T]
- func (o Optional[T]) String() string
- func (o *Optional[T]) UnmarshalJSON(data []byte) error
- type Pair
- type Track
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Chunk ¶
Chunk returns a function that groups a Flow's consecutive elements into slices of length n; the final chunk may be shorter. It panics if n is less than 1, matching slices.Chunk. The returned Flow is lazy. Each yielded slice is freshly allocated (it does not alias the Flow's backing storage or previously yielded chunks).
Chunk cannot be a Flow method, for the same reason as Zip: its return type instantiates Flow with a type argument derived from the receiver's T (Flow[T] -> Flow[[]T]). The curried form plugs directly into Flow.To (f.To(Chunk[T](n))) and, matching Fp's shape, into a Compose chain; wrap it with NilError first to use it in a Groove pipeline.
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
// Chunk cannot be a method (see its doc comment), so it is called
// curried: Chunk(n)(flow), or in a chain via To: flow.To(Chunk[int](n)).
// The final chunk may be shorter than n.
chunks := funq.Chunk[int](2)(funq.From(1, 2, 3, 4, 5)).Slice()
fmt.Println(chunks)
}
Output: [[1 2] [3 4] [5]]
func Contains ¶
func Contains[T comparable](f Flow[T], v T) bool
Contains reports whether v is among the elements of f. It is equivalent to f.Any(Equal(v)).
Contains cannot be a method, for the same reason as Distinct. Use Flow.Any with a custom predicate when T is not comparable or the match is looser than equality.
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
// Contains is a free function, not a method (see its doc comment for why).
fmt.Println(funq.Contains(funq.From(1, 2, 3), 2))
fmt.Println(funq.Contains(funq.From(1, 2, 3), 9))
}
Output: true false
Types ¶
type Chain ¶
type Chain[T0, T1 any] struct { // contains filtered or unexported fields }
Chain is a plain-function pipeline under construction. It has no useful zero value: build one with Compose and extend it with Chain.Then.
Chain works on plain (Fp) stages that cannot fail. When a stage can return an error, use Track, the error-aware (railway-oriented) counterpart that short-circuits on the first failure.
func Compose ¶
Compose cuts a Chain's first stage from f: T0 -> T1. A chain has no fixed arity: add further stages by calling Chain.Then once per stage.
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
addThenFilter := func(n int) funq.Flow[int] {
return funq.From(n).
Map(func(v int) int { return v + 10 }).
Filter(funq.GreaterThan(15))
}
// A no-arg method is already usable as a method expression
// (funq.Flow[int].Slice is an Fp[Flow[int], []int]).
pipeline := funq.Compose(addThenFilter).Then(funq.Flow[int].Slice)
fmt.Println(pipeline.Run(5))
fmt.Println(pipeline.Run(10))
}
Output: [] [20]
func (Chain[T0, T1]) Run ¶
func (c Chain[T0, T1]) Run(t0 T0) T1
Run executes the chain from t0 through every stage in order and returns the final result.
Run is itself a method value of type Fp[T0, T1]: a built Chain drops straight into anything that takes a plain function, for example flow.Map(chain.Run), or as a stage of another Chain via inner.Run.
type Fe ¶
Fe is a function func(T) (U, error) that may fail. It is the error-returning counterpart of Fp.
func ErrOnNone ¶
ErrOnNone converts an Fp returning an Optional to an Fe that fails with err when the Optional is None, so a lookup or search that reports absence rather than failure can join a Groove pipeline. It is the Optional counterpart of NilError, PanicOnError and IgnoreError.
The body is just Optional.OrErr applied to f's result. The wrapper is point-free, so it drops straight into a Jam call without a closure:
Groove(ErrOnNone(lookup, ErrNotFound)).Jam(save)
Example ¶
package main
import (
"errors"
"fmt"
"github.com/cedar10bits/funq"
)
// Sentinel errors for examples that turn an absent Optional into a failure.
var errNotFound = errors.New("user not found")
func main() {
// A lookup reports absence as None, not as an error.
users := map[int]string{1: "ada"}
lookup := func(id int) funq.Optional[string] {
if name, ok := users[id]; ok {
return funq.Some(name)
}
return funq.None[string]()
}
// ErrOnNone adapts it into a Groove stage without a wrapping closure.
greet := funq.Groove(funq.ErrOnNone(lookup, errNotFound)).
Jam(funq.NilError(func(name string) string { return "hello, " + name }))
fmt.Println(greet.Play(1))
_, err := greet.Play(2)
fmt.Println(errors.Is(err, errNotFound))
}
Output: hello, ada <nil> true
type Flow ¶
type Flow[T any] struct { // contains filtered or unexported fields }
Flow is an immutable, type-safe sequence of values.
A Flow is a concrete value type (not an interface), which lets its transformation methods carry their own type parameter and therefore change the element type while keeping the chain fluent (see Flow.Map, Flow.FlatMap, Flow.Fold). Whether a Flow is eager or lazy is a property of its source: From wraps concrete data, FromFn computes on demand. That describes how elements are produced, not how many times a pipeline built on either one runs — see below.
Intermediate operations build a pipeline; terminal operations run it. A terminal operation ends the chain: it returns something other than a Flow (Flow.Slice, Flow.Count, Flow.Reduce, ...), or, in Flow.Partition's case, two Flows that are already evaluated. Flow.Seq and Flow.To are not terminal — Seq hands back an iterator that runs the pipeline when ranged over, and To only applies a function to the Flow itself. A pipeline cannot be relied on to run only once: a terminal operation that needs the elements may re-run it from the source. Flow.Count and Flow.IsEmpty are the exception — they answer from the statically known count when there is one, without running anything. That leaves the caller two rules:
- Keep the functions passed to Map/Filter/... pure: this package does not guarantee when, how many times, or in what order they are invoked. An impure one makes that schedule observable — a predicate that logs, counts, or warms a cache fires once per element on every terminal operation that traverses, and again whenever an intermediate operation evaluates part of the pipeline early.
- To traverse an expensive pipeline more than once, materialize it with Flow.Cache so it runs exactly once.
Under these rules evaluation timing is unobservable, and the implementation exploits that: some intermediate operations evaluate part of the pipeline when they are called. Flow.Cache and the sort methods always do; Flow.Take, Flow.Drop, Flow.TakeWhile, Flow.DropWhile and Flow.Reverse do depending on how the Flow was built, and each method's doc states when. Materialize, in those docs, means what Cache does: the upstream pipeline runs once, there, and the result is a Flow of known count that later terminal operations do not re-run. docs/performance.md has the benchmarks behind those choices; the timings are today's behavior, not a contract.
Two properties decide the conditional cases, and a caller can read both off the way the Flow was built:
- Whether its element count is statically known. From, FromFn and Optional.AsFlow establish it and materializing re-establishes it; Flow.Map, Flow.Take, Flow.Drop, Flow.TakeWhile, Flow.DropWhile, Flow.Reverse and a Flow.Concat of inputs that all have one carry it; Flow.Filter gives it up.
- Whether it is still random-access. FromSeq builds a forward-only Flow directly; Flow.FlatMap, Flow.MapIndexed, Flow.DistinctBy, Distinct, Zip, Chunk and a Flow.Concat whose inputs do not all have a known count leave the Flow forward-only instead, with no statically known count either, until it is materialized again. Flow.Reverse materializes such a Flow, and Flow.Count, Flow.IsEmpty and Flow.Last have to traverse it.
A step that can fail belongs in Groove, not in a Flow: Map/Filter/... take Fp, not Fe. Convert it to Fp first (e.g. with IgnoreError or PanicOnError).
The zero value is a valid empty Flow.
A Flow never mutates its source, but does not defensively copy it either: From aliases the slice it is spread from (see From), so immutability of what a Flow yields depends on the caller leaving that source alone.
A Flow is not safe for concurrent use: the re-runs behind rule 1 mean that sharing one across goroutines invokes its generator and transform functions concurrently, a data race as soon as any of them touch shared state. Confine a Flow to a single goroutine, or materialize it with Flow.Cache and share the resulting slice instead.
func Distinct ¶
func Distinct[T comparable](f Flow[T]) Flow[T]
Distinct returns a Flow that yields each distinct element of f once, in first-occurrence order.
Distinct cannot be a method: it requires T itself to satisfy comparable, which a parameterized method cannot express (unlike Flow.DistinctBy, whose constraint attaches to its own new type parameter, not the receiver's T). Use DistinctBy when T is not comparable, or to dedupe by a derived key instead of the whole value. In a chain, Flow.To applies it postfix: f.To(Distinct).
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
// Distinct keeps the first occurrence of each value.
uniq := funq.Distinct(funq.From(1, 2, 2, 3, 1)).Slice()
fmt.Println(uniq)
}
Output: [1 2 3]
func From ¶
From creates an eager Flow backed directly by the given elements, without copying. It returns an empty Flow when no elements are provided.
When called with a slice spread (From(xs...)), the Flow aliases xs: the caller hands over ownership, and mutating xs afterward changes what the Flow (and any Flow derived from it) yields. Callers who cannot promise that should pass a copy (From(slices.Clone(xs)...)). Calls listing the elements directly (From(1, 2, 3)) allocate a fresh slice and are unaffected.
func FromFn ¶
FromFn creates a lazy Flow of n elements, generating element i with fn(i). Elements are produced on demand. n <= 0 yields an empty Flow.
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
// FromFn creates elements on-demand (lazy evaluation): only the elements
// the chain needs are generated.
squares := funq.FromFn(1000000, func(i int) int { return i * i }).
Filter(funq.LessThan(100)).
Take(10).
Slice()
fmt.Println(squares)
}
Output: [0 1 4 9 16 25 36 49 64 81]
func FromSeq ¶
FromSeq creates a lazy Flow over the elements of seq: the way into funq from the standard library's iterator vocabulary, and the mirror image of Flow.Seq's outbound direction. The result is forward-only with no statically known element count (see the Flow documentation's second bullet). FromSeq(nil) is the exception, returning the empty Flow, whose count is statically known to be zero.
seq must be restartable: per the Flow documentation's rule 1, a terminal operation may range over it more than once. A single-use iter.Seq — one backed by a channel or a bufio.Scanner, for instance — must be [Flow.Cache]d first, or later terminal operations will see it already exhausted.
Example ¶
package main
import (
"fmt"
"slices"
"github.com/cedar10bits/funq"
)
func main() {
// FromSeq brings a stdlib iter.Seq into a Flow directly, so the rest of
// the chain stays lazy. Without FromSeq, the only way in is materializing
// the source first with slices.Collect.
result := funq.FromSeq(slices.Values([]int{1, 2, 3, 4, 5})).
Filter(func(v int) bool { return v%2 == 0 }).
Map(func(v int) int { return v * 10 }).
Slice()
fmt.Println(result)
}
Output: [20 40]
func Zip ¶
Zip pairs up the elements of a and b by position, stopping as soon as either Flow runs out of elements.
Zip cannot be a method: a method on Flow[T] whose signature instantiates Flow with a type argument derived from T (here Flow[Pair[T, U]]) is rejected with an "instantiation cycle" error.
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
// Zip pairs elements by position, stopping at the shorter Flow.
pairs := funq.Zip(funq.From(1, 2, 3), funq.From("a", "b")).Slice()
fmt.Println(pairs)
}
Output: [{1 a} {2 b}]
func (Flow[T]) Any ¶
Any reports whether any element satisfies pred.
For a membership test on a comparable element type, use the free function Contains.
func (Flow[T]) Cache ¶
Cache materializes the Flow (see Flow) so later traversals do not recompute it.
Cache always copies every element into a fresh slice, even from an already eager Flow, so on a pure pipeline with a single terminal operation it buys nothing and adds an allocation.
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
// A chain's transforms may re-run on each terminal call, so a side-effecting
// Map or Filter can fire more than once overall; either keep transforms pure
// or use Cache to avoid repeated side effects.
calls := 0
eager := funq.From(1, 2, 3).Map(func(v int) int {
calls++
return v * 10
})
_ = eager.Slice()
_ = eager.Slice()
fmt.Println("From without Cache:", calls)
calls = 0
lazy := funq.FromFn(3, funq.Identity).Map(func(v int) int {
calls++
return v * 10
})
_ = lazy.Slice()
_ = lazy.Slice()
fmt.Println("without Cache:", calls)
// Cache materializes the result once; later traversals reuse it instead
// of re-running the chain.
calls = 0
cached := funq.FromFn(3, funq.Identity).Map(func(v int) int {
calls++
return v * 10
}).Cache()
_ = cached.Slice()
_ = cached.Slice()
fmt.Println("with Cache:", calls)
}
Output: From without Cache: 6 without Cache: 6 with Cache: 3
func (Flow[T]) Concat ¶
Concat returns a Flow that yields the elements of f followed by the elements of each of others, in order.
Concat evaluates nothing at construction time. When every input's element count is statically known (see Flow — a Flow left empty by Flow.Filter does not qualify), the result's is too, preserving O(1) Flow.Reverse and constant-time Flow.Take and Flow.Drop; otherwise the result is forward-only.
Pass every Flow in one call. Reading an element from the result costs O(#inputs) and concatenating a result nests that cost, so accumulating in a loop (f = f.Concat(x)) makes a traversal quadratic in the number of iterations. Collect the parts first and call f.Concat(parts...) once, or insert Flow.Cache to flatten a chain already built that way.
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
combined := funq.From(1, 2).Concat(funq.From(3), funq.From(4, 5)).Slice()
fmt.Println(combined)
}
Output: [1 2 3 4 5]
func (Flow[T]) DistinctBy ¶
func (f Flow[T]) DistinctBy[K comparable](key func(T) K) Flow[T]
DistinctBy returns a Flow that yields each element of f once, keeping the first occurrence for each key extracted by key.
DistinctBy is the method form of Distinct: its comparable constraint attaches to its own type parameter K rather than to T, which is why it can be a method (see Distinct for why the T-is-the-key form cannot). Distinct(f) is equivalent to f.DistinctBy(Identity).
func (Flow[T]) Drop ¶
Drop skips the first n elements.
Drop is O(1) and fully lazy while the element count is statically known, and lazy on a forward-only Flow, where it skips as it traverses; Flow describes both properties. In between — count unknown but the Flow not yet forward-only, e.g. straight after Flow.Filter — Drop scans the upstream pipeline once at construction time to locate the boundary and keeps only that boundary, so any predicate involved runs then and again on every later terminal operation; Flow.Cache before the Drop collapses that to a single run.
func (Flow[T]) DropWhile ¶
DropWhile skips leading elements while pred is true.
pred determines the boundary, so DropWhile scans the upstream pipeline once at construction time exactly as Flow.Drop does in its scanning case, but whether or not the element count is known; only a forward-only Flow stays fully lazy. When the input's count was statically known, the result's is too — recovered in O(1) from the width of the scanned boundary, the same way Flow.Drop adjusts its bounds.
func (Flow[T]) Filter ¶
Filter keeps only the elements that satisfy pred.
Filter is lazy however the Flow was built: pred runs once per element on every traversal of the result, so use Flow.Cache when pred is expensive and the result is traversed more than once. Giving up the statically known count is also what makes a later Flow.Drop or Flow.Take scan eagerly.
Chaining several Filter calls nests one closure per call around f.at, which measured roughly on par with dropping to the sequential representation at a chain depth of two, and clearly slower by a depth of four (BenchmarkFilterRepresentation) — combine the predicates with And into a single Filter call instead; that matches or beats the sequential chain's throughput at those depths while keeping the indexed representation (see the note below).
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
// Predicates combine with And/Or/Not and plug into Filter.
result := funq.From(-5, -2, 0, 3, 4, 7, 8, 12).
Filter(funq.And(funq.GreaterThan(0), func(v int) bool { return v%2 == 0 })).
Slice()
fmt.Println(result)
}
Output: [4 8 12]
func (Flow[T]) Fold ¶
Fold reduces the Flow into a single accumulator value, starting from init. Unlike Reduce, the accumulator may be of a different type than the elements.
func (Flow[T]) ForEach ¶
func (f Flow[T]) ForEach(fn func(T))
ForEach applies fn to each element for its side effects.
func (Flow[T]) ForEachIndexed ¶
ForEachIndexed applies fn to each element for its side effects, along with the element's 0-based position in iteration order (logical, as in Flow.MapIndexed).
func (Flow[T]) GroupBy ¶
func (f Flow[T]) GroupBy[K comparable](key func(T) K) map[K][]T
GroupBy groups elements by the key extracted by key. Each group preserves the relative order in which its elements appeared in the Flow.
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
groups := funq.From(1, 2, 3, 4, 5, 6).GroupBy(func(v int) int { return v % 2 })
fmt.Println(groups[0])
fmt.Println(groups[1])
}
Output: [2 4 6] [1 3 5]
func (Flow[T]) Map ¶
Map transforms each element with fn, possibly changing the element type; see the Flow documentation for why methods like this can carry their own type parameter.
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
doubled := funq.From(1, 2, 3, 4, 5).Map(func(v int) int { return v * 2 }).Slice()
fmt.Println(doubled)
}
Output: [2 4 6 8 10]
func (Flow[T]) MapIndexed ¶
MapIndexed transforms each element with fn, which also receives the element's 0-based position in iteration order.
The position is logical, not a source index: it counts elements as they are yielded, so it stays 0, 1, 2, ... after Filter or Reverse. Unlike Flow.Map, MapIndexed always leaves the Flow forward-only (see Flow).
func (Flow[T]) MaxBy ¶
MaxBy returns the element with the largest key, wrapped in Optional. When several elements share the largest key, the first one wins. It returns None for an empty Flow.
Pass Identity as key to compare the elements themselves; see Flow.MinBy for why there is no element-wise Max method.
A NaN key orders before every non-NaN key (see Flow.MinBy), so a NaN key never wins unless every key is NaN.
func (Flow[T]) MinBy ¶
MinBy returns the element with the smallest key, wrapped in Optional. When several elements share the smallest key, the first one wins. It returns None for an empty Flow.
Pass Identity as key to compare the elements themselves. There is no element-wise Min method, for the same reason as Distinct. A free function is not provided either, since the name is taken by the builtin `min`.
A NaN key orders before every non-NaN key, as under cmp.Compare and Flow.SortBy, so a NaN key wins wherever it appears in the Flow. Among several NaN keys the first wins, as for any other tie.
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
// Ties keep the first element.
shortest := funq.From("ccc", "a", "bb").MinBy(func(s string) int { return len(s) })
fmt.Println(shortest.MustGet())
}
Output: a
func (Flow[T]) Partition ¶
Partition splits the elements into those satisfying pred and those that do not, preserving relative order within each side. Both results are materialized (see Flow), so the upstream pipeline runs exactly once.
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
even, odd := funq.From(1, 2, 3, 4, 5).Partition(func(v int) bool { return v%2 == 0 })
fmt.Println(even.Slice())
fmt.Println(odd.Slice())
}
Output: [2 4] [1 3 5]
func (Flow[T]) Reduce ¶
Reduce combines all elements with fn, returning None for an empty Flow.
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
sum := funq.From(1, 2, 3, 4, 5).Reduce(func(a, b int) int { return a + b }).MustGet()
fmt.Println(sum)
}
Output: 15
func (Flow[T]) Reverse ¶
Reverse returns the elements in reverse order.
Reverse is O(1) and fully lazy unless the Flow is forward-only (see Flow): such a Flow cannot be walked backwards, so Reverse materializes it at construction time instead.
func (Flow[T]) Seq ¶
Seq returns a forward iterator over the elements of the Flow, in iteration order: the way out of funq into the standard library's iterator vocabulary (range-over-func, slices.Collect, slices.Sorted, ...). FromSeq is the way back in.
The iterator is a view of the Flow, not a snapshot: each range over it may re-run the pipeline. Call Flow.Cache first when the pipeline is expensive and the iterator is consumed more than once. Breaking out of the range stops the pipeline where it is.
func (Flow[T]) SortBy ¶
SortBy sorts the elements in ascending order of the key extracted by key. The sort is stable, as in Flow.SortFunc.
key is called exactly once per element. It costs three slices of len n that Flow.SortFunc does not: the keys, a permutation of indices, and a fresh output slice.
Keys order through cmp.Compare, so a NaN key sorts before every non-NaN key — the same order Flow.MinBy and Flow.MaxBy use.
SortBy materializes the Flow immediately; see Flow.SortFunc.
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
byLength := funq.From("ccc", "a", "bb").
SortBy(func(s string) int { return len(s) }).
Slice()
fmt.Println(byLength)
}
Output: [a bb ccc]
func (Flow[T]) SortFunc ¶
SortFunc sorts the elements using cmp, which reports a negative number to mean a sorts before b, a positive number to mean a sorts after b, and zero to mean they're equal (the same contract as slices.SortFunc). The sort is stable.
Sorting needs every element up front, so SortFunc materializes the Flow immediately (see Flow).
func (Flow[T]) Take ¶
Take keeps the first n elements.
Take branches as Flow.Drop does and on the same conditions, with one difference in the scanning case: it keeps the elements the scan produced rather than the boundary, so the result is materialized (see Flow) and the upstream predicate or generator runs once per scanned element in total. That scan reaches no further than the n-th surviving element, so a short-circuiting Take over a large lazy source stays cheap; it runs to the end only when fewer than n survive, since that is the only way to establish there are no more.
func (Flow[T]) TakeWhile ¶
TakeWhile keeps leading elements while pred is true.
TakeWhile scans eagerly under the same condition and for the same reason as Flow.DropWhile: pred determines the boundary, and when the input's count was statically known, the result's is too, recovered the same way. When pred held for every element it returns the Flow unchanged, statically known count and all.
Unlike DropWhile, the range TakeWhile keeps is exactly the range its construction scan already walked, not the disjoint remainder — so an expensive pred or upstream pipeline runs once at construction and again in full on every later terminal operation. Flow.Cache before the TakeWhile collapses that to a single run, the same remedy as Flow.Drop's.
func (Flow[T]) To ¶
To applies fn to the Flow itself and returns fn's result. It is postfix function application (F#'s |> pipe), which keeps a chain fluent through the free functions that cannot be methods, such as Chunk and Distinct:
From(1, 2, 3, 4, 5).To(Chunk[int](2)).Map(...) // continues as Flow[[]int] From(1, 2, 2, 3).To(Distinct).Slice() // [1 2 3]
Unlike Flow.Map, which transforms each element with a func(T) U, To passes the whole Flow to fn exactly once. The result may be any type, not just another Flow.
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
// To applies a func(Flow[T]) U to the Flow itself, keeping free
// functions like Chunk and Distinct in a fluent chain.
sizes := funq.From(1, 2, 3, 4, 5).
To(funq.Chunk[int](2)).
Map(func(c []int) int { return len(c) }).
Slice()
sum := func(f funq.Flow[int]) int {
return f.Reduce(func(a, b int) int { return a + b }).OrElse(0)
}
total := funq.From(1, 2, 2, 3).To(funq.Distinct).To(sum)
fmt.Println(sizes, total)
}
Output: [2 2 1] 6
func (Flow[T]) ToMap ¶
func (f Flow[T]) ToMap[K comparable](key func(T) K) map[K]T
ToMap collects the elements into a map keyed by key. When two elements map to the same key, the first occurrence wins, consistent with Flow.DistinctBy.
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
// On a key collision the first occurrence wins.
byLen := funq.From("a", "bb", "c").ToMap(func(s string) int { return len(s) })
fmt.Println(byLen[1], byLen[2])
}
Output: a bb
type Fp ¶
type Fp[T, U any] = func(T) U
Fp is a plain function func(T) U that does not return an error. The p suffix means "plain". It is the counterpart of Fe, whose e suffix means "error". (Plain does not imply purity: an Fp may still have side effects.)
func And ¶
And returns a predicate that is true only when every preds is true. It short-circuits on the first false. It returns true when preds is empty.
func Between ¶
Between returns a predicate that is true for values within the inclusive range bounded by x and y. The bounds may be given in either order.
For floating-point types the predicate reports false whenever NaN is involved: a NaN argument compares false against any bound, and a NaN bound propagates through min/max, making both comparisons false.
func Const ¶
Const returns a function that ignores its argument and always returns u.
T, the argument type of the returned function, is not determined by u and is never inferred, not even from the context the result is used in. It comes first so that supplying it alone suffices: Const[int]("x") is a func(int) string, with U inferred from u.
func Equal ¶
func Equal[T comparable](x T) Fp[T, bool]
Equal returns a predicate that is true for values equal to x.
func GreaterThan ¶
GreaterThan returns a predicate that is true for values strictly greater than r.
func IgnoreError ¶
IgnoreError converts an Fe to an Fp that returns orElse when f fails.
func OneOf ¶
func OneOf[T comparable](vs ...T) Fp[T, bool]
OneOf returns a predicate that is true for values present in vs.
func Or ¶
Or returns a predicate that is true when any preds is true. It short-circuits on the first true. It returns false when preds is empty.
func PanicOnError ¶
PanicOnError converts an Fe to an Fp, panicking if f returns an error.
The panic value is an error wrapping the original err with %w, so a recover call can inspect it with errors.Is/errors.As to reach the original error.
type Optional ¶
type Optional[T any] struct { // contains filtered or unexported fields }
Optional is a null-safe container holding zero or one value of type T.
Like Flow, it is a concrete value type, so Optional.Map and Optional.FlatMap can change the value type while keeping the chain fluent and the result an Optional.
Optional is intentionally decoupled from Flow: it offers a small, focused API (presence, mapping, fallback) rather than the full sequence interface. Use Optional.AsFlow or Optional.Seq to bridge into Flow operations.
JSON support is scoped to the Optional's role as a fluent chain / return value that may be serialized (e.g. in an HTTP response), not as a persisted struct-field type. For database round-trips use database/sql's Null[T]. Optional intentionally does not implement sql.Scanner / driver.Valuer.
The zero value is None.
func FromPtr ¶
FromPtr converts a pointer to an Optional: None if p is nil, otherwise Some with the dereferenced value.
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
ptr := &[]int{1, 2, 3}[0]
opt := funq.FromPtr(ptr)
fmt.Println(opt.OrElse(0))
fmt.Println(funq.FromPtr[int](nil).OrElse(0))
}
Output: 1 0
func FromResult ¶
FromResult converts a (value, error) pair to an Optional: None if err is non-nil, otherwise Some(v). Its parameters match the results of an error-returning call, so such a call can be passed directly: FromResult(strconv.Atoi(s)).
The error is discarded, so reach for FromResult only when presence is all that matters downstream. It is the inverse of Optional.OrErr, but only for presence: a round trip replaces the original error with whichever one OrErr was handed.
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/cedar10bits/funq"
)
func main() {
// An error-returning call's results feed FromResult directly. The error is
// dropped.
fmt.Println(funq.FromResult(strconv.Atoi("42")).OrElse(-1))
fmt.Println(funq.FromResult(strconv.Atoi("nope")).OrElse(-1))
}
Output: 42 -1
func Some ¶
Some creates an Optional containing v.
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/cedar10bits/funq"
)
func main() {
// The chain stays Optional and can change type along the way.
val := funq.Some(42).
Filter(funq.GreaterThan(10)).
Map(func(v int) int { return v * 2 }). // Optional[int]
Map(strconv.Itoa) // Optional[string]
fmt.Println(val.OrElse("none"))
}
Output: 84
func (Optional[T]) AsFlow ¶
AsFlow bridges the Optional into a Flow of zero or one element.
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
// AsFlow bridges into the full Flow API.
nums := funq.Some(10).AsFlow().Map(func(v int) int { return v * 2 }).Slice()
fmt.Println(nums)
}
Output: [20]
func (Optional[T]) FlatMap ¶
FlatMap transforms the value into another Optional, propagating None unchanged.
func (Optional[T]) ForEach ¶
func (o Optional[T]) ForEach(fn func(T))
ForEach applies fn to the value if present.
func (Optional[T]) IsZero ¶
IsZero reports whether the Optional is None, matching the zero value being None. It lets encoding/json's ",omitzero" option drop a None field.
func (Optional[T]) Map ¶
Map transforms the value with fn, propagating None unchanged and possibly changing the value type; see the Optional documentation for why methods like this can carry their own type parameter.
func (Optional[T]) MarshalJSON ¶
MarshalJSON encodes Some as its value and None as JSON null.
Example ¶
package main
import (
"encoding/json"
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
type Profile struct {
Name string `json:"name"`
Email funq.Optional[string] `json:"email"` // null when None
Phone funq.Optional[string] `json:"phone,omitzero"` // omitted when None
}
// Some(v) -> v, None -> null. Phone is None and dropped by omitzero.
b, _ := json.Marshal(Profile{Name: "Ada", Email: funq.Some("ada@example.com")})
fmt.Println(string(b))
// On decode, an absent field or null becomes None.
var p Profile
_ = json.Unmarshal([]byte(`{"name":"Ada","email":null}`), &p)
fmt.Println(p.Email.IsPresent(), p.Phone.IsPresent())
}
Output: {"name":"Ada","email":"ada@example.com"} false false
func (Optional[T]) MustGet ¶
func (o Optional[T]) MustGet() T
MustGet returns the value if present, otherwise panics.
func (Optional[T]) OrElse ¶
func (o Optional[T]) OrElse(fallback T) T
OrElse returns the value if present, otherwise fallback.
func (Optional[T]) OrElseGet ¶
func (o Optional[T]) OrElseGet(fn func() T) T
OrElseGet returns the value if present, otherwise the result of fn.
func (Optional[T]) OrErr ¶
OrErr returns the value and a nil error if present, otherwise the zero value and err. It is the exit from a fluent chain back into Go's (value, error) idiom, so an Optional-returning terminal such as Flow.Find can be returned straight from an error-returning function.
It is the inverse of FromResult; see there for why a round trip loses the original error. ErrOnNone wraps this for use as a Groove pipeline stage.
Pass a non-nil err. A nil one makes None report success with the zero value, indistinguishable from Some of that value.
Example ¶
package main
import (
"errors"
"fmt"
"github.com/cedar10bits/funq"
)
// Sentinel errors for examples that turn an absent Optional into a failure.
var errNoEven = errors.New("no even element")
func main() {
// OrErr leaves the fluent chain for Go's (value, error) idiom, so an
// Optional-returning terminal can be returned as an ordinary failure.
firstEven := func(xs ...int) (int, error) {
return funq.From(xs...).Find(func(v int) bool { return v%2 == 0 }).
OrErr(errNoEven)
}
fmt.Println(firstEven(1, 3, 4, 5))
fmt.Println(firstEven(1, 3, 5))
}
Output: 4 <nil> 0 no even element
func (Optional[T]) Ptr ¶
func (o Optional[T]) Ptr() *T
Ptr returns a pointer to the value, or nil for None. It is the inverse of FromPtr, for interoperating with APIs that represent an optional value as a pointer (e.g. the AWS SDK or generated protobuf code).
The pointer refers to a copy of the value, so writing through it does not change the Optional. Each call returns a fresh pointer.
Example ¶
package main
import (
"fmt"
"github.com/cedar10bits/funq"
)
func main() {
fmt.Println(*funq.Some(42).Ptr())
fmt.Println(funq.None[int]().Ptr())
}
Output: 42 <nil>
func (Optional[T]) String ¶
String renders Some as "Some(v)" and None as "None", so %v and %s (e.g. in a log line) show a readable form instead of the struct's unexported fields.
func (*Optional[T]) UnmarshalJSON ¶
UnmarshalJSON decodes JSON into the Optional. A JSON null yields None; any other value is decoded into T and yields Some. When the Optional is a value- type struct field, an absent field leaves it at its zero value (None) because UnmarshalJSON is never called. Absent and null therefore both map to None.
type Pair ¶
type Pair[A, B any] struct { First A Second B }
Pair holds two values of possibly different types, e.g. the result of Zip.
type Track ¶
type Track[T0, T1 any] struct { // contains filtered or unexported fields }
Track is an error-returning function pipeline under construction. It has no useful zero value: build one with Groove and extend it with Track.Jam.
In brief:
- Groove cuts a track's first groove
- Jam adds a stage to it
- Play runs it
It is the railway-oriented-programming counterpart of Chain: on failure a Track stops at the stage that failed and reports which one, rather than running the rest of the pipeline on a zero value.
func Groove ¶
Groove cuts a Track's first stage from f: T0 -> (T1, error). Add further stages with Track.Jam, and run the finished pipeline with Track.Play.
Example ¶
package main
import (
"fmt"
"strconv"
"github.com/cedar10bits/funq"
)
func main() {
doubleOrError := func(n int) (int, error) {
if n > 1000 {
return 0, fmt.Errorf("number too large: %d", n)
}
return n * 2, nil
}
// Groove(f) cuts the first stage; each Jam appends exactly one more.
parseDoubleStringify := funq.Groove(strconv.Atoi).
Jam(doubleOrError).
Jam(funq.NilError(strconv.Itoa))
result, err := parseDoubleStringify.Play("21")
fmt.Println(result, err)
}
Output: 42 <nil>
func (Track[T0, T1]) Jam ¶
Jam appends exactly one stage, g: T1 -> (T2, error), to the track. It does not repeat or retry the stage it appends.
func (Track[T0, T1]) Play ¶
Play executes the track from t0 through every stage in order. If a stage returns an error, Play stops there and returns an error reporting the failing stage's position and the track's total stage count, for example "funq: Groove pipeline failed at stage 3 of 5: <underlying error>".
Play is itself a method value of type Fe[T0, T1]: a built Track drops straight into anything that takes a plain error-returning function, for example as a stage of another Track via inner.Play. When it is used that way, the inner track's own stage numbering is preserved in its error: a failure inside inner.Play still reports "stage k of <inner's own stage count>", wrapped by the outer track's error for the stage that called it.