mirror of
https://github.com/IBM/fp-go.git
synced 2026-03-20 13:58:04 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02acbae8f6 | ||
|
|
eb27ecdc01 |
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,6 +5,7 @@ package lenses
|
||||
// 2026-01-27 16:08:47.5483589 +0100 CET m=+0.003380301
|
||||
|
||||
import (
|
||||
"net"
|
||||
url "net/url"
|
||||
|
||||
__iso_option "github.com/IBM/fp-go/v2/optics/iso/option"
|
||||
@@ -119,6 +120,8 @@ type URLLenses struct {
|
||||
RawQuery __lens.Lens[url.URL, string]
|
||||
Fragment __lens.Lens[url.URL, string]
|
||||
RawFragment __lens.Lens[url.URL, string]
|
||||
Hostname __lens.Lens[url.URL, string]
|
||||
Port __lens.Lens[url.URL, string]
|
||||
// optional fields
|
||||
SchemeO __lens_option.LensO[url.URL, string]
|
||||
OpaqueO __lens_option.LensO[url.URL, string]
|
||||
@@ -131,6 +134,8 @@ type URLLenses struct {
|
||||
RawQueryO __lens_option.LensO[url.URL, string]
|
||||
FragmentO __lens_option.LensO[url.URL, string]
|
||||
RawFragmentO __lens_option.LensO[url.URL, string]
|
||||
HostnameO __lens_option.LensO[url.URL, string]
|
||||
PortO __lens_option.LensO[url.URL, string]
|
||||
}
|
||||
|
||||
// URLRefLenses provides lenses for accessing fields of url.URL via a reference to url.URL
|
||||
@@ -147,6 +152,8 @@ type URLRefLenses struct {
|
||||
RawQuery __lens.Lens[*url.URL, string]
|
||||
Fragment __lens.Lens[*url.URL, string]
|
||||
RawFragment __lens.Lens[*url.URL, string]
|
||||
Hostname __lens.Lens[*url.URL, string]
|
||||
Port __lens.Lens[*url.URL, string]
|
||||
// optional fields
|
||||
SchemeO __lens_option.LensO[*url.URL, string]
|
||||
OpaqueO __lens_option.LensO[*url.URL, string]
|
||||
@@ -159,6 +166,8 @@ type URLRefLenses struct {
|
||||
RawQueryO __lens_option.LensO[*url.URL, string]
|
||||
FragmentO __lens_option.LensO[*url.URL, string]
|
||||
RawFragmentO __lens_option.LensO[*url.URL, string]
|
||||
HostnameO __lens_option.LensO[*url.URL, string]
|
||||
PortO __lens_option.LensO[*url.URL, string]
|
||||
}
|
||||
|
||||
// MakeURLLenses creates a new URLLenses with lenses for all fields
|
||||
@@ -219,6 +228,38 @@ func MakeURLLenses() URLLenses {
|
||||
func(s url.URL, v string) url.URL { s.RawFragment = v; return s },
|
||||
"URL.RawFragment",
|
||||
)
|
||||
lensHostname := __lens.MakeLensWithName(
|
||||
func(s url.URL) string {
|
||||
host, _, err := net.SplitHostPort(s.Host)
|
||||
if err != nil {
|
||||
return s.Host
|
||||
}
|
||||
return host
|
||||
},
|
||||
func(s url.URL, v string) url.URL {
|
||||
_, port, err := net.SplitHostPort(s.Host)
|
||||
if err != nil {
|
||||
s.Host = v
|
||||
} else {
|
||||
s.Host = net.JoinHostPort(v, port)
|
||||
}
|
||||
return s
|
||||
},
|
||||
"URL.Hostname",
|
||||
)
|
||||
lensPort := __lens.MakeLensWithName(
|
||||
func(s url.URL) string { return s.Port() },
|
||||
func(s url.URL, v string) url.URL {
|
||||
host, _, err := net.SplitHostPort(s.Host)
|
||||
if err != nil {
|
||||
s.Host = net.JoinHostPort(s.Host, v)
|
||||
} else {
|
||||
s.Host = net.JoinHostPort(host, v)
|
||||
}
|
||||
return s
|
||||
},
|
||||
"URL.Port",
|
||||
)
|
||||
// optional lenses
|
||||
lensSchemeO := __lens_option.FromIso[url.URL](__iso_option.FromZero[string]())(lensScheme)
|
||||
lensOpaqueO := __lens_option.FromIso[url.URL](__iso_option.FromZero[string]())(lensOpaque)
|
||||
@@ -231,6 +272,8 @@ func MakeURLLenses() URLLenses {
|
||||
lensRawQueryO := __lens_option.FromIso[url.URL](__iso_option.FromZero[string]())(lensRawQuery)
|
||||
lensFragmentO := __lens_option.FromIso[url.URL](__iso_option.FromZero[string]())(lensFragment)
|
||||
lensRawFragmentO := __lens_option.FromIso[url.URL](__iso_option.FromZero[string]())(lensRawFragment)
|
||||
lensHostnameO := __lens_option.FromIso[url.URL](__iso_option.FromZero[string]())(lensHostname)
|
||||
lensPortO := __lens_option.FromIso[url.URL](__iso_option.FromZero[string]())(lensPort)
|
||||
return URLLenses{
|
||||
// mandatory lenses
|
||||
Scheme: lensScheme,
|
||||
@@ -244,6 +287,8 @@ func MakeURLLenses() URLLenses {
|
||||
RawQuery: lensRawQuery,
|
||||
Fragment: lensFragment,
|
||||
RawFragment: lensRawFragment,
|
||||
Hostname: lensHostname,
|
||||
Port: lensPort,
|
||||
// optional lenses
|
||||
SchemeO: lensSchemeO,
|
||||
OpaqueO: lensOpaqueO,
|
||||
@@ -256,6 +301,8 @@ func MakeURLLenses() URLLenses {
|
||||
RawQueryO: lensRawQueryO,
|
||||
FragmentO: lensFragmentO,
|
||||
RawFragmentO: lensRawFragmentO,
|
||||
HostnameO: lensHostnameO,
|
||||
PortO: lensPortO,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,6 +364,38 @@ func MakeURLRefLenses() URLRefLenses {
|
||||
func(s *url.URL, v string) *url.URL { s.RawFragment = v; return s },
|
||||
"(*url.URL).RawFragment",
|
||||
)
|
||||
lensHostname := __lens.MakeLensStrictWithName(
|
||||
func(s *url.URL) string {
|
||||
host, _, err := net.SplitHostPort(s.Host)
|
||||
if err != nil {
|
||||
return s.Host
|
||||
}
|
||||
return host
|
||||
},
|
||||
func(s *url.URL, v string) *url.URL {
|
||||
_, port, err := net.SplitHostPort(s.Host)
|
||||
if err != nil {
|
||||
s.Host = v
|
||||
} else {
|
||||
s.Host = net.JoinHostPort(v, port)
|
||||
}
|
||||
return s
|
||||
},
|
||||
"URL.Hostname",
|
||||
)
|
||||
lensPort := __lens.MakeLensStrictWithName(
|
||||
(*url.URL).Port,
|
||||
func(s *url.URL, v string) *url.URL {
|
||||
host, _, err := net.SplitHostPort(s.Host)
|
||||
if err != nil {
|
||||
s.Host = net.JoinHostPort(s.Host, v)
|
||||
} else {
|
||||
s.Host = net.JoinHostPort(host, v)
|
||||
}
|
||||
return s
|
||||
},
|
||||
"URL.Port",
|
||||
)
|
||||
// optional lenses
|
||||
lensSchemeO := __lens_option.FromIso[*url.URL](__iso_option.FromZero[string]())(lensScheme)
|
||||
lensOpaqueO := __lens_option.FromIso[*url.URL](__iso_option.FromZero[string]())(lensOpaque)
|
||||
@@ -329,6 +408,8 @@ func MakeURLRefLenses() URLRefLenses {
|
||||
lensRawQueryO := __lens_option.FromIso[*url.URL](__iso_option.FromZero[string]())(lensRawQuery)
|
||||
lensFragmentO := __lens_option.FromIso[*url.URL](__iso_option.FromZero[string]())(lensFragment)
|
||||
lensRawFragmentO := __lens_option.FromIso[*url.URL](__iso_option.FromZero[string]())(lensRawFragment)
|
||||
lensHostnameO := __lens_option.FromIso[*url.URL](__iso_option.FromZero[string]())(lensHostname)
|
||||
lensPortO := __lens_option.FromIso[*url.URL](__iso_option.FromZero[string]())(lensPort)
|
||||
return URLRefLenses{
|
||||
// mandatory lenses
|
||||
Scheme: lensScheme,
|
||||
@@ -342,6 +423,8 @@ func MakeURLRefLenses() URLRefLenses {
|
||||
RawQuery: lensRawQuery,
|
||||
Fragment: lensFragment,
|
||||
RawFragment: lensRawFragment,
|
||||
Hostname: lensHostname,
|
||||
Port: lensPort,
|
||||
// optional lenses
|
||||
SchemeO: lensSchemeO,
|
||||
OpaqueO: lensOpaqueO,
|
||||
@@ -354,6 +437,8 @@ func MakeURLRefLenses() URLRefLenses {
|
||||
RawQueryO: lensRawQueryO,
|
||||
FragmentO: lensFragmentO,
|
||||
RawFragmentO: lensRawFragmentO,
|
||||
HostnameO: lensHostnameO,
|
||||
PortO: lensPortO,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user