Gofu leverages Go 1.27+ method generics for readable, chainable slice and map transformations - with the same fluent API for sequential and parallel execution.
Zero reflection, zero interface{}/any, zero dependencies.
Requires Go 1.27+ (not yet released). See golang-1.27.md to build from source on Go 1.26.
Because chaining .Map().Filter().Reduce() reads like a story. The standard library gets the job done, but functional composition makes data transformation feel like the main event instead of boilerplate.
And for parallel work? Gofu provides the same API around sequential and concurrent execution - write same code, parallelize by calling Parallel with a scaling function.
Method generics (Go 1.27+) let methods like Map[R], FlatMap[R], GroupBy[K], and Reduce[R] infer their result type from the function argument - no explicit type annotations at the call site. The type parameter flows naturally from receiver through function to result.
// Sequential - reads top to bottom
words := gofu.Slice[string]{
"the quick brown fox jumps over the lazy dog",
}.
FlatMap(strings.Fields).
Filter(func(w string) bool { return len(w) > 3 }).
DistinctBy(func(s string) rune {
r, _ := utf8.DecodeRuneInString(s)
return r
}).
SortBy(strings.Compare)
fmt.Println(words) // [brown dog fox jumps lazy over quick the]
// Parallel - add Parallel with a scaling function
big := gofu.Slice[string]{
"the quick brown fox jumps over the lazy dog",
"pack my box with five dozen liquor jugs",
}.
Parallel(func(dataSize int) int { return 4 }).
FlatMap(strings.Fields).
Filter(func(w string) bool { return len(w) > 3 }).
Collect()- Type inference first:
slice.Map(strconv.Itoa)infers bothTandRfrom the receiver and function - no explicit type arguments needed. - Zero reflection: no
reflectpackage, nointerface{}, noanyin the core logic. - Same API, parallel on tap:
SliceandSliceParallelshare the same method set. CallParallelto switch,Sequential()to unwrap. - Mutation opt-in: All methods are immutable by default. Call
.Mut()to access in-place mutation methods that avoid unnecessary allocations.
Install:
go get github.com/iamgoroot/gofu
import "github.com/iamgoroot/gofu"Wrap a Go slice or map, chain transformations, then collect:
// Slice
data := []int{1, 2, 3, 4, 5, 6}
s := gofu.Slice[int](data) // explicit
s2 := gofu.S(data) // shorthand, type inferred
// Slice[T] is []T - direct indexing, slicing, and range work
s = s[:4]
shuffledStrings := s.Map(strconv.Itoa).Shuffle()// Map
goMap := map[string]int{"one": 1, "two": 2}
m := gofu.M(goMap) // shorthand
m2 := gofu.Map[string, int](goMap) // explicit
m3 := gofu.Map[string, int]{"three": 3} // initialized
// Map[K,V] is map[K]V - direct key access and range work
one := m["one"]
m4 := m.Merge(m3)// Parallel
ps := gofu.Slice[int](hugeData).Parallel(8)All methods are immutable by default. Call .Mut() for opt-in in-place mutation:
// SortBy will mutate. Further calls like Filter will be immutable until Mut() called again
s.Mut().SortBy(strings.Compare).Filter(func(v string) bool { return len(v) > 3 })Full runnable examples are in the examples/ directory:
| Directory | Description | Run |
|---|---|---|
examples/slice/ |
Word deduplication with Map, FlatMap, Filter, DistinctBy, SortBy |
go run ./examples/slice/main.go |
examples/map/ |
Product catalog with Merge, Mut().Filter, ToSlice |
go run ./examples/map/main.go |
examples/starwars/ |
SWAPI data pipeline with Parallel, Filter, MapFilter, Sequential, Map |
go run ./examples/starwars/main.go |
examples/rickandmorty/ |
R&M API pipeline with Parallel, Filter, MapFilter, FlatMap, DistinctBy, Fold |
go run ./examples/rickandmorty/main.go |
- [IMMUTABLE] - Returns a new collection. The original data is never modified.
- [MUTATES] - Modifies the receiver in-place. Accessed via
.Mut(). More efficient (zero allocations beyond the backing array) but alters the original data. - [READONLY] - Inspects the collection without allocating or mutating. Returns a value (count, element, boolean) or provides iteration callbacks.
| Method | Description | Example | Time | Space |
|---|---|---|---|---|
Map |
Applies fn to each element | s.Map(strconv.Itoa) |
O(n) | O(n) |
MapFilter |
Maps and filters in one pass | s.MapFilter(func(v int) (string, bool) { if v%2 == 0 { return strconv.Itoa(v), false }; return "", true }) |
O(n) | O(k) |
FlatMap |
Applies fn, then flattens results | s.FlatMap(strings.Fields) |
O(n+m) | O(m) |
Append |
Returns new slice with items appended | s.Append(4, 5) |
O(n) | O(n) |
Prepend |
Returns new slice with items prepended | s.Prepend(0, -1) |
O(n) | O(n) |
Cast |
Attempts type-safe conversion of elements | s.Cast[string]() |
O(n) | O(n) |
ToMap |
Converts to Map via key-value fn | s.ToMap(func(v int) (int, int) { return v, v*2 }) |
O(n) | O(n) |
Reduce / Fold |
Combines elements with fn from initial | s.Reduce(0, func(a, b int) int { return a + b }) |
O(n) | O(1) |
GroupBy |
Groups elements by key | s.GroupBy(func(v string) byte { return v[0] }) |
O(n) | O(n) |
DistinctBy |
Removes duplicates by key | s.DistinctBy(func(v string) byte { return v[0] }) |
O(n) | O(n) |
MapChunks |
Processes chunks and collects results | s.MapChunks(4, func(i int, chunk Slice[int]) T { ... }) |
O(n) | O(n) |
Filter |
New slice with matching elements | s.Filter(func(v int) bool { return v > 0 }) |
O(n) | O(k) |
Partition |
Splits slice by predicate | s.Partition(func(v int) bool { return v%2 == 0 }) |
O(n) | O(n) |
Reverse |
Returns new slice in reverse order | s.Reverse() |
O(n) | O(n) |
Shuffle |
Randomly shuffles elements | s.Shuffle() |
O(n) | O(n) |
SortBy |
Returns new slice sorted by comparator | s.SortBy(func(a, b int) int { return a - b }) |
O(n log n) | O(n) |
Copy |
Returns an independent clone | s.Copy() |
O(n) | O(n) |
Parallel |
Wraps slice for concurrent execution | s.Parallel(4) |
O(1) | O(1) |
ParallelScale |
Wraps with custom scaling function | s.ParallelScale(func(n int) int { return 4 }) |
O(1) | O(1) |
ParallelAuto |
Auto-scales by CPU cores & log size | s.ParallelAuto() |
O(1) | O(1) |
| Method | Description | Example | Time | Space |
|---|---|---|---|---|
Mut().Delete |
Removes matching elements in-place | s.Mut().Delete(func(v int) bool { return v%2 == 0 }) |
O(n) | O(1) |
Mut().Compact |
Removes consecutive duplicates | s.Mut().Compact(func(a, b int) bool { return a == b }) |
O(n) | O(1) |
Mut().Reverse |
Reverses elements in-place | s.Mut().Reverse() |
O(n) | O(1) |
Mut().SortBy |
Sorts elements in-place | s.Mut().SortBy(func(a, b int) int { return a - b }) |
O(n log n) | O(1) |
Mut().Shuffle |
Shuffles elements in-place | s.Mut().Shuffle() |
O(n) | O(1) |
| Method | Description | Example | Time | Space |
|---|---|---|---|---|
| Search | ||||
IndexBy |
Creates Map indexed by key | s.IndexBy(func(v int) string { return strconv.Itoa(v) }) |
O(n) | O(n) |
Find |
Finds first element matching fn | s.Find(func(v int) bool { return v > 15 }) |
O(n) | O(1) |
FindIndex |
Returns index of first match | s.FindIndex(func(v int) bool { return v > 15 }) |
O(n) | O(1) |
FindOr |
Finds first match or returns default | s.FindOr(0, func(v int) bool { return v > 15 }) |
O(n) | O(1) |
ContainsFunc / SatisfyAny |
Checks if any element matches | s.ContainsFunc(func(v int) bool { return v == 20 }) |
O(n) | O(1) |
SatisfyAll |
Checks if every element matches | s.SatisfyAll(func(v int) bool { return v > 5 }) |
O(n) | O(1) |
SatisfyNone |
Checks if no element matches | s.SatisfyNone(func(v int) bool { return v < 5 }) |
O(n) | O(1) |
CountBy |
Counts elements satisfying fn | s.CountBy(func(v int) bool { return v%2 == 0 }) |
O(n) | O(1) |
| Access | ||||
First |
Returns the first element | s.First() |
O(1) | O(1) |
FirstOr |
Returns first element or default | s.FirstOr(0) |
O(1) | O(1) |
Last |
Returns the last element | s.Last() |
O(1) | O(1) |
LastOr |
Returns last element or default | s.LastOr(0) |
O(1) | O(1) |
Count / Len |
Returns number of elements | s.Count() |
O(1) | O(1) |
Collect |
Returns the underlying []T |
s.Collect() |
O(1) | O(1) |
Get |
Returns element at index and ok flag | s.Get(1) |
O(1) | O(1) |
GetOr |
Returns element at index or fallback | s.GetOr(5, 0) |
O(1) | O(1) |
| Iteration | ||||
ForEach |
Calls fn for each element | s.ForEach(func(v int) { fmt.Println(v) }) |
O(n) | O(1) |
ForEachChunk |
Iterates over chunks | s.ForEachChunk(2, func(i int, chunk Slice[int]) { ... }) |
O(n) | O(1) |
ToIter |
Returns iter.Seq[T] over elements |
s.ToIter() |
O(n) | O(1) |
SliceParallel[T] provides concurrent versions of all Slice[T] operations. Operations split the slice into chunks, process each chunk in its own goroutine, and merge results. Best for CPU-bound work on large slices.
ps := gofu.Slice[int](hugeData).Parallel(8)SliceParallel[T] is a struct, not a slice. Use Sequential() or Collect() before native Go access (indexing, slicing, range):
first := ps.Sequential()[:1] // Sequential() returns Slice[T]
vals := ps.Collect()[:1] // Collect() returns []TSliceParallel[T] provides all Slice[T] operations with concurrent execution. The differences are:
- Return types use
SliceParallel[T]instead ofSlice[T]for continued chaining (e.g.ps.Map(fn)returnsSliceParallel[R], notSlice[R]) - Time complexity is O(n/p) for data-parallel operations (p = parallelism level)
- Additional methods for managing parallelism:
| Method | Description | Example | Time | Space |
|---|---|---|---|---|
Sequential |
Unwraps to the underlying Slice[T] |
ps.Sequential() |
O(1) | O(1) |
Call .Mut() for in-place mutation on SliceParallel[T] as well - same methods as Slice.Mut().
| Method | Description | Example | Time | Space |
|---|---|---|---|---|
Map |
Transforms keys and values | m.Map(func(k string, v int) (string, string) { return k, strconv.Itoa(v) }) |
O(n) | O(n) |
Reduce |
Combines entries from initial value | m.Reduce(0, func(acc int, k string, v int) int { return acc + v }) |
O(n) | O(1) |
ToSlice |
Converts to Slice via mapper fn | m.ToSlice(func(k string, v int) string { return k + strconv.Itoa(v) }) |
O(n) | O(n) |
Merge |
Merges entries from other maps | m.Merge(other) |
O(n+m) | O(n+m) |
Intersect |
Keeps keys present in all maps | m.Intersect(other) |
O(n+m) | O(min(n,m)) |
Difference |
Keeps keys not in other map | m.Difference(other) |
O(n+m) | O(n) |
Keys |
Returns a slice of all keys | m.Keys() |
O(n) | O(n) |
Values |
Returns a slice of all values | m.Values() |
O(n) | O(n) |
Entries |
Returns slice of Entry[K,V] pairs |
m.Entries() |
O(n) | O(n) |
| Method | Description | Example | Time | Space |
|---|---|---|---|---|
Mut().Filter |
Keeps matching entries in-place | m.Mut().Filter(func(k string, v int) bool { return v > 1 }) |
O(n) | O(1) |
Mut().Delete |
Removes entries by keys | m.Mut().Delete("a", "c") |
O(k) | O(1) |
Mut().DeleteFunc |
Removes entries matching fn | m.Mut().DeleteFunc(func(k string, v int) bool { return v%2 == 0 }) |
O(n) | O(1) |
| Method | Description | Example | Time | Space |
|---|---|---|---|---|
Count |
Returns number of entries | m.Count() |
O(1) | O(1) |
ForKeys |
Calls fn for each key | m.ForKeys(func(k string) { ... }) |
O(n) | O(1) |
ForValues |
Calls fn for each value | m.ForValues(func(v int) { ... }) |
O(n) | O(1) |
ForEntries |
Calls fn for each key-value pair | m.ForEntries(func(k string, v int) { ... }) |
O(n) | O(1) |
ToIter |
Returns iter.Seq2[K,V] over entries |
m.ToIter() |
O(n) | O(1) |
Apache 2.0. See LICENSE.