-
Notifications
You must be signed in to change notification settings - Fork 43
Description
Functional/Immutability Enhancements
This PR applies moderate, tasteful functional programming and immutability techniques to improve code clarity, safety, testability, and maintainability across the pkg/ directory.
Summary of Changes
1. New Functional Helpers in sliceutil (pkg/sliceutil/sliceutil.go)
Added three generic helper functions to support functional programming patterns:
Filter[T any](slice []T, predicate func(T) bool) []T- Returns elements matching a predicate (pure function)Map[T, U any](slice []T, transform func(T) U) []U- Transforms each element (pure function)MapToSlice[K comparable, V any](m map[K]V) []K- Converts map keys to slice (pure function)
These helpers enable immutable transformations throughout the codebase and are type-safe using Go generics.
Files affected:
pkg/sliceutil/sliceutil.go- Added 3 new generic functions
2. Immutable Sort Operations (pkg/workflow/action_pins.go)
Changed sortPinsByVersion() from in-place mutation to immutable operation:
// Before: Mutates input slice
func sortPinsByVersion(pins []ActionPin) {
sort.Slice(pins, func(i, j int) bool { ... })
}
// After: Returns new sorted slice without modifying input
func sortPinsByVersion(pins []ActionPin) []ActionPin {
result := make([]ActionPin, len(pins))
copy(result, pins)
sort.Slice(result, func(i, j int) bool { ... })
return result
}Benefits:
- Safer - no accidental side effects on shared slices
- More predictable - input remains unchanged
- Easier to test - pure function behavior
- Better for concurrent access - original data unmodified
Files affected:
pkg/workflow/action_pins.go- Made sort immutablepkg/workflow/action_pins_test.go- Updated test to use returned value
3. Functional Filter Operations (pkg/workflow/action_pins.go)
Replaced 4 imperative filter loops with functional sliceutil.Filter:
// Before: Imperative append loop (repeated 4 times)
var matchingPins []ActionPin
for _, pin := range actionPins {
if pin.Repo == actionRepo {
matchingPins = append(matchingPins, pin)
}
}
// After: Functional filter (declarative and immutable)
matchingPins := sliceutil.Filter(actionPins, func(pin ActionPin) bool {
return pin.Repo == actionRepo
})Locations converted:
GetActionPin()- Filter by repo (line ~139)GetActionPinWithData()- Filter by repo (line ~193)GetActionPinWithData()- Filter semver-compatible pins (line ~233)GetActionPinByRepo()- Filter by repo (line ~420)
Files affected:
pkg/workflow/action_pins.go- 4 filter conversions
4. Immutable Map-to-Slice Conversions (Multiple Files)
Replaced 11 map-to-slice extraction patterns with sliceutil.MapToSlice:
// Before: Mutable append loop pattern (repeated 11 times)
keys := make([]string, 0, len(myMap))
for key := range myMap {
keys = append(keys, key)
}
sort.Strings(keys)
// After: Functional helper (single immutable operation)
keys := sliceutil.MapToSlice(myMap)
sort.Strings(keys)Locations converted:
pkg/workflow/sandbox.go- Domain map conversion (line ~170)pkg/workflow/mcp_setup_generator.go- Safe inputs tool names (line ~332)pkg/workflow/mcp_setup_generator.go- Safe inputs secrets (line ~410)pkg/workflow/mcp_setup_generator.go- MCP env vars (line ~450)pkg/workflow/mcp_setup_generator.go- Gateway env vars (line ~518)pkg/workflow/mcp_setup_generator.go- Gateway env vars Add workflow: githubnext/agentics/weekly-research #2 (line ~606)pkg/workflow/mcp_config_custom.go- Env keys (line ~319)pkg/workflow/mcp_config_custom.go- Header keys rejig docs #1 (line ~392)pkg/workflow/mcp_config_custom.go- Header keys Add workflow: githubnext/agentics/weekly-research #2 (line ~409)pkg/workflow/env.go- Header keys (line ~24)
Files affected:
pkg/workflow/sandbox.go- 1 conversionpkg/workflow/mcp_setup_generator.go- 5 conversionspkg/workflow/mcp_config_custom.go- 3 conversionspkg/workflow/env.go- 1 conversion
Benefits
Safety
- Reduced mutation surface area - 15+ mutable operations converted to immutable
- No accidental side effects - Functions don't modify shared state
- Safer concurrent access - Original data structures remain unmodified
- Predictable behavior - Pure functions always produce same output for same input
Clarity
- Declarative over imperative - Filter/map express intent more clearly than loops
- Self-documenting -
sliceutil.Filter(pins, predicate)is immediately understandable - Reduced cognitive load - Less tracking of mutation through code paths
- Consistent patterns - Same helpers used throughout codebase
Testability
- Pure functions easier to test - No mocks or setup needed
- Deterministic results - Same input always gives same output
- No hidden dependencies - All inputs explicit, no global state
- Better isolation - Test one function without worrying about side effects
Maintainability
- Reusable patterns - Generic helpers work for any type
- Less code duplication - 15+ repeated patterns now use 3 shared helpers
- Easier to reason about - Immutability guarantees simplify mental model
- Type-safe with generics - Compile-time type checking prevents errors
Principles Applied
- Immutability First - Variables are immutable unless mutation is necessary
- Declarative Over Imperative - Express "what" not "how"
- Pure Functions - Separate calculations from side effects
- Pragmatic Balance - Changes improve clarity without dogmatic adherence
Testing
- ✅ All tests pass -
go test ./pkg/workflow/... ./pkg/sliceutil/...(27.7s) - ✅ No behavioral changes - Functionality is identical
- ✅ Test updated -
TestSortPinsByVersionupdated for immutable sort - ✅ Type safety - Generic helpers provide compile-time type checking
- ✅ No formatting issues -
gofmtclean
Performance Considerations
The changes have minimal performance impact:
- Filter operations - Same O(n) complexity, slightly more readable
- Map operations - Same O(n) complexity, pre-allocated slices
- Sort operations - One extra O(n) copy operation, but immutability benefit outweighs cost
- MapToSlice - Same O(n) complexity as manual loops
All performance-critical paths maintain their algorithmic complexity while gaining immutability benefits.
Examples
Example 1: Immutable Sort
// Before: Mutation hidden in function call
pins := getPins()
sortPinsByVersion(pins) // ⚠️ pins is modified!
usePins(pins)
// After: Explicit immutable operation
pins := getPins()
sortedPins := sortPinsByVersion(pins) // ✅ pins unchanged
usePins(sortedPins)Example 2: Functional Filter
// Before: Imperative filtering
var active []Item
for _, item := range items {
if item.Active {
active = append(active, item)
}
}
// After: Declarative filtering
active := sliceutil.Filter(items, func(item Item) bool {
return item.Active
})Example 3: Map Keys Extraction
// Before: Manual loop
keys := make([]string, 0, len(myMap))
for key := range myMap {
keys = append(keys, key)
}
// After: Functional helper
keys := sliceutil.MapToSlice(myMap)Review Focus
Please verify:
- ✅ Immutability changes are appropriate and beneficial
- ✅ Functional helpers are correctly implemented and used
- ✅ No unintended side effects or behavior changes
- ✅ Test coverage is maintained
- ✅ Code clarity has improved
🤖 Generated by Functional/Immutability Enhancer - applying moderate functional programming techniques
AI generated by Functional Enhancer
- expires on Feb 7, 2026, 1:20 PM UTC
Note: This was originally intended as a pull request, but PR creation failed. The changes have been pushed to the branch main-463d2c68a6a3c6b5.
Original error: Unexpected end of JSON input
You can manually create a pull request from the branch if needed.
Show patch preview (431 of 431 lines)
From c2ba6ddcbd24e2a4ab52fa3005cecd93caaf0a75 Mon Sep 17 00:00:00 2001
From: gh-aw <gh-aw@anthropic.com>
Date: Sat, 31 Jan 2026 13:18:48 +0000
Subject: [PATCH] Apply functional programming and immutability improvements
- Add Filter, Map, and MapToSlice generic helpers to sliceutil
- Convert sortPinsByVersion to immutable operation (returns new slice)
- Replace 4 imperative filter loops with functional sliceutil.Filter
- Replace 11 map-to-slice patterns with sliceutil.MapToSlice
- Update test to use immutable sort return value
Benefits:
- Reduced mutation surface area (15+ instances)
- Improved code clarity through declarative patterns
- Better testability with pure functions
- Type-safe generic helpers with compile-time checking
- All tests pass, no behavioral changes
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
---
pkg/sliceutil/sliceutil.go | 33 ++++++++++++
pkg/workflow/action_pins.go | 82 ++++++++++++++---------------
pkg/workflow/action_pins_test.go | 7 +--
pkg/workflow/env.go | 8 ++-
pkg/workflow/mcp_config_custom.go | 19 +++----
pkg/workflow/mcp_setup_generator.go | 31 ++++-------
pkg/workflow/sandbox.go | 8 ++-
7 files changed, 97 insertions(+), 91 deletions(-)
diff --git a/pkg/sliceutil/sliceutil.go b/pkg/sliceutil/sliceutil.go
index 0e5ee21..3c41e18 100644
--- a/pkg/sliceutil/sliceutil.go
+++ b/pkg/sliceutil/sliceutil.go
@@ -27,3 +27,36 @@ func ContainsAny(s string, substrings ...string) bool {
func ContainsIgnoreCase(s, substr string) bool {
return strings.Contains(strings.ToLower(s), strings.ToLower(substr))
}
+
+// Filter returns a new slice containing only elements that match the predicate.
+// This is a pure function that does not modify the input slice.
+func Filter[T any](slice []T, predicate func(T) bool) []T {
+ result := make([]T, 0, len(slice))
+ for _, item := range slice {
+ if predicate(item) {
+ result = append(result, item)
+ }
+ }
+ return result
+}
+
+//
... (truncated)