funq

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 29, 2026 License: MIT Imports: 5 Imported by: 0

README

funq

Go CI

Type-safe, composable utilities for functional programming in Go.

Overview

funq is a Go library that provides type-safe functional programming utilities using generics. It offers fluent, value-typed sequences, null-safe value handling, and function composition.

Flow[T] and Optional[T] are concrete value types (not interfaces). This lets their Map/FlatMap methods (and, on Flow, Fold) carry their own type parameter, so a chain can change the element type and stay fluent — a capability that requires the parameterized methods added in Go 1.27.

When to reach for funq (vs the standard library)

Go 1.23+'s iter, slices, and maps packages cover a lot of ground. funq does not compete with them for one-off transforms — it targets the cases they leave open.

Prefer the standard library (or a plain loop) when:

  • The transform is one or two steps. slices.Contains, slices.Sorted, or a small for loop is shorter and has zero overhead.
  • The code is a hot path that scans every element — a Flow pipeline is roughly an order of magnitude slower than a raw loop (see Performance).

Prefer funq when:

  • The pipeline has three or more steps, or changes element type mid-chain. Nested slices.X(slices.Y(...)) calls read inside-out. No standard library iterator adapter changes element type fluently.
  • You need operations the standard library does not provide: GroupBy, Partition, Zip, Optional, or railway-oriented error handling (Groove).
  • A lazy source lets a short-circuiting terminal (First, Find, Any) — or Take, which stops the scan the same way without ending the chain — stop early instead of materializing everything (see Performance).

The choice is not all-or-nothing: Flow.Seq returns a standard iter.Seq[T], so a funq chain can feed straight into standard library iterator adapters that consume one. FromSeq goes the other way, building a lazy funq chain from an existing iterator (e.g. slices.Values, maps.Keys, or a hand-rolled generator) without materializing it first.

Installation

go get github.com/cedar10bits/funq

Usage Examples

A taste of each type below. pkg.go.dev has runnable, compiler-verified examples for more operations than shown here, including SortBy, GroupBy, Zip, Partition, and Chunk: https://pkg.go.dev/github.com/cedar10bits/funq#pkg-examples

Flow - Slice Operations
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]
}
Optional - Null-Safe Values
// 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

Optional is decoupled from Flow: it offers a small, focused API (Map, FlatMap, Filter, OrElse, OrElseGet, Get, MustGet, Or, Ptr, String, ...). Use AsFlow() or Seq() to bridge into the full Flow API.

Optional also bridges to Go's (value, error) idiom. FromResult(strconv.Atoi(s)) takes an error-returning call directly, keeping presence and dropping the error. OrErr(err) goes the other way, turning a None back into a failure so an Optional-returning terminal like Find can be returned straight from an error-returning function. ErrOnNone(f, err) wraps OrErr in point-free form, so an Optional-returning stage drops into a Groove pipeline's Jam call without a closure.

JSON

Because an Optional is often returned from a chain and then serialized (e.g. in an HTTP response), it implements json.Marshaler / json.Unmarshaler:

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. On decode, an absent field or null becomes None.
b, _ := json.Marshal(Profile{Name: "Ada", Email: funq.Some("ada@example.com")})
// {"name":"Ada","email":"ada@example.com"}

Optional also implements IsZero, so the json:",omitzero" tag (Go 1.24+) drops a None field entirely, matching the zero value being None.

Scope: JSON support reflects Optional's role as a fluent chain / return value that may be serialized, not a persisted struct-field type — see the Optional type documentation for the full rationale, including why sql.Scanner/driver.Valuer aren't implemented instead.

Groove - Error-Aware Composition (Railway-Oriented)
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.
// string -> (int, error) -> (int, error) -> (string, error)
parseDoubleStringify := funq.Groove(strconv.Atoi).
	Jam(doubleOrError).
	Jam(funq.NilError(strconv.Itoa))

result, err := parseDoubleStringify.Play("21")
// result: "42", err: nil

A Track has no fixed length: keep calling Jam for as many stages as the pipeline needs. The built value is reusable across inputs. Since Play is itself an Fe[T0, T1] method value, it drops straight into anywhere a plain error-returning function is expected — including as a stage of another Track via inner.Play. Compose/Then/Run are the same shape for stages that cannot fail (see Utilities for Flow and Groove below).

On failure, Play stops at the stage that failed and returns an error reporting its position and the pipeline's stage count:

funq: Groove pipeline failed at stage 3 of 5: <underlying error>

A stage that reports absence rather than failure joins the pipeline through ErrOnNone; see Optional.

Utilities for Flow and Groove

Predicates – Build logic for filtering and validation:

// Use predicates with Flow.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()
// Output: [4 8 12]

Function Composition – Compose higher-order functions:

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
pipeline := funq.Compose(addThenFilter).Then(funq.Flow[int].Slice)
result1 := pipeline.Run(5)	// Output: []
result2 := pipeline.Run(10)	// Output: [20]

API Reference

For the complete, up-to-date list of methods and functions, see pkg.go.dev: https://pkg.go.dev/github.com/cedar10bits/funq

A few entry points, to get oriented:

  • From(values...), FromFn(n, fn), FromSeq(seq): create a Flow from values, a function, or an iterator. Flow.Seq goes the other way, returning an iter.Seq[T]
  • Cache(): materialize a Flow to run the upstream pipeline exactly once, then reuse the result across multiple terminal operations
  • Some(x), None[int](), FromPtr(ptr), FromResult(v, err): create Optionals. Optional.OrErr(err) and ErrOnNone(f, err) go back to (value, error)
  • Compose(f).Then(g)...Run(x): build a plain-function pipeline one stage at a time. Groove(f).Jam(g)...Play(x) is the error-returning counterpart, short-circuiting on the first error
  • Identity, Const(v): pass as the key or step argument of SortBy/MinBy/ Map and friends. Const's argument type is never inferred, so name it: Const[int]("x") is a func(int) string
  • Predicates (And, Or, LessThan, Between, OneOf, ...) are designed to plug directly into Filter/Map
  • Flow.To(fn) applies a func(Flow[T]) U to the Flow itself, keeping the free functions in a fluent chain: f.To(funq.Chunk[int](2)), f.To(funq.Distinct)
  • There is no Min/Max/Contains method on Flow — a parameterized method cannot constrain the receiver's T (see the Distinct doc comment). Membership is the free function funq.Contains(f, v). Element-wise min/max are spelled f.MinBy(funq.Identity) / f.MaxBy(funq.Identity)

Performance

Flow is roughly an order of magnitude slower than a raw loop on a full traversal — its value is readability and composition, not raw throughput. Laziness pays off on early exit from a lazy source (short-circuiting terminals like First, Find, Any, or Take without ending the chain), turning an O(N) computation into O(k). A terminal operation may re-run the pipeline from the source each time. Use Cache() to materialize an expensive pipeline once and reuse it. Count() and IsEmpty() are the exception — when the element count is statically known they answer from it without traversing at all. See docs/performance.md for the full benchmark breakdown, methodology, and reproduce commands.

Versioning

funq follows Semantic Versioning. It stays on 0.x for now, so the API may change between minor versions. Breaking changes are called out in CHANGELOG.md rather than silently shipped. v1.0.0 is not planned on a fixed schedule — it will happen once the API has settled, not simply once Go 1.27 reaches GA.

License

MIT License - see LICENSE for details.

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

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Chunk

func Chunk[T any](n int) func(Flow[T]) Flow[[]T]

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

func False

func False[T any](T) bool

False is a predicate that always reports false.

func Identity

func Identity[T any](t T) T

Identity returns its argument unchanged.

func True

func True[T any](T) bool

True is a predicate that always reports true.

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

func Compose[T0, T1 any](f Fp[T0, T1]) Chain[T0, T1]

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.

func (Chain[T0, T1]) Then

func (c Chain[T0, T1]) Then[T2 any](g Fp[T1, T2]) Chain[T0, T2]

Then appends one stage, g: T1 -> T2, to the chain.

type Fe

type Fe[T, U any] = func(T) (U, error)

Fe is a function func(T) (U, error) that may fail. It is the error-returning counterpart of Fp.

func ErrOnNone

func ErrOnNone[T, U any](f Fp[T, Optional[U]], err error) Fe[T, U]

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

func NilError

func NilError[T, U any](f Fp[T, U]) Fe[T, U]

NilError converts an Fp to an Fe whose error is always nil.

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:

  1. 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.
  2. 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:

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

func From[T any](s ...T) Flow[T]

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

func FromFn[T any](n int, fn func(int) T) Flow[T]

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

func FromSeq[T any](seq iter.Seq[T]) Flow[T]

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

func Zip[T, U any](a Flow[T], b Flow[U]) Flow[Pair[T, U]]

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

func (f Flow[T]) All(pred func(T) bool) bool

All reports whether every element satisfies pred (true for an empty Flow).

func (Flow[T]) Any

func (f Flow[T]) Any(pred func(T) bool) bool

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

func (f Flow[T]) Cache() Flow[T]

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

func (f Flow[T]) Concat(others ...Flow[T]) Flow[T]

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

func (f Flow[T]) Count() int

Count returns the number of elements in the Flow.

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

func (f Flow[T]) Drop(n int) Flow[T]

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

func (f Flow[T]) DropWhile(pred func(T) bool) Flow[T]

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

func (f Flow[T]) Filter(pred func(T) bool) Flow[T]

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

func (f Flow[T]) Find(pred func(T) bool) Optional[T]

Find returns the first element satisfying pred, wrapped in Optional.

func (Flow[T]) First

func (f Flow[T]) First() Optional[T]

First returns the first element, wrapped in Optional.

func (Flow[T]) FlatMap

func (f Flow[T]) FlatMap[U any](fn func(T) Flow[U]) Flow[U]

FlatMap maps each element to a Flow and concatenates the results.

func (Flow[T]) Fold

func (f Flow[T]) Fold[U any](init U, fn func(U, T) U) U

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

func (f Flow[T]) ForEachIndexed(fn func(int, T))

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

func (f Flow[T]) IsEmpty() bool

IsEmpty reports whether the Flow has no elements.

func (Flow[T]) Last

func (f Flow[T]) Last() Optional[T]

Last returns the last element, wrapped in Optional.

func (Flow[T]) Map

func (f Flow[T]) Map[U any](fn func(T) U) Flow[U]

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

func (f Flow[T]) MapIndexed[U any](fn func(int, T) U) Flow[U]

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

func (f Flow[T]) MaxBy[K cmp.Ordered](key func(T) K) Optional[T]

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

func (f Flow[T]) MinBy[K cmp.Ordered](key func(T) K) Optional[T]

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

func (f Flow[T]) Partition(pred func(T) bool) (matched, rest Flow[T])

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

func (f Flow[T]) Reduce(fn func(T, T) T) Optional[T]

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

func (f Flow[T]) Reverse() Flow[T]

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

func (f Flow[T]) Seq() iter.Seq[T]

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

func (f Flow[T]) Slice() []T

Slice materializes the Flow into a new slice.

func (Flow[T]) SortBy

func (f Flow[T]) SortBy[K cmp.Ordered](key func(T) K) Flow[T]

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

func (f Flow[T]) SortFunc(cmp func(a, b T) int) Flow[T]

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

func (f Flow[T]) Take(n int) Flow[T]

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

func (f Flow[T]) TakeWhile(pred func(T) bool) Flow[T]

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

func (f Flow[T]) To[U any](fn func(Flow[T]) U) U

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

func And[T any](preds ...Fp[T, bool]) Fp[T, bool]

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 AtLeast

func AtLeast[T cmp.Ordered](r T) Fp[T, bool]

AtLeast returns a predicate that is true for values greater than or equal to r.

func AtMost

func AtMost[T cmp.Ordered](r T) Fp[T, bool]

AtMost returns a predicate that is true for values less than or equal to r.

func Between

func Between[T cmp.Ordered](x, y T) Fp[T, bool]

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

func Const[T, U any](u U) Fp[T, U]

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

func GreaterThan[T cmp.Ordered](r T) Fp[T, bool]

GreaterThan returns a predicate that is true for values strictly greater than r.

func IgnoreError

func IgnoreError[T, U any](f Fe[T, U], orElse U) Fp[T, U]

IgnoreError converts an Fe to an Fp that returns orElse when f fails.

func Implies

func Implies[T any](l, r Fp[T, bool]) Fp[T, bool]

Implies returns a predicate that is true unless l is true and r is false.

func LessThan

func LessThan[T cmp.Ordered](r T) Fp[T, bool]

LessThan returns a predicate that is true for values strictly less than r.

func Nand

func Nand[T any](l, r Fp[T, bool]) Fp[T, bool]

Nand returns a predicate that is true unless both l and r are true.

func Nor

func Nor[T any](l, r Fp[T, bool]) Fp[T, bool]

Nor returns a predicate that is true only when both l and r are false.

func Not

func Not[T any](pred Fp[T, bool]) Fp[T, bool]

Not returns a predicate that negates pred.

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

func Or[T any](preds ...Fp[T, bool]) Fp[T, bool]

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

func PanicOnError[T, U any](f Fe[T, U]) Fp[T, U]

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.

func Xnor

func Xnor[T any](l, r Fp[T, bool]) Fp[T, bool]

Xnor returns a predicate that is true when l and r agree.

func Xor

func Xor[T any](l, r Fp[T, bool]) Fp[T, bool]

Xor returns a predicate that is true when exactly one of l or r is true.

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

func FromPtr[T any](p *T) Optional[T]

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

func FromResult[T any](v T, err error) Optional[T]

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 None

func None[T any]() Optional[T]

None creates an empty Optional.

func Some

func Some[T any](v T) Optional[T]

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

func (o Optional[T]) AsFlow() Flow[T]

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

func (o Optional[T]) Filter(pred func(T) bool) Optional[T]

Filter keeps the value only if it satisfies pred.

func (Optional[T]) FlatMap

func (o Optional[T]) FlatMap[U any](fn func(T) Optional[U]) Optional[U]

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

func (o Optional[T]) Get() (T, bool)

Get returns the value and whether it is present, in the Go comma-ok style.

func (Optional[T]) IsEmpty

func (o Optional[T]) IsEmpty() bool

IsEmpty reports whether the Optional is empty.

func (Optional[T]) IsPresent

func (o Optional[T]) IsPresent() bool

IsPresent reports whether a value is present.

func (Optional[T]) IsZero

func (o Optional[T]) IsZero() bool

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

func (o Optional[T]) Map[U any](fn func(T) U) Optional[U]

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

func (o Optional[T]) MarshalJSON() ([]byte, error)

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

func (o Optional[T]) Or(other Optional[T]) Optional[T]

Or returns o if present, otherwise other.

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

func (o Optional[T]) OrErr(err error) (T, error)

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

func (o Optional[T]) Seq() iter.Seq[T]

Seq returns an iterator yielding the value if present, otherwise nothing.

func (Optional[T]) String

func (o Optional[T]) String() 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

func (o *Optional[T]) UnmarshalJSON(data []byte) error

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

func Groove[T0, T1 any](f Fe[T0, T1]) Track[T0, T1]

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

func (t Track[T0, T1]) Jam[T2 any](g Fe[T1, T2]) Track[T0, T2]

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

func (t Track[T0, T1]) Play(t0 T0) (T1, error)

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.

Jump to

Keyboard shortcuts

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