Skip to content

Conversation

@github-actions
Copy link
Contributor

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 immutable
  • pkg/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 conversion
  • pkg/workflow/mcp_setup_generator.go - 5 conversions
  • pkg/workflow/mcp_config_custom.go - 3 conversions
  • pkg/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

  1. Immutability First - Variables are immutable unless mutation is necessary
  2. Declarative Over Imperative - Express "what" not "how"
  3. Pure Functions - Separate calculations from side effects
  4. 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 - TestSortPinsByVersion updated for immutable sort
  • Type safety - Generic helpers provide compile-time type checking
  • No formatting issues - gofmt clean

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

- 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>
@dsyme dsyme marked this pull request as ready for review January 31, 2026 13:54
@dsyme dsyme merged commit 201f632 into main Jan 31, 2026
6 checks passed
@dsyme dsyme deleted the main-463d2c68a6a3c6b5 branch January 31, 2026 15:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant