Skip to content

[fp-enhancer] Apply functional programming and immutability improvements #12922

@github-actions

Description

@github-actions

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

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)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions