1
0
mirror of https://github.com/IBM/fp-go.git synced 2026-03-14 13:42:48 +02:00

Compare commits

...

1 Commits

Author SHA1 Message Date
Dr. Carsten Leue
eb27ecdc01 fix: clarify behaviour of array.Concat
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-03-13 18:39:55 +01:00
2 changed files with 145 additions and 27 deletions

View File

@@ -529,71 +529,116 @@ func Push[A any](a A) Operator[A, A] {
return G.Push[Operator[A, A]](a)
}
// Concat concatenates two arrays, appending the provided array to the end of the input array.
// This is a curried function that takes an array to append and returns a function that
// takes the base array and returns the concatenated result.
// Concat concatenates two arrays by appending a suffix array to a base array.
//
// The function creates a new array containing all elements from the base array followed
// by all elements from the appended array. Neither input array is modified.
// This is a curried function that takes a suffix array and returns a function
// that takes a base array and produces a new array with the suffix appended.
// It follows the "data last" pattern, where the data to be operated on (base array)
// is provided last, making it ideal for use in functional pipelines.
//
// Semantic: Concat(suffix)(base) produces [base... suffix...]
//
// The function creates a new array containing all elements from the base array
// followed by all elements from the suffix array. Neither input array is modified.
//
// Type Parameters:
//
// - A: The type of elements in the arrays
//
// Parameters:
// - as: The array to append to the end of the base array
//
// - suffix: The array to append to the end of the base array
//
// Returns:
// - A function that takes a base array and returns a new array with `as` appended to its end
//
// - A function that takes a base array and returns [base... suffix...]
//
// Behavior:
// - Creates a new array with length equal to the sum of both input arrays
// - Copies all elements from the base array first
// - Appends all elements from the `as` array at the end
// - Returns the base array unchanged if `as` is empty
// - Returns `as` unchanged if the base array is empty
// - Does not modify either input array
//
// Example:
// - Creates a new array with length equal to len(base) + len(suffix)
// - Copies all elements from the base array first
// - Appends all elements from the suffix array at the end
// - Returns the base array unchanged if suffix is empty
// - Returns suffix unchanged if the base array is empty
// - Does not modify either input array
// - Preserves element order within each array
//
// Example - Basic concatenation:
//
// base := []int{1, 2, 3}
// toAppend := []int{4, 5, 6}
// result := array.Concat(toAppend)(base)
// suffix := []int{4, 5, 6}
// concat := array.Concat(suffix)
// result := concat(base)
// // result: []int{1, 2, 3, 4, 5, 6}
// // base: []int{1, 2, 3} (unchanged)
// // toAppend: []int{4, 5, 6} (unchanged)
// // suffix: []int{4, 5, 6} (unchanged)
//
// Example with empty arrays:
// Example - Direct application:
//
// result := array.Concat([]int{4, 5, 6})([]int{1, 2, 3})
// // result: []int{1, 2, 3, 4, 5, 6}
// // Demonstrates: Concat(b)(a) = [a... b...]
//
// Example - Empty arrays:
//
// base := []int{1, 2, 3}
// empty := []int{}
// result := array.Concat(empty)(base)
// // result: []int{1, 2, 3}
//
// Example with strings:
// Example - Strings:
//
// words1 := []string{"hello", "world"}
// words2 := []string{"foo", "bar"}
// result := array.Concat(words2)(words1)
// // result: []string{"hello", "world", "foo", "bar"}
//
// Example with functional composition:
// Example - Functional composition:
//
// numbers := []int{1, 2, 3}
// result := F.Pipe2(
// numbers,
// array.Map(N.Mul(2)),
// array.Concat([]int{10, 20}),
// array.Map(N.Mul(2)), // [2, 4, 6]
// array.Concat([]int{10, 20}), // [2, 4, 6, 10, 20]
// )
// // result: []int{2, 4, 6, 10, 20}
//
// Example - Multiple concatenations:
//
// result := F.Pipe2(
// []int{1},
// array.Concat([]int{2, 3}), // [1, 2, 3]
// array.Concat([]int{4, 5}), // [1, 2, 3, 4, 5]
// )
//
// Example - Building arrays incrementally:
//
// header := []string{"Name", "Age"}
// data := []string{"Alice", "30"}
// footer := []string{"Total: 1"}
// result := F.Pipe2(
// header,
// array.Concat(data),
// array.Concat(footer),
// )
// // result: []string{"Name", "Age", "Alice", "30", "Total: 1"}
//
// Use cases:
//
// - Combining multiple arrays into one
// - Building arrays incrementally
// - Building arrays incrementally in pipelines
// - Implementing array-based data structures (queues, buffers)
// - Merging results from multiple operations
// - Creating array pipelines with functional composition
// - Creating array transformation pipelines
// - Appending batches of elements
//
// Mathematical properties:
//
// - Associativity: Concat(c)(Concat(b)(a)) == Concat(Concat(c)(b))(a)
// - Identity: Concat([])(a) == a and Concat(a)([]) == a
// - Length: len(Concat(b)(a)) == len(a) + len(b)
//
// Performance:
//
// - Time complexity: O(n + m) where n and m are the lengths of the arrays
// - Space complexity: O(n + m) for the new array
// - Optimized to avoid allocation when one array is empty
@@ -601,9 +646,15 @@ func Push[A any](a A) Operator[A, A] {
// Note: This function is immutable - it creates a new array rather than modifying
// the input arrays. For appending a single element, consider using Append or Push.
//
// See Also:
//
// - Append: For appending a single element
// - Push: Curried version of Append
// - Flatten: For flattening nested arrays
//
//go:inline
func Concat[A any](as []A) Operator[A, A] {
return F.Bind2nd(array.Concat[[]A, A], as)
func Concat[A any](suffix []A) Operator[A, A] {
return F.Bind2nd(array.Concat[[]A, A], suffix)
}
// MonadFlap applies a value to an array of functions, producing an array of results.

View File

@@ -767,6 +767,25 @@ func TestExtendUseCases(t *testing.T) {
// TestConcat tests the Concat function
func TestConcat(t *testing.T) {
t.Run("Semantic: Concat(b)(a) produces [a... b...]", func(t *testing.T) {
a := []int{1, 2, 3}
b := []int{4, 5, 6}
// Concat(b)(a) should produce [a... b...]
result := Concat(b)(a)
expected := []int{1, 2, 3, 4, 5, 6}
assert.Equal(t, expected, result, "Concat(b)(a) should produce [a... b...]")
// Verify order: a's elements come first, then b's elements
assert.Equal(t, a[0], result[0], "First element should be from a")
assert.Equal(t, a[1], result[1], "Second element should be from a")
assert.Equal(t, a[2], result[2], "Third element should be from a")
assert.Equal(t, b[0], result[3], "Fourth element should be from b")
assert.Equal(t, b[1], result[4], "Fifth element should be from b")
assert.Equal(t, b[2], result[5], "Sixth element should be from b")
})
t.Run("Concat two non-empty arrays", func(t *testing.T) {
base := []int{1, 2, 3}
toAppend := []int{4, 5, 6}
@@ -870,6 +889,54 @@ func TestConcat(t *testing.T) {
expected := []int{1, 2, 3}
assert.Equal(t, expected, result)
})
t.Run("Explicit append semantic demonstration", func(t *testing.T) {
// Given a base array
base := []string{"A", "B", "C"}
// And a suffix to append
suffix := []string{"D", "E", "F"}
// When we apply Concat(suffix) to base
appendSuffix := Concat(suffix)
result := appendSuffix(base)
// Then the result should be base followed by suffix
expected := []string{"A", "B", "C", "D", "E", "F"}
assert.Equal(t, expected, result)
// And the base should be unchanged
assert.Equal(t, []string{"A", "B", "C"}, base)
// And the suffix should be unchanged
assert.Equal(t, []string{"D", "E", "F"}, suffix)
})
t.Run("Append semantic with different types", func(t *testing.T) {
// Integers
intResult := Concat([]int{4, 5})([]int{1, 2, 3})
assert.Equal(t, []int{1, 2, 3, 4, 5}, intResult)
// Strings
strResult := Concat([]string{"world"})([]string{"hello"})
assert.Equal(t, []string{"hello", "world"}, strResult)
// Floats
floatResult := Concat([]float64{3.3, 4.4})([]float64{1.1, 2.2})
assert.Equal(t, []float64{1.1, 2.2, 3.3, 4.4}, floatResult)
})
t.Run("Append semantic in pipeline", func(t *testing.T) {
// Start with [1, 2, 3]
// Append [4, 5] to get [1, 2, 3, 4, 5]
// Append [6, 7] to get [1, 2, 3, 4, 5, 6, 7]
result := F.Pipe2(
[]int{1, 2, 3},
Concat([]int{4, 5}),
Concat([]int{6, 7}),
)
expected := []int{1, 2, 3, 4, 5, 6, 7}
assert.Equal(t, expected, result)
})
}
// TestConcatComposition tests Concat with other array operations