A fluent, chainable library for functional-style slice operations in Go. Provides a familiar API for filtering, mapping, reducing, and transforming collections with full type safety via Go generics.
go get github.com/RobGraham/fluentimport "github.com/RobGraham/fluent"
// Basic filtering and chaining
activeProducts := fn.Of(products).
Filter(func(p Product) bool { return p.Active }).
Filter(func(p Product) bool { return p.Price > 50 }).
Take(10).
Collect()
// Type-changing operations use package functions
names := fn.Map(
fn.Of(products).Filter(func(p Product) bool { return p.Active }),
func(p Product) string { return p.Name },
).Collect()
// Deep nested filtering with FlatMap
inStockSKUs := fn.Map(
fn.FlatMap(fn.Of(products), func(p Product) []Variant {
return p.Variants
}).Filter(func(v Variant) bool {
return v.InStock
}),
func(v Variant) string { return v.SKU },
).Collect()Due to Go's type system limitation where methods cannot introduce new type parameters, this library uses a hybrid approach:
- Same-type operations → chainable methods (
.Filter(),.Take(),.Reverse()) - Type-changing operations → package functions (
fn.Map(),fn.FlatMap(),fn.Reduce())
This provides the best balance of ergonomics within Go's constraints.
- Immutable: All operations return new slices; originals are never mutated
- Lazy-ish: Operations chain but execute immediately (not truly lazy iterators)
- Type-safe: Full generic support with compile-time type checking
- Zero dependencies: Uses only Go standard library
This library is designed with performance in mind. Key optimizations include:
- Pre-sized allocations: Functions like
Union,Difference, andIntersectioncalculate expected sizes upfront to minimize map and slice reallocations during growth. - Early exits: Operations like
Intersectionshort-circuit when results become empty, avoiding unnecessary iteration over remaining inputs. - Direct iteration: Predicates are called directly without wrapper closures where possible, reducing function call overhead.
- Standard library leverage: Operations delegate to optimized
slicesandmapspackage functions (e.g.,slices.Clone,slices.Concat) where they outperform manual implementations.
Some operations have performance characteristics that vary based on your data:
- Filter/Reject: Uses clone-then-delete rather than build-from-scratch. This provides consistent performance regardless of how many elements match, but may use more memory when very few elements pass the predicate.
- FlatMap: Iterates twice (once to calculate total size, once to build the result) to avoid intermediate allocations. This is faster for typical struct field access but could be slower if your transform function is computationally expensive.
- SortBy: Computes the key function during each comparison. For simple field access this is optimal, but if your key function is expensive, consider precomputing keys into a struct and sorting that instead.
For most use cases, these trade-offs favor the common path. If you're working with performance-critical code, benchmark with your actual data.
fn.Of(slice []T) Slice[T] // Wrap a slice for fluent operations
slice.Collect() []T // Extract the final result| Method | Description |
|---|---|
.Filter(predicate) |
Keep elements matching predicate |
.Reject(predicate) |
Remove elements matching predicate |
.Take(n) |
First n elements |
.TakeRight(n) |
Last n elements |
.TakeWhile(predicate) |
Take while predicate is true |
.Drop(n) |
Skip first n elements |
.DropRight(n) |
Skip last n elements |
.DropWhile(predicate) |
Drop while predicate is true |
.Reverse() |
Reverse order |
.Concat(others...) |
Append slices |
.Append(elements...) |
Append elements |
.Prepend(elements...) |
Prepend elements |
.Clone() |
Shallow copy |
.ForEach(fn) |
Execute for each (returns self) |
.ForEachIndexed(fn) |
Execute with index |
| Method | Return Type | Description |
|---|---|---|
.Collect() |
[]T |
Get underlying slice |
.Len() |
int |
Number of elements |
.IsEmpty() |
bool |
True if empty |
.First() |
(T, bool) |
First element |
.Last() |
(T, bool) |
Last element |
.Nth(n) |
(T, bool) |
Element at index (negative = from end) |
.Find(predicate) |
(T, bool) |
First matching element |
.FindLast(predicate) |
(T, bool) |
Last matching element |
.FindIndex(predicate) |
int |
Index of first match (-1 if none) |
.FindLastIndex(predicate) |
int |
Index of last match |
.Some(predicate) |
bool |
Any element matches |
.Every(predicate) |
bool |
All elements match |
.None(predicate) |
bool |
No elements match |
.Count(predicate) |
int |
Count of matching elements |
.Partition(predicate) |
(Slice[T], Slice[T]) |
Split by predicate |
.Chunk(size) |
[][]T |
Split into chunks |
// Transform
fn.Map(s, func(T) U) Slice[U] // Transform each element
fn.FlatMap(s, func(T) []U) Slice[U] // Transform and flatten
fn.Flatten(s Slice[[]T]) Slice[T] // Flatten nested slices
// Aggregate
fn.Reduce(s, initial U, func(U, T) U) U
fn.ReduceRight(s, initial, fn) U
// Group
fn.GroupBy(s, keyFn) map[K][]T // Group by key
fn.KeyBy(s, keyFn) map[K]T // Index by key
fn.CountBy(s, keyFn) map[K]int // Count by key
// Sort
fn.SortBy(s, keyFn) Slice[T] // Sort by comparable key
fn.SortByDesc(s, keyFn) Slice[T] // Sort descending
fn.SortFunc(s, cmpFn) Slice[T] // Custom comparator
// Unique
fn.Unique(s Slice[T]) Slice[T] // Remove duplicates (T must be comparable)
fn.UniqueBy(s, keyFn) Slice[T] // Unique by key function
// Set Operations
fn.Contains(s, element) bool
fn.Compact(s) Slice[T] // Remove zero values
fn.Difference(s, others...) Slice[T] // Elements not in others
fn.Intersection(s, others...) Slice[T] // Common elements
fn.Union(s, others...) Slice[T] // All unique elements
// Aggregations
fn.Max(s) (T, bool) // Maximum (T must be ordered)
fn.Min(s) (T, bool) // Minimum
fn.Sum(s) T // Sum (T must be ordered)
fn.MaxBy(s, keyFn) (T, bool) // Max by key
fn.MinBy(s, keyFn) (T, bool) // Min by key
fn.SumBy(s, keyFn) K // Sum by key
// Conversion
fn.ToMap(s, keyFn, valueFn) map[K]V
fn.ToSet(s) map[T]struct{}
fn.FromMap(m) Slice[V]
fn.FromMapKeys(m) Slice[K]
fn.FromMapEntries(m) Slice[Entry[K,V]]
// Utilities
fn.Range(start, end) Slice[int]lowStock := fn.FlatMap(
fn.Of(products).Filter(func(p Product) bool { return p.Active }),
func(p Product) []InventoryAlert {
return fn.Map(
fn.Of(p.Variants).Filter(func(v Variant) bool {
return v.InStock && v.Quantity < 20
}),
func(v Variant) InventoryAlert {
return InventoryAlert{
ProductName: p.Name,
SKU: v.SKU,
Quantity: v.Quantity,
}
},
).Collect()
},
).Collect()highValueCustomers := fn.Of(users).
Filter(func(u User) bool { return u.Active }).
Filter(func(u User) bool {
total := fn.Reduce(fn.Of(u.Orders), 0.0, func(acc float64, o Order) float64 {
return acc + o.Total
})
return total > 100
}).
Collect()completedLineItems := fn.FlatMap(
fn.FlatMap(
fn.Of(users).Filter(func(u User) bool { return u.Active }),
func(u User) []Order { return u.Orders },
).Filter(func(o Order) bool { return o.Status == "completed" }),
func(o Order) []LineItem { return o.LineItems },
).Collect()searchResults := fn.SortBy(
fn.Of(products).
Filter(func(p Product) bool { return p.Active }).
Filter(func(p Product) bool {
return fn.Of(p.Tags).Some(func(tag string) bool {
return strings.Contains(tag, query)
})
}),
func(p Product) float64 { return p.Price },
).Take(20).Collect()// Group products by category
byCategory := fn.GroupBy(fn.Of(products), func(p Product) string {
return p.Category
})
// Index users by ID for O(1) lookup
userIndex := fn.KeyBy(fn.Of(users), func(u User) int {
return u.ID
})
// Count products per category
categoryCounts := fn.CountBy(fn.Of(products), func(p Product) string {
return p.Category
})names := fn.Map(
fn.Of(products).Filter(func(p Product) bool { return p.Active }),
func(p Product) string { return p.Name },
).Collect()// products → variants → images
allImages := fn.FlatMap(
fn.FlatMap(fn.Of(products), func(p Product) []Variant { return p.Variants }),
func(v Variant) []Image { return v.Images },
).Collect()active, inactive := fn.Of(products).Partition(func(p Product) bool {
return p.Active
})totalRevenue := fn.Reduce(
fn.Of(orders),
0.0,
func(acc float64, o Order) float64 { return acc + o.Total },
)MIT