Skip to content

iamgoroot/gofu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gofu - functional Go

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.

Why

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.

Quick Example

// 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()

Core Philosophy

  • Type inference first: slice.Map(strconv.Itoa) infers both T and R from the receiver and function - no explicit type arguments needed.
  • Zero reflection: no reflect package, no interface{}, no any in the core logic.
  • Same API, parallel on tap: Slice and SliceParallel share the same method set. Call Parallel to 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.

Usage

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 })

Examples

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

Method Taxonomy

  • [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.

Slice[T]

Transformation [IMMUTABLE]

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)

Mutation [MUTATES]

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)

Read-only [READONLY]

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]

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 []T

SliceParallel[T] provides all Slice[T] operations with concurrent execution. The differences are:

  • Return types use SliceParallel[T] instead of Slice[T] for continued chaining (e.g. ps.Map(fn) returns SliceParallel[R], not Slice[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().

Map[K, V]

Transformation [IMMUTABLE]

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)

Mutation [MUTATES]

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)

Read-only [READONLY]

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)

License

Apache 2.0. See LICENSE.

About

Nice functional slice and map wrappers for golang 1.27 - It's time to go functional

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages