1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-11-23 22:14:53 +02:00

fix: some performance optimizations

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
Dr. Carsten Leue
2025-11-05 16:55:19 +01:00
parent 9919b75fe6
commit 8e7fc699a1
25 changed files with 1704 additions and 62 deletions

View File

@@ -19,12 +19,28 @@ import (
G "github.com/IBM/fp-go/v2/array/generic"
)
// AnyWithIndex tests if any of the elements in the array matches the predicate
// AnyWithIndex tests if any of the elements in the array matches the predicate.
// The predicate receives both the index and the element.
// Returns true if at least one element satisfies the predicate, false otherwise.
//
// Example:
//
// hasEvenAtEvenIndex := array.AnyWithIndex(func(i, x int) bool {
// return i%2 == 0 && x%2 == 0
// })
// result := hasEvenAtEvenIndex([]int{1, 3, 4, 5}) // true (4 is at index 2)
func AnyWithIndex[A any](pred func(int, A) bool) func([]A) bool {
return G.AnyWithIndex[[]A](pred)
}
// Any tests if any of the elements in the array matches the predicate
// Any tests if any of the elements in the array matches the predicate.
// Returns true if at least one element satisfies the predicate, false otherwise.
// Returns false for an empty array.
//
// Example:
//
// hasEven := array.Any(func(x int) bool { return x%2 == 0 })
// result := hasEven([]int{1, 3, 4, 5}) // true
func Any[A any](pred func(A) bool) func([]A) bool {
return G.Any[[]A](pred)
}

View File

@@ -40,10 +40,14 @@ func Replicate[A any](n int, a A) []A {
return G.Replicate[[]A](n, a)
}
// MonadMap applies a function to each element of an array, returning a new array with the results.
// This is the monadic version of Map that takes the array as the first parameter.
func MonadMap[A, B any](as []A, f func(a A) B) []B {
return G.MonadMap[[]A, []B](as, f)
}
// MonadMapRef applies a function to a pointer to each element of an array, returning a new array with the results.
// This is useful when you need to access elements by reference without copying.
func MonadMapRef[A, B any](as []A, f func(a *A) B) []B {
count := len(as)
bs := make([]B, count)
@@ -53,14 +57,24 @@ func MonadMapRef[A, B any](as []A, f func(a *A) B) []B {
return bs
}
// MapWithIndex applies a function to each element and its index in an array, returning a new array with the results.
func MapWithIndex[A, B any](f func(int, A) B) func([]A) []B {
return G.MapWithIndex[[]A, []B](f)
}
// Map applies a function to each element of an array, returning a new array with the results.
// This is the curried version that returns a function.
//
// Example:
//
// double := array.Map(func(x int) int { return x * 2 })
// result := double([]int{1, 2, 3}) // [2, 4, 6]
func Map[A, B any](f func(a A) B) func([]A) []B {
return G.Map[[]A, []B, A, B](f)
}
// MapRef applies a function to a pointer to each element of an array, returning a new array with the results.
// This is the curried version that returns a function.
func MapRef[A, B any](f func(a *A) B) func([]A) []B {
return F.Bind2nd(MonadMapRef[A, B], f)
}
@@ -99,14 +113,19 @@ func FilterWithIndex[A any](pred func(int, A) bool) EM.Endomorphism[[]A] {
return G.FilterWithIndex[[]A](pred)
}
// FilterRef returns a new array with all elements from the original array that match a predicate operating on pointers.
func FilterRef[A any](pred func(*A) bool) EM.Endomorphism[[]A] {
return F.Bind2nd(filterRef[A], pred)
}
// MonadFilterMap maps an array with a function that returns an Option and keeps only the Some values.
// This is the monadic version that takes the array as the first parameter.
func MonadFilterMap[A, B any](fa []A, f func(A) O.Option[B]) []B {
return G.MonadFilterMap[[]A, []B](fa, f)
}
// MonadFilterMapWithIndex maps an array with a function that takes an index and returns an Option,
// keeping only the Some values. This is the monadic version that takes the array as the first parameter.
func MonadFilterMapWithIndex[A, B any](fa []A, f func(int, A) O.Option[B]) []B {
return G.MonadFilterMapWithIndex[[]A, []B](fa, f)
}
@@ -126,6 +145,7 @@ func FilterChain[A, B any](f func(A) O.Option[[]B]) func([]A) []B {
return G.FilterChain[[]A](f)
}
// FilterMapRef filters an array using a predicate on pointers and maps the matching elements using a function on pointers.
func FilterMapRef[A, B any](pred func(a *A) bool, f func(a *A) B) func([]A) []B {
return func(fa []A) []B {
return filterMapRef(fa, pred, f)
@@ -141,44 +161,62 @@ func reduceRef[A, B any](fa []A, f func(B, *A) B, initial B) B {
return current
}
// Reduce folds an array from left to right, applying a function to accumulate a result.
//
// Example:
//
// sum := array.Reduce(func(acc, x int) int { return acc + x }, 0)
// result := sum([]int{1, 2, 3, 4, 5}) // 15
func Reduce[A, B any](f func(B, A) B, initial B) func([]A) B {
return G.Reduce[[]A](f, initial)
}
// ReduceWithIndex folds an array from left to right with access to the index,
// applying a function to accumulate a result.
func ReduceWithIndex[A, B any](f func(int, B, A) B, initial B) func([]A) B {
return G.ReduceWithIndex[[]A](f, initial)
}
// ReduceRight folds an array from right to left, applying a function to accumulate a result.
func ReduceRight[A, B any](f func(A, B) B, initial B) func([]A) B {
return G.ReduceRight[[]A](f, initial)
}
// ReduceRightWithIndex folds an array from right to left with access to the index,
// applying a function to accumulate a result.
func ReduceRightWithIndex[A, B any](f func(int, A, B) B, initial B) func([]A) B {
return G.ReduceRightWithIndex[[]A](f, initial)
}
// ReduceRef folds an array from left to right using pointers to elements,
// applying a function to accumulate a result.
func ReduceRef[A, B any](f func(B, *A) B, initial B) func([]A) B {
return func(as []A) B {
return reduceRef(as, f, initial)
}
}
// Append adds an element to the end of an array, returning a new array.
func Append[A any](as []A, a A) []A {
return G.Append(as, a)
}
// IsEmpty checks if an array has no elements.
func IsEmpty[A any](as []A) bool {
return G.IsEmpty(as)
}
// IsNonEmpty checks if an array has at least one element.
func IsNonEmpty[A any](as []A) bool {
return len(as) > 0
}
// Empty returns an empty array of type A.
func Empty[A any]() []A {
return G.Empty[[]A]()
}
// Zero returns an empty array of type A (alias for Empty).
func Zero[A any]() []A {
return Empty[A]()
}
@@ -188,46 +226,70 @@ func Of[A any](a A) []A {
return G.Of[[]A](a)
}
// MonadChain applies a function that returns an array to each element and flattens the results.
// This is the monadic version that takes the array as the first parameter (also known as FlatMap).
func MonadChain[A, B any](fa []A, f func(a A) []B) []B {
return G.MonadChain[[]A, []B](fa, f)
}
// Chain applies a function that returns an array to each element and flattens the results.
// This is the curried version (also known as FlatMap).
//
// Example:
//
// duplicate := array.Chain(func(x int) []int { return []int{x, x} })
// result := duplicate([]int{1, 2, 3}) // [1, 1, 2, 2, 3, 3]
func Chain[A, B any](f func(A) []B) func([]A) []B {
return G.Chain[[]A, []B](f)
}
// MonadAp applies an array of functions to an array of values, producing all combinations.
// This is the monadic version that takes both arrays as parameters.
func MonadAp[B, A any](fab []func(A) B, fa []A) []B {
return G.MonadAp[[]B](fab, fa)
}
// Ap applies an array of functions to an array of values, producing all combinations.
// This is the curried version.
func Ap[B, A any](fa []A) func([]func(A) B) []B {
return G.Ap[[]B, []func(A) B](fa)
}
// Match performs pattern matching on an array, calling onEmpty if empty or onNonEmpty if not.
func Match[A, B any](onEmpty func() B, onNonEmpty func([]A) B) func([]A) B {
return G.Match[[]A](onEmpty, onNonEmpty)
}
// MatchLeft performs pattern matching on an array, calling onEmpty if empty or onNonEmpty with head and tail if not.
func MatchLeft[A, B any](onEmpty func() B, onNonEmpty func(A, []A) B) func([]A) B {
return G.MatchLeft[[]A](onEmpty, onNonEmpty)
}
// Tail returns all elements except the first, wrapped in an Option.
// Returns None if the array is empty.
func Tail[A any](as []A) O.Option[[]A] {
return G.Tail(as)
}
// Head returns the first element of an array, wrapped in an Option.
// Returns None if the array is empty.
func Head[A any](as []A) O.Option[A] {
return G.Head(as)
}
// First returns the first element of an array, wrapped in an Option (alias for Head).
// Returns None if the array is empty.
func First[A any](as []A) O.Option[A] {
return G.First(as)
}
// Last returns the last element of an array, wrapped in an Option.
// Returns None if the array is empty.
func Last[A any](as []A) O.Option[A] {
return G.Last(as)
}
// PrependAll inserts a separator before each element of an array.
func PrependAll[A any](middle A) EM.Endomorphism[[]A] {
return func(as []A) []A {
count := len(as)
@@ -243,6 +305,11 @@ func PrependAll[A any](middle A) EM.Endomorphism[[]A] {
}
}
// Intersperse inserts a separator between each element of an array.
//
// Example:
//
// result := array.Intersperse(0)([]int{1, 2, 3}) // [1, 0, 2, 0, 3]
func Intersperse[A any](middle A) EM.Endomorphism[[]A] {
prepend := PrependAll(middle)
return func(as []A) []A {
@@ -253,33 +320,47 @@ func Intersperse[A any](middle A) EM.Endomorphism[[]A] {
}
}
// Intercalate inserts a separator between elements and concatenates them using a Monoid.
func Intercalate[A any](m M.Monoid[A]) func(A) func([]A) A {
concatAll := ConcatAll[A](m)
return func(middle A) func([]A) A {
return Match(m.Empty, F.Flow2(Intersperse(middle), concatAll))
return Match(m.Empty, F.Flow2(Intersperse(middle), ConcatAll[A](m)))
}
}
// Flatten converts a nested array into a flat array by concatenating all inner arrays.
//
// Example:
//
// result := array.Flatten([][]int{{1, 2}, {3, 4}, {5}}) // [1, 2, 3, 4, 5]
func Flatten[A any](mma [][]A) []A {
return G.Flatten(mma)
}
// Slice extracts a subarray from index low (inclusive) to high (exclusive).
func Slice[A any](low, high int) func(as []A) []A {
return array.Slice[[]A](low, high)
}
// Lookup returns the element at the specified index, wrapped in an Option.
// Returns None if the index is out of bounds.
func Lookup[A any](idx int) func([]A) O.Option[A] {
return G.Lookup[[]A](idx)
}
// UpsertAt returns a function that inserts or updates an element at a specific index.
// If the index is out of bounds, the element is appended.
func UpsertAt[A any](a A) EM.Endomorphism[[]A] {
return G.UpsertAt[[]A](a)
}
// Size returns the number of elements in an array.
func Size[A any](as []A) int {
return G.Size(as)
}
// MonadPartition splits an array into two arrays based on a predicate.
// The first array contains elements for which the predicate returns false,
// the second contains elements for which it returns true.
func MonadPartition[A any](as []A, pred func(A) bool) tuple.Tuple2[[]A, []A] {
return G.MonadPartition(as, pred)
}
@@ -305,6 +386,7 @@ func ConstNil[A any]() []A {
return array.ConstNil[[]A]()
}
// SliceRight extracts a subarray from the specified start index to the end.
func SliceRight[A any](start int) EM.Endomorphism[[]A] {
return G.SliceRight[[]A](start)
}
@@ -334,18 +416,24 @@ func Fold[A any](m M.Monoid[A]) func([]A) A {
return G.Fold[[]A](m)
}
// Push adds an element to the end of an array (alias for Append).
func Push[A any](a A) EM.Endomorphism[[]A] {
return G.Push[EM.Endomorphism[[]A]](a)
}
// MonadFlap applies a value to an array of functions, producing an array of results.
// This is the monadic version that takes both parameters.
func MonadFlap[B, A any](fab []func(A) B, a A) []B {
return G.MonadFlap[func(A) B, []func(A) B, []B, A, B](fab, a)
}
// Flap applies a value to an array of functions, producing an array of results.
// This is the curried version.
func Flap[B, A any](a A) func([]func(A) B) []B {
return G.Flap[func(A) B, []func(A) B, []B, A, B](a)
}
// Prepend adds an element to the beginning of an array, returning a new array.
func Prepend[A any](head A) EM.Endomorphism[[]A] {
return G.Prepend[EM.Endomorphism[[]A]](head)
}

View File

@@ -0,0 +1,325 @@
// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package array
import (
"fmt"
"testing"
N "github.com/IBM/fp-go/v2/number"
O "github.com/IBM/fp-go/v2/option"
S "github.com/IBM/fp-go/v2/string"
"github.com/stretchr/testify/assert"
)
func TestReplicate(t *testing.T) {
result := Replicate(3, "a")
assert.Equal(t, []string{"a", "a", "a"}, result)
empty := Replicate(0, 42)
assert.Equal(t, []int{}, empty)
}
func TestMonadMap(t *testing.T) {
src := []int{1, 2, 3}
result := MonadMap(src, func(x int) int { return x * 2 })
assert.Equal(t, []int{2, 4, 6}, result)
}
func TestMonadMapRef(t *testing.T) {
src := []int{1, 2, 3}
result := MonadMapRef(src, func(x *int) int { return *x * 2 })
assert.Equal(t, []int{2, 4, 6}, result)
}
func TestMapWithIndex(t *testing.T) {
src := []string{"a", "b", "c"}
mapper := MapWithIndex(func(i int, s string) string {
return fmt.Sprintf("%d:%s", i, s)
})
result := mapper(src)
assert.Equal(t, []string{"0:a", "1:b", "2:c"}, result)
}
func TestMapRef(t *testing.T) {
src := []int{1, 2, 3}
mapper := MapRef(func(x *int) int { return *x * 2 })
result := mapper(src)
assert.Equal(t, []int{2, 4, 6}, result)
}
func TestFilterWithIndex(t *testing.T) {
src := []int{1, 2, 3, 4, 5}
filter := FilterWithIndex(func(i, x int) bool {
return i%2 == 0 && x > 2
})
result := filter(src)
assert.Equal(t, []int{3, 5}, result)
}
func TestFilterRef(t *testing.T) {
src := []int{1, 2, 3, 4, 5}
filter := FilterRef(func(x *int) bool { return *x > 2 })
result := filter(src)
assert.Equal(t, []int{3, 4, 5}, result)
}
func TestMonadFilterMap(t *testing.T) {
src := []int{1, 2, 3, 4}
result := MonadFilterMap(src, func(x int) O.Option[string] {
if x%2 == 0 {
return O.Some(fmt.Sprintf("even:%d", x))
}
return O.None[string]()
})
assert.Equal(t, []string{"even:2", "even:4"}, result)
}
func TestMonadFilterMapWithIndex(t *testing.T) {
src := []int{1, 2, 3, 4}
result := MonadFilterMapWithIndex(src, func(i, x int) O.Option[string] {
if i%2 == 0 {
return O.Some(fmt.Sprintf("%d:%d", i, x))
}
return O.None[string]()
})
assert.Equal(t, []string{"0:1", "2:3"}, result)
}
func TestFilterMapWithIndex(t *testing.T) {
src := []int{1, 2, 3, 4}
filter := FilterMapWithIndex(func(i, x int) O.Option[string] {
if i%2 == 0 {
return O.Some(fmt.Sprintf("%d:%d", i, x))
}
return O.None[string]()
})
result := filter(src)
assert.Equal(t, []string{"0:1", "2:3"}, result)
}
func TestFilterMapRef(t *testing.T) {
src := []int{1, 2, 3, 4, 5}
filter := FilterMapRef(
func(x *int) bool { return *x > 2 },
func(x *int) string { return fmt.Sprintf("val:%d", *x) },
)
result := filter(src)
assert.Equal(t, []string{"val:3", "val:4", "val:5"}, result)
}
func TestReduceWithIndex(t *testing.T) {
src := []int{1, 2, 3}
reducer := ReduceWithIndex(func(i, acc, x int) int {
return acc + i + x
}, 0)
result := reducer(src)
assert.Equal(t, 9, result) // 0 + (0+1) + (1+2) + (2+3) = 9
}
func TestReduceRightWithIndex(t *testing.T) {
src := []string{"a", "b", "c"}
reducer := ReduceRightWithIndex(func(i int, x, acc string) string {
return fmt.Sprintf("%s%d:%s", acc, i, x)
}, "")
result := reducer(src)
assert.Equal(t, "2:c1:b0:a", result)
}
func TestReduceRef(t *testing.T) {
src := []int{1, 2, 3}
reducer := ReduceRef(func(acc int, x *int) int {
return acc + *x
}, 0)
result := reducer(src)
assert.Equal(t, 6, result)
}
func TestZero(t *testing.T) {
result := Zero[int]()
assert.Equal(t, []int{}, result)
assert.True(t, IsEmpty(result))
}
func TestMonadChain(t *testing.T) {
src := []int{1, 2, 3}
result := MonadChain(src, func(x int) []int {
return []int{x, x * 10}
})
assert.Equal(t, []int{1, 10, 2, 20, 3, 30}, result)
}
func TestChain(t *testing.T) {
src := []int{1, 2, 3}
chain := Chain(func(x int) []int {
return []int{x, x * 10}
})
result := chain(src)
assert.Equal(t, []int{1, 10, 2, 20, 3, 30}, result)
}
func TestMonadAp(t *testing.T) {
fns := []func(int) int{
func(x int) int { return x * 2 },
func(x int) int { return x + 10 },
}
values := []int{1, 2}
result := MonadAp(fns, values)
assert.Equal(t, []int{2, 4, 11, 12}, result)
}
func TestMatchLeft(t *testing.T) {
matcher := MatchLeft(
func() string { return "empty" },
func(head int, tail []int) string {
return fmt.Sprintf("head:%d,tail:%v", head, tail)
},
)
assert.Equal(t, "empty", matcher([]int{}))
assert.Equal(t, "head:1,tail:[2 3]", matcher([]int{1, 2, 3}))
}
func TestTail(t *testing.T) {
assert.Equal(t, O.None[[]int](), Tail([]int{}))
assert.Equal(t, O.Some([]int{2, 3}), Tail([]int{1, 2, 3}))
assert.Equal(t, O.Some([]int{}), Tail([]int{1}))
}
func TestFirst(t *testing.T) {
assert.Equal(t, O.None[int](), First([]int{}))
assert.Equal(t, O.Some(1), First([]int{1, 2, 3}))
}
func TestLast(t *testing.T) {
assert.Equal(t, O.None[int](), Last([]int{}))
assert.Equal(t, O.Some(3), Last([]int{1, 2, 3}))
assert.Equal(t, O.Some(1), Last([]int{1}))
}
func TestUpsertAt(t *testing.T) {
src := []int{1, 2, 3}
upsert := UpsertAt(99)
result1 := upsert(src)
assert.Equal(t, []int{1, 2, 3, 99}, result1)
}
func TestSize(t *testing.T) {
assert.Equal(t, 0, Size([]int{}))
assert.Equal(t, 3, Size([]int{1, 2, 3}))
}
func TestMonadPartition(t *testing.T) {
src := []int{1, 2, 3, 4, 5}
result := MonadPartition(src, func(x int) bool { return x > 2 })
assert.Equal(t, []int{1, 2}, result.F1)
assert.Equal(t, []int{3, 4, 5}, result.F2)
}
func TestIsNil(t *testing.T) {
var nilSlice []int
assert.True(t, IsNil(nilSlice))
assert.False(t, IsNil([]int{}))
assert.False(t, IsNil([]int{1}))
}
func TestIsNonNil(t *testing.T) {
var nilSlice []int
assert.False(t, IsNonNil(nilSlice))
assert.True(t, IsNonNil([]int{}))
assert.True(t, IsNonNil([]int{1}))
}
func TestConstNil(t *testing.T) {
result := ConstNil[int]()
assert.True(t, IsNil(result))
}
func TestSliceRight(t *testing.T) {
src := []int{1, 2, 3, 4, 5}
slicer := SliceRight[int](2)
result := slicer(src)
assert.Equal(t, []int{3, 4, 5}, result)
}
func TestCopy(t *testing.T) {
src := []int{1, 2, 3}
copied := Copy(src)
assert.Equal(t, src, copied)
// Verify it's a different slice
copied[0] = 99
assert.Equal(t, 1, src[0])
assert.Equal(t, 99, copied[0])
}
func TestClone(t *testing.T) {
src := []int{1, 2, 3}
cloner := Clone(func(x int) int { return x * 2 })
result := cloner(src)
assert.Equal(t, []int{2, 4, 6}, result)
}
func TestFoldMapWithIndex(t *testing.T) {
src := []string{"a", "b", "c"}
folder := FoldMapWithIndex[string](S.Monoid)(func(i int, s string) string {
return fmt.Sprintf("%d:%s", i, s)
})
result := folder(src)
assert.Equal(t, "0:a1:b2:c", result)
}
func TestFold(t *testing.T) {
src := []int{1, 2, 3, 4, 5}
folder := Fold(N.MonoidSum[int]())
result := folder(src)
assert.Equal(t, 15, result)
}
func TestPush(t *testing.T) {
src := []int{1, 2, 3}
pusher := Push(4)
result := pusher(src)
assert.Equal(t, []int{1, 2, 3, 4}, result)
}
func TestMonadFlap(t *testing.T) {
fns := []func(int) string{
func(x int) string { return fmt.Sprintf("a%d", x) },
func(x int) string { return fmt.Sprintf("b%d", x) },
}
result := MonadFlap(fns, 5)
assert.Equal(t, []string{"a5", "b5"}, result)
}
func TestFlap(t *testing.T) {
fns := []func(int) string{
func(x int) string { return fmt.Sprintf("a%d", x) },
func(x int) string { return fmt.Sprintf("b%d", x) },
}
flapper := Flap[string](5)
result := flapper(fns)
assert.Equal(t, []string{"a5", "b5"}, result)
}
func TestPrepend(t *testing.T) {
src := []int{2, 3, 4}
prepender := Prepend(1)
result := prepender(src)
assert.Equal(t, []int{1, 2, 3, 4}, result)
}
// Made with Bob

View File

@@ -113,6 +113,17 @@ func TestIntercalate(t *testing.T) {
assert.Equal(t, "a-b-c-d", is([]string{"a", "b", "c", "d"}))
}
func TestIntersperse(t *testing.T) {
// Test with empty array
assert.Equal(t, []int{}, Intersperse(0)([]int{}))
// Test with single element
assert.Equal(t, []int{1}, Intersperse(0)([]int{1}))
// Test with multiple elements
assert.Equal(t, []int{1, 0, 2, 0, 3}, Intersperse(0)([]int{1, 2, 3}))
}
func TestPrependAll(t *testing.T) {
empty := Empty[int]()
prep := PrependAll(0)

View File

@@ -19,14 +19,37 @@ import (
G "github.com/IBM/fp-go/v2/array/generic"
)
// Bind creates an empty context of type [S] to be used with the [Bind] operation
// Do creates an empty context of type S to be used with the Bind operation.
// This is the starting point for monadic do-notation style computations.
//
// Example:
//
// type State struct {
// X int
// Y int
// }
// result := array.Do(State{})
func Do[S any](
empty S,
) []S {
return G.Do[[]S, S](empty)
}
// Bind attaches the result of a computation to a context [S1] to produce a context [S2]
// Bind attaches the result of a computation to a context S1 to produce a context S2.
// The setter function defines how to update the context with the computation result.
// This enables monadic composition where each step can produce multiple results.
//
// Example:
//
// result := F.Pipe2(
// array.Do(struct{ X, Y int }{}),
// array.Bind(
// func(x int) func(s struct{}) struct{ X int } {
// return func(s struct{}) struct{ X int } { return struct{ X int }{x} }
// },
// func(s struct{}) []int { return []int{1, 2} },
// ),
// )
func Bind[S1, S2, T any](
setter func(T) func(S1) S2,
f func(S1) []T,
@@ -34,7 +57,19 @@ func Bind[S1, S2, T any](
return G.Bind[[]S1, []S2, []T, S1, S2, T](setter, f)
}
// Let attaches the result of a computation to a context [S1] to produce a context [S2]
// Let attaches the result of a pure computation to a context S1 to produce a context S2.
// Unlike Bind, the computation function returns a plain value T rather than []T.
//
// Example:
//
// result := array.Let(
// func(sum int) func(s struct{ X int }) struct{ X, Sum int } {
// return func(s struct{ X int }) struct{ X, Sum int } {
// return struct{ X, Sum int }{s.X, sum}
// }
// },
// func(s struct{ X int }) int { return s.X * 2 },
// )
func Let[S1, S2, T any](
setter func(T) func(S1) S2,
f func(S1) T,
@@ -42,7 +77,19 @@ func Let[S1, S2, T any](
return G.Let[[]S1, []S2, S1, S2, T](setter, f)
}
// LetTo attaches the a value to a context [S1] to produce a context [S2]
// LetTo attaches a constant value to a context S1 to produce a context S2.
// This is useful for adding constant values to the context.
//
// Example:
//
// result := array.LetTo(
// func(name string) func(s struct{ X int }) struct{ X int; Name string } {
// return func(s struct{ X int }) struct{ X int; Name string } {
// return struct{ X int; Name string }{s.X, name}
// }
// },
// "constant",
// )
func LetTo[S1, S2, T any](
setter func(T) func(S1) S2,
b T,
@@ -50,14 +97,37 @@ func LetTo[S1, S2, T any](
return G.LetTo[[]S1, []S2, S1, S2, T](setter, b)
}
// BindTo initializes a new state [S1] from a value [T]
// BindTo initializes a new state S1 from a value T.
// This is typically the first operation after Do to start building the context.
//
// Example:
//
// result := F.Pipe2(
// []int{1, 2, 3},
// array.BindTo(func(x int) struct{ X int } {
// return struct{ X int }{x}
// }),
// )
func BindTo[S1, T any](
setter func(T) S1,
) func([]T) []S1 {
return G.BindTo[[]S1, []T, S1, T](setter)
}
// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently
// ApS attaches a value to a context S1 to produce a context S2 by considering
// the context and the value concurrently (using applicative semantics).
// This produces all combinations of context values and array values.
//
// Example:
//
// result := array.ApS(
// func(y int) func(s struct{ X int }) struct{ X, Y int } {
// return func(s struct{ X int }) struct{ X, Y int } {
// return struct{ X, Y int }{s.X, y}
// }
// },
// []int{10, 20},
// )
func ApS[S1, S2, T any](
setter func(T) func(S1) S2,
fa []T,

View File

@@ -0,0 +1,80 @@
// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package array
import (
"testing"
F "github.com/IBM/fp-go/v2/function"
"github.com/stretchr/testify/assert"
)
type TestState1 struct {
X int
}
type TestState2 struct {
X int
Y int
}
func TestLet(t *testing.T) {
result := F.Pipe2(
Do(TestState1{}),
Let(
func(y int) func(s TestState1) TestState2 {
return func(s TestState1) TestState2 {
return TestState2{X: s.X, Y: y}
}
},
func(s TestState1) int { return s.X * 2 },
),
Map(func(s TestState2) int { return s.X + s.Y }),
)
assert.Equal(t, []int{0}, result)
}
func TestLetTo(t *testing.T) {
result := F.Pipe2(
Do(TestState1{X: 5}),
LetTo(
func(y int) func(s TestState1) TestState2 {
return func(s TestState1) TestState2 {
return TestState2{X: s.X, Y: y}
}
},
42,
),
Map(func(s TestState2) int { return s.X + s.Y }),
)
assert.Equal(t, []int{47}, result)
}
func TestBindTo(t *testing.T) {
result := F.Pipe1(
[]int{1, 2, 3},
BindTo(func(x int) TestState1 {
return TestState1{X: x}
}),
)
expected := []TestState1{{X: 1}, {X: 2}, {X: 3}}
assert.Equal(t, expected, result)
}
// Made with Bob

134
v2/array/coverage.out Normal file
View File

@@ -0,0 +1,134 @@
mode: set
github.com/IBM/fp-go/v2/array/any.go:32.65,34.2 1 1
github.com/IBM/fp-go/v2/array/any.go:44.51,46.2 1 1
github.com/IBM/fp-go/v2/array/array.go:29.33,31.2 1 1
github.com/IBM/fp-go/v2/array/array.go:34.52,36.2 1 1
github.com/IBM/fp-go/v2/array/array.go:39.39,41.2 1 1
github.com/IBM/fp-go/v2/array/array.go:45.52,47.2 1 1
github.com/IBM/fp-go/v2/array/array.go:51.56,54.34 3 1
github.com/IBM/fp-go/v2/array/array.go:54.34,56.3 1 1
github.com/IBM/fp-go/v2/array/array.go:57.2,57.11 1 1
github.com/IBM/fp-go/v2/array/array.go:61.61,63.2 1 1
github.com/IBM/fp-go/v2/array/array.go:72.49,74.2 1 1
github.com/IBM/fp-go/v2/array/array.go:78.53,80.2 1 1
github.com/IBM/fp-go/v2/array/array.go:82.57,85.29 3 1
github.com/IBM/fp-go/v2/array/array.go:85.29,87.15 2 1
github.com/IBM/fp-go/v2/array/array.go:87.15,89.4 1 1
github.com/IBM/fp-go/v2/array/array.go:91.2,91.15 1 1
github.com/IBM/fp-go/v2/array/array.go:94.79,97.29 3 1
github.com/IBM/fp-go/v2/array/array.go:97.29,99.15 2 1
github.com/IBM/fp-go/v2/array/array.go:99.15,101.4 1 1
github.com/IBM/fp-go/v2/array/array.go:103.2,103.15 1 1
github.com/IBM/fp-go/v2/array/array.go:107.60,109.2 1 1
github.com/IBM/fp-go/v2/array/array.go:112.74,114.2 1 1
github.com/IBM/fp-go/v2/array/array.go:117.64,119.2 1 1
github.com/IBM/fp-go/v2/array/array.go:123.66,125.2 1 1
github.com/IBM/fp-go/v2/array/array.go:129.80,131.2 1 1
github.com/IBM/fp-go/v2/array/array.go:134.63,136.2 1 1
github.com/IBM/fp-go/v2/array/array.go:139.77,141.2 1 1
github.com/IBM/fp-go/v2/array/array.go:144.67,146.2 1 1
github.com/IBM/fp-go/v2/array/array.go:149.81,150.26 1 1
github.com/IBM/fp-go/v2/array/array.go:150.26,152.3 1 1
github.com/IBM/fp-go/v2/array/array.go:155.64,158.29 3 1
github.com/IBM/fp-go/v2/array/array.go:158.29,160.3 1 1
github.com/IBM/fp-go/v2/array/array.go:161.2,161.16 1 1
github.com/IBM/fp-go/v2/array/array.go:170.62,172.2 1 1
github.com/IBM/fp-go/v2/array/array.go:176.76,178.2 1 1
github.com/IBM/fp-go/v2/array/array.go:181.67,183.2 1 1
github.com/IBM/fp-go/v2/array/array.go:187.81,189.2 1 1
github.com/IBM/fp-go/v2/array/array.go:193.66,194.24 1 1
github.com/IBM/fp-go/v2/array/array.go:194.24,196.3 1 1
github.com/IBM/fp-go/v2/array/array.go:200.37,202.2 1 1
github.com/IBM/fp-go/v2/array/array.go:205.34,207.2 1 1
github.com/IBM/fp-go/v2/array/array.go:210.37,212.2 1 1
github.com/IBM/fp-go/v2/array/array.go:215.25,217.2 1 1
github.com/IBM/fp-go/v2/array/array.go:220.24,222.2 1 1
github.com/IBM/fp-go/v2/array/array.go:225.25,227.2 1 1
github.com/IBM/fp-go/v2/array/array.go:231.56,233.2 1 1
github.com/IBM/fp-go/v2/array/array.go:242.51,244.2 1 1
github.com/IBM/fp-go/v2/array/array.go:248.53,250.2 1 1
github.com/IBM/fp-go/v2/array/array.go:254.49,256.2 1 1
github.com/IBM/fp-go/v2/array/array.go:259.76,261.2 1 1
github.com/IBM/fp-go/v2/array/array.go:264.83,266.2 1 1
github.com/IBM/fp-go/v2/array/array.go:270.40,272.2 1 1
github.com/IBM/fp-go/v2/array/array.go:276.38,278.2 1 1
github.com/IBM/fp-go/v2/array/array.go:282.39,284.2 1 1
github.com/IBM/fp-go/v2/array/array.go:288.38,290.2 1 1
github.com/IBM/fp-go/v2/array/array.go:293.55,294.26 1 1
github.com/IBM/fp-go/v2/array/array.go:294.26,298.35 4 1
github.com/IBM/fp-go/v2/array/array.go:298.35,303.4 4 1
github.com/IBM/fp-go/v2/array/array.go:304.3,304.16 1 1
github.com/IBM/fp-go/v2/array/array.go:313.56,315.26 2 1
github.com/IBM/fp-go/v2/array/array.go:315.26,316.18 1 1
github.com/IBM/fp-go/v2/array/array.go:316.18,318.4 1 0
github.com/IBM/fp-go/v2/array/array.go:319.3,319.25 1 1
github.com/IBM/fp-go/v2/array/array.go:324.60,326.36 2 1
github.com/IBM/fp-go/v2/array/array.go:326.36,328.3 1 1
github.com/IBM/fp-go/v2/array/array.go:336.36,338.2 1 1
github.com/IBM/fp-go/v2/array/array.go:341.51,343.2 1 1
github.com/IBM/fp-go/v2/array/array.go:347.51,349.2 1 1
github.com/IBM/fp-go/v2/array/array.go:353.48,355.2 1 1
github.com/IBM/fp-go/v2/array/array.go:358.30,360.2 1 1
github.com/IBM/fp-go/v2/array/array.go:365.78,367.2 1 1
github.com/IBM/fp-go/v2/array/array.go:371.75,373.2 1 1
github.com/IBM/fp-go/v2/array/array.go:376.32,378.2 1 1
github.com/IBM/fp-go/v2/array/array.go:381.35,383.2 1 1
github.com/IBM/fp-go/v2/array/array.go:386.28,388.2 1 1
github.com/IBM/fp-go/v2/array/array.go:391.56,393.2 1 1
github.com/IBM/fp-go/v2/array/array.go:396.29,398.2 1 1
github.com/IBM/fp-go/v2/array/array.go:401.49,403.2 1 1
github.com/IBM/fp-go/v2/array/array.go:406.67,408.2 1 1
github.com/IBM/fp-go/v2/array/array.go:411.81,413.2 1 1
github.com/IBM/fp-go/v2/array/array.go:416.45,418.2 1 1
github.com/IBM/fp-go/v2/array/array.go:421.44,423.2 1 1
github.com/IBM/fp-go/v2/array/array.go:427.52,429.2 1 1
github.com/IBM/fp-go/v2/array/array.go:433.48,435.2 1 1
github.com/IBM/fp-go/v2/array/array.go:438.50,440.2 1 1
github.com/IBM/fp-go/v2/array/bind.go:34.7,36.2 1 1
github.com/IBM/fp-go/v2/array/bind.go:56.19,58.2 1 1
github.com/IBM/fp-go/v2/array/bind.go:76.19,78.2 1 1
github.com/IBM/fp-go/v2/array/bind.go:96.19,98.2 1 1
github.com/IBM/fp-go/v2/array/bind.go:113.18,115.2 1 1
github.com/IBM/fp-go/v2/array/bind.go:134.19,136.2 1 1
github.com/IBM/fp-go/v2/array/eq.go:22.66,23.29 1 1
github.com/IBM/fp-go/v2/array/eq.go:23.29,25.3 1 1
github.com/IBM/fp-go/v2/array/eq.go:26.2,26.26 1 1
github.com/IBM/fp-go/v2/array/eq.go:26.26,28.18 2 1
github.com/IBM/fp-go/v2/array/eq.go:28.18,30.4 1 1
github.com/IBM/fp-go/v2/array/eq.go:32.2,32.13 1 1
github.com/IBM/fp-go/v2/array/eq.go:46.37,48.49 2 1
github.com/IBM/fp-go/v2/array/eq.go:48.49,50.3 1 1
github.com/IBM/fp-go/v2/array/find.go:31.64,33.2 1 1
github.com/IBM/fp-go/v2/array/find.go:44.78,46.2 1 1
github.com/IBM/fp-go/v2/array/find.go:62.76,64.2 1 1
github.com/IBM/fp-go/v2/array/find.go:68.90,70.2 1 1
github.com/IBM/fp-go/v2/array/find.go:79.63,81.2 1 1
github.com/IBM/fp-go/v2/array/find.go:85.77,87.2 1 1
github.com/IBM/fp-go/v2/array/find.go:91.75,93.2 1 1
github.com/IBM/fp-go/v2/array/find.go:97.89,99.2 1 1
github.com/IBM/fp-go/v2/array/magma.go:36.50,38.2 1 1
github.com/IBM/fp-go/v2/array/monad.go:37.65,39.2 1 1
github.com/IBM/fp-go/v2/array/monoid.go:24.41,27.13 2 1
github.com/IBM/fp-go/v2/array/monoid.go:27.13,29.3 1 1
github.com/IBM/fp-go/v2/array/monoid.go:30.2,31.13 2 1
github.com/IBM/fp-go/v2/array/monoid.go:31.13,33.3 1 1
github.com/IBM/fp-go/v2/array/monoid.go:35.2,37.12 3 1
github.com/IBM/fp-go/v2/array/monoid.go:48.36,50.2 1 1
github.com/IBM/fp-go/v2/array/monoid.go:59.42,61.2 1 1
github.com/IBM/fp-go/v2/array/monoid.go:63.45,65.2 1 1
github.com/IBM/fp-go/v2/array/monoid.go:79.45,84.48 3 1
github.com/IBM/fp-go/v2/array/monoid.go:84.48,86.3 1 1
github.com/IBM/fp-go/v2/array/monoid.go:88.2,88.12 1 1
github.com/IBM/fp-go/v2/array/sequence.go:62.22,64.47 2 1
github.com/IBM/fp-go/v2/array/sequence.go:64.47,69.3 1 1
github.com/IBM/fp-go/v2/array/sequence.go:91.61,97.2 1 1
github.com/IBM/fp-go/v2/array/sort.go:33.49,35.2 1 1
github.com/IBM/fp-go/v2/array/sort.go:61.70,63.2 1 1
github.com/IBM/fp-go/v2/array/sort.go:90.53,92.2 1 1
github.com/IBM/fp-go/v2/array/traverse.go:60.34,62.2 1 1
github.com/IBM/fp-go/v2/array/traverse.go:75.24,78.2 1 1
github.com/IBM/fp-go/v2/array/uniq.go:18.43,20.2 1 1
github.com/IBM/fp-go/v2/array/uniq.go:45.62,47.2 1 1
github.com/IBM/fp-go/v2/array/zip.go:36.73,38.2 1 1
github.com/IBM/fp-go/v2/array/zip.go:54.55,56.2 1 1
github.com/IBM/fp-go/v2/array/zip.go:75.62,77.2 1 1

253
v2/array/doc.go Normal file
View File

@@ -0,0 +1,253 @@
// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package array provides functional programming utilities for working with Go slices.
//
// This package treats Go slices as immutable arrays and provides a rich set of operations
// for transforming, filtering, folding, and combining arrays in a functional style.
// All operations return new arrays rather than modifying existing ones.
//
// # Core Concepts
//
// The array package implements several functional programming abstractions:
// - Functor: Transform array elements with Map
// - Applicative: Apply functions in arrays to values in arrays
// - Monad: Chain operations that produce arrays with Chain/FlatMap
// - Foldable: Reduce arrays to single values with Reduce/Fold
// - Traversable: Transform arrays while preserving structure
//
// # Basic Operations
//
// // Creating arrays
// arr := array.From(1, 2, 3, 4, 5)
// repeated := array.Replicate(3, "hello")
// generated := array.MakeBy(5, func(i int) int { return i * 2 })
//
// // Transforming arrays
// doubled := array.Map(func(x int) int { return x * 2 })(arr)
// filtered := array.Filter(func(x int) bool { return x > 2 })(arr)
//
// // Combining arrays
// combined := array.Flatten([][]int{{1, 2}, {3, 4}})
// zipped := array.Zip([]string{"a", "b"})([]int{1, 2})
//
// # Mapping and Filtering
//
// Transform array elements with Map, or filter elements with Filter:
//
// numbers := []int{1, 2, 3, 4, 5}
//
// // Map transforms each element
// doubled := array.Map(func(x int) int { return x * 2 })(numbers)
// // Result: [2, 4, 6, 8, 10]
//
// // Filter keeps elements matching a predicate
// evens := array.Filter(func(x int) bool { return x%2 == 0 })(numbers)
// // Result: [2, 4]
//
// // FilterMap combines both operations
// import "github.com/IBM/fp-go/v2/option"
// result := array.FilterMap(func(x int) option.Option[int] {
// if x%2 == 0 {
// return option.Some(x * 2)
// }
// return option.None[int]()
// })(numbers)
// // Result: [4, 8]
//
// # Folding and Reducing
//
// Reduce arrays to single values:
//
// numbers := []int{1, 2, 3, 4, 5}
//
// // Sum all elements
// sum := array.Reduce(func(acc, x int) int { return acc + x }, 0)(numbers)
// // Result: 15
//
// // Using a Monoid
// import "github.com/IBM/fp-go/v2/monoid"
// sum := array.Fold(monoid.MonoidSum[int]())(numbers)
// // Result: 15
//
// # Chaining Operations
//
// Chain operations that produce arrays (also known as FlatMap):
//
// numbers := []int{1, 2, 3}
// result := array.Chain(func(x int) []int {
// return []int{x, x * 10}
// })(numbers)
// // Result: [1, 10, 2, 20, 3, 30]
//
// # Finding Elements
//
// Search for elements matching predicates:
//
// numbers := []int{1, 2, 3, 4, 5}
//
// // Find first element > 3
// first := array.FindFirst(func(x int) bool { return x > 3 })(numbers)
// // Result: Some(4)
//
// // Find last element > 3
// last := array.FindLast(func(x int) bool { return x > 3 })(numbers)
// // Result: Some(5)
//
// // Get head and tail
// head := array.Head(numbers) // Some(1)
// tail := array.Tail(numbers) // Some([2, 3, 4, 5])
//
// # Sorting
//
// Sort arrays using Ord instances:
//
// import "github.com/IBM/fp-go/v2/ord"
//
// numbers := []int{3, 1, 4, 1, 5}
// sorted := array.Sort(ord.FromStrictCompare[int]())(numbers)
// // Result: [1, 1, 3, 4, 5]
//
// // Sort by extracted key
// type Person struct { Name string; Age int }
// people := []Person{{"Alice", 30}, {"Bob", 25}}
// byAge := array.SortByKey(ord.FromStrictCompare[int](), func(p Person) int {
// return p.Age
// })(people)
//
// # Uniqueness
//
// Remove duplicate elements:
//
// numbers := []int{1, 2, 2, 3, 3, 3}
// unique := array.StrictUniq(numbers)
// // Result: [1, 2, 3]
//
// // Unique by key
// type Person struct { Name string; Age int }
// people := []Person{{"Alice", 30}, {"Bob", 25}, {"Alice", 35}}
// uniqueByName := array.Uniq(func(p Person) string { return p.Name })(people)
// // Result: [{"Alice", 30}, {"Bob", 25}]
//
// # Zipping
//
// Combine multiple arrays:
//
// names := []string{"Alice", "Bob", "Charlie"}
// ages := []int{30, 25, 35}
//
// // Zip into tuples
// pairs := array.Zip(ages)(names)
// // Result: [(Alice, 30), (Bob, 25), (Charlie, 35)]
//
// // Zip with custom function
// result := array.ZipWith(names, ages, func(name string, age int) string {
// return fmt.Sprintf("%s is %d", name, age)
// })
//
// # Monadic Do Notation
//
// Build complex array computations using do-notation style:
//
// result := array.Do(
// struct{ X, Y int }{},
// )(
// array.Bind(
// func(x int) func(s struct{}) struct{ X int } {
// return func(s struct{}) struct{ X int } { return struct{ X int }{x} }
// },
// func(s struct{}) []int { return []int{1, 2, 3} },
// ),
// array.Bind(
// func(y int) func(s struct{ X int }) struct{ X, Y int } {
// return func(s struct{ X int }) struct{ X, Y int } {
// return struct{ X, Y int }{s.X, y}
// }
// },
// func(s struct{ X int }) []int { return []int{4, 5} },
// ),
// )
// // Produces all combinations: [{1,4}, {1,5}, {2,4}, {2,5}, {3,4}, {3,5}]
//
// # Sequence and Traverse
//
// Transform arrays of effects into effects of arrays:
//
// import "github.com/IBM/fp-go/v2/option"
//
// // Sequence: []Option[A] -> Option[[]A]
// opts := []option.Option[int]{
// option.Some(1),
// option.Some(2),
// option.Some(3),
// }
// result := array.ArrayOption[int]()(opts)
// // Result: Some([1, 2, 3])
//
// // If any is None, result is None
// opts2 := []option.Option[int]{
// option.Some(1),
// option.None[int](),
// option.Some(3),
// }
// result2 := array.ArrayOption[int]()(opts2)
// // Result: None
//
// # Equality and Comparison
//
// Compare arrays for equality:
//
// import "github.com/IBM/fp-go/v2/eq"
//
// eq := array.Eq(eq.FromStrictEquals[int]())
// equal := eq.Equals([]int{1, 2, 3}, []int{1, 2, 3})
// // Result: true
//
// # Monoid Operations
//
// Combine arrays using monoid operations:
//
// import "github.com/IBM/fp-go/v2/monoid"
//
// // Concatenate arrays
// m := array.Monoid[int]()
// result := m.Concat([]int{1, 2}, []int{3, 4})
// // Result: [1, 2, 3, 4]
//
// // Concatenate multiple arrays efficiently
// result := array.ArrayConcatAll(
// []int{1, 2},
// []int{3, 4},
// []int{5, 6},
// )
// // Result: [1, 2, 3, 4, 5, 6]
//
// # Performance Considerations
//
// Most operations create new arrays rather than modifying existing ones. For performance-critical
// code, consider:
// - Using Copy for shallow copies when needed
// - Using Clone with a custom cloning function for deep copies
// - Batching operations to minimize intermediate allocations
// - Using ArrayConcatAll for efficient concatenation of multiple arrays
//
// # Subpackages
//
// - array/generic: Generic implementations for custom array-like types
// - array/nonempty: Operations for non-empty arrays with compile-time guarantees
// - array/testing: Testing utilities for array laws and properties
package array
// Made with Bob

View File

@@ -32,6 +32,17 @@ func equals[T any](left []T, right []T, eq func(T, T) bool) bool {
return true
}
// Eq creates an equality checker for arrays given an equality checker for elements.
// Two arrays are considered equal if they have the same length and all corresponding
// elements are equal according to the provided Eq instance.
//
// Example:
//
// import "github.com/IBM/fp-go/v2/eq"
//
// intArrayEq := array.Eq(eq.FromStrictEquals[int]())
// result := intArrayEq.Equals([]int{1, 2, 3}, []int{1, 2, 3}) // true
// result2 := intArrayEq.Equals([]int{1, 2, 3}, []int{1, 2, 4}) // false
func Eq[T any](e E.Eq[T]) E.Eq[[]T] {
eq := e.Equals
return E.FromEquals(func(left, right []T) bool {

46
v2/array/eq_test.go Normal file
View File

@@ -0,0 +1,46 @@
// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package array
import (
"testing"
E "github.com/IBM/fp-go/v2/eq"
"github.com/stretchr/testify/assert"
)
func TestEq(t *testing.T) {
intEq := Eq(E.FromStrictEquals[int]())
// Test equal arrays
assert.True(t, intEq.Equals([]int{1, 2, 3}, []int{1, 2, 3}))
// Test different lengths
assert.False(t, intEq.Equals([]int{1, 2, 3}, []int{1, 2}))
// Test different values
assert.False(t, intEq.Equals([]int{1, 2, 3}, []int{1, 2, 4}))
// Test empty arrays
assert.True(t, intEq.Equals([]int{}, []int{}))
// Test string arrays
stringEq := Eq(E.FromStrictEquals[string]())
assert.True(t, stringEq.Equals([]string{"a", "b"}, []string{"a", "b"}))
assert.False(t, stringEq.Equals([]string{"a", "b"}, []string{"a", "c"}))
}
// Made with Bob

View File

@@ -20,42 +20,80 @@ import (
O "github.com/IBM/fp-go/v2/option"
)
// FindFirst finds the first element which satisfies a predicate (or a refinement) function
// FindFirst finds the first element which satisfies a predicate function.
// Returns Some(element) if found, None if no element matches.
//
// Example:
//
// findGreaterThan3 := array.FindFirst(func(x int) bool { return x > 3 })
// result := findGreaterThan3([]int{1, 2, 4, 5}) // Some(4)
// result2 := findGreaterThan3([]int{1, 2, 3}) // None
func FindFirst[A any](pred func(A) bool) func([]A) O.Option[A] {
return G.FindFirst[[]A](pred)
}
// FindFirstWithIndex finds the first element which satisfies a predicate (or a refinement) function
// FindFirstWithIndex finds the first element which satisfies a predicate function that also receives the index.
// Returns Some(element) if found, None if no element matches.
//
// Example:
//
// findEvenAtEvenIndex := array.FindFirstWithIndex(func(i, x int) bool {
// return i%2 == 0 && x%2 == 0
// })
// result := findEvenAtEvenIndex([]int{1, 3, 4, 5}) // Some(4)
func FindFirstWithIndex[A any](pred func(int, A) bool) func([]A) O.Option[A] {
return G.FindFirstWithIndex[[]A](pred)
}
// FindFirstMap finds the first element returned by an [O.Option] based selector function
// FindFirstMap finds the first element for which the selector function returns Some.
// This combines finding and mapping in a single operation.
//
// Example:
//
// import "strconv"
//
// parseFirst := array.FindFirstMap(func(s string) option.Option[int] {
// if n, err := strconv.Atoi(s); err == nil {
// return option.Some(n)
// }
// return option.None[int]()
// })
// result := parseFirst([]string{"a", "42", "b"}) // Some(42)
func FindFirstMap[A, B any](sel func(A) O.Option[B]) func([]A) O.Option[B] {
return G.FindFirstMap[[]A](sel)
}
// FindFirstMapWithIndex finds the first element returned by an [O.Option] based selector function
// FindFirstMapWithIndex finds the first element for which the selector function returns Some.
// The selector receives both the index and the element.
func FindFirstMapWithIndex[A, B any](sel func(int, A) O.Option[B]) func([]A) O.Option[B] {
return G.FindFirstMapWithIndex[[]A](sel)
}
// FindLast finds the Last element which satisfies a predicate (or a refinement) function
// FindLast finds the last element which satisfies a predicate function.
// Returns Some(element) if found, None if no element matches.
//
// Example:
//
// findGreaterThan3 := array.FindLast(func(x int) bool { return x > 3 })
// result := findGreaterThan3([]int{1, 4, 2, 5}) // Some(5)
func FindLast[A any](pred func(A) bool) func([]A) O.Option[A] {
return G.FindLast[[]A](pred)
}
// FindLastWithIndex finds the Last element which satisfies a predicate (or a refinement) function
// FindLastWithIndex finds the last element which satisfies a predicate function that also receives the index.
// Returns Some(element) if found, None if no element matches.
func FindLastWithIndex[A any](pred func(int, A) bool) func([]A) O.Option[A] {
return G.FindLastWithIndex[[]A](pred)
}
// FindLastMap finds the Last element returned by an [O.Option] based selector function
// FindLastMap finds the last element for which the selector function returns Some.
// This combines finding and mapping in a single operation, searching from the end.
func FindLastMap[A, B any](sel func(A) O.Option[B]) func([]A) O.Option[B] {
return G.FindLastMap[[]A](sel)
}
// FindLastMapWithIndex finds the Last element returned by an [O.Option] based selector function
// FindLastMapWithIndex finds the last element for which the selector function returns Some.
// The selector receives both the index and the element, searching from the end.
func FindLastMapWithIndex[A, B any](sel func(int, A) O.Option[B]) func([]A) O.Option[B] {
return G.FindLastMapWithIndex[[]A](sel)
}

107
v2/array/find_test.go Normal file
View File

@@ -0,0 +1,107 @@
// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package array
import (
"fmt"
"testing"
O "github.com/IBM/fp-go/v2/option"
"github.com/stretchr/testify/assert"
)
func TestFindFirstWithIndex(t *testing.T) {
src := []int{1, 2, 3, 4, 5}
finder := FindFirstWithIndex(func(i, x int) bool {
return i > 2 && x%2 == 0
})
result := finder(src)
assert.Equal(t, O.Some(4), result)
notFound := FindFirstWithIndex(func(i, x int) bool {
return i > 10
})
assert.Equal(t, O.None[int](), notFound(src))
}
func TestFindFirstMap(t *testing.T) {
src := []string{"a", "42", "b", "100"}
finder := FindFirstMap(func(s string) O.Option[int] {
if len(s) > 1 {
return O.Some(len(s))
}
return O.None[int]()
})
result := finder(src)
assert.Equal(t, O.Some(2), result)
}
func TestFindFirstMapWithIndex(t *testing.T) {
src := []string{"a", "b", "c", "d"}
finder := FindFirstMapWithIndex(func(i int, s string) O.Option[string] {
if i > 1 {
return O.Some(fmt.Sprintf("%d:%s", i, s))
}
return O.None[string]()
})
result := finder(src)
assert.Equal(t, O.Some("2:c"), result)
}
func TestFindLast(t *testing.T) {
src := []int{1, 2, 3, 4, 5}
finder := FindLast(func(x int) bool { return x%2 == 0 })
result := finder(src)
assert.Equal(t, O.Some(4), result)
notFound := FindLast(func(x int) bool { return x > 10 })
assert.Equal(t, O.None[int](), notFound(src))
}
func TestFindLastWithIndex(t *testing.T) {
src := []int{1, 2, 3, 4, 5}
finder := FindLastWithIndex(func(i, x int) bool {
return i < 3 && x%2 == 0
})
result := finder(src)
assert.Equal(t, O.Some(2), result)
}
func TestFindLastMap(t *testing.T) {
src := []string{"a", "42", "b", "100"}
finder := FindLastMap(func(s string) O.Option[int] {
if len(s) > 1 {
return O.Some(len(s))
}
return O.None[int]()
})
result := finder(src)
assert.Equal(t, O.Some(3), result)
}
func TestFindLastMapWithIndex(t *testing.T) {
src := []string{"a", "b", "c", "d"}
finder := FindLastMapWithIndex(func(i int, s string) O.Option[string] {
if i < 3 {
return O.Some(fmt.Sprintf("%d:%s", i, s))
}
return O.None[string]()
})
result := finder(src)
assert.Equal(t, O.Some("2:c"), result)
}
// Made with Bob

View File

@@ -163,15 +163,25 @@ func Size[GA ~[]A, A any](as GA) int {
}
func filterMap[GA ~[]A, GB ~[]B, A, B any](fa GA, f func(A) O.Option[B]) GB {
return array.Reduce(fa, func(bs GB, a A) GB {
return O.MonadFold(f(a), F.Constant(bs), F.Bind1st(Append[GB, B], bs))
}, Empty[GB]())
result := make(GB, 0, len(fa))
for _, a := range fa {
O.Map(func(b B) B {
result = append(result, b)
return b
})(f(a))
}
return result
}
func filterMapWithIndex[GA ~[]A, GB ~[]B, A, B any](fa GA, f func(int, A) O.Option[B]) GB {
return array.ReduceWithIndex(fa, func(idx int, bs GB, a A) GB {
return O.MonadFold(f(idx, a), F.Constant(bs), F.Bind1st(Append[GB, B], bs))
}, Empty[GB]())
result := make(GB, 0, len(fa))
for i, a := range fa {
O.Map(func(b B) B {
result = append(result, b)
return b
})(f(i, a))
}
return result
}
func MonadFilterMap[GA ~[]A, GB ~[]B, A, B any](fa GA, f func(A) O.Option[B]) GB {
@@ -310,28 +320,34 @@ func Clone[AS ~[]A, A any](f func(A) A) func(as AS) AS {
}
func FoldMap[AS ~[]A, A, B any](m M.Monoid[B]) func(func(A) B) func(AS) B {
empty := m.Empty()
concat := m.Concat
return func(f func(A) B) func(AS) B {
return func(as AS) B {
return array.Reduce(as, func(cur B, a A) B {
return m.Concat(cur, f(a))
}, m.Empty())
return concat(cur, f(a))
}, empty)
}
}
}
func FoldMapWithIndex[AS ~[]A, A, B any](m M.Monoid[B]) func(func(int, A) B) func(AS) B {
empty := m.Empty()
concat := m.Concat
return func(f func(int, A) B) func(AS) B {
return func(as AS) B {
return array.ReduceWithIndex(as, func(idx int, cur B, a A) B {
return m.Concat(cur, f(idx, a))
}, m.Empty())
return concat(cur, f(idx, a))
}, empty)
}
}
}
func Fold[AS ~[]A, A any](m M.Monoid[A]) func(AS) A {
empty := m.Empty()
concat := m.Concat
return func(as AS) A {
return array.Reduce(as, m.Concat, m.Empty())
return array.Reduce(as, concat, empty)
}
}

View File

@@ -19,6 +19,20 @@ import (
M "github.com/IBM/fp-go/v2/monoid"
)
// ConcatAll concatenates all elements of an array using the provided Monoid.
// This reduces the array to a single value by repeatedly applying the Monoid's concat operation.
//
// Example:
//
// import "github.com/IBM/fp-go/v2/monoid"
//
// // Sum all numbers
// sumAll := array.ConcatAll(monoid.MonoidSum[int]())
// result := sumAll([]int{1, 2, 3, 4, 5}) // 15
//
// // Concatenate all strings
// concatStrings := array.ConcatAll(monoid.MonoidString())
// result2 := concatStrings([]string{"Hello", " ", "World"}) // "Hello World"
func ConcatAll[A any](m M.Monoid[A]) func([]A) A {
return Reduce(m.Concat, m.Empty())
}

163
v2/array/misc_test.go Normal file
View File

@@ -0,0 +1,163 @@
// Copyright (c) 2023 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package array
import (
"testing"
O "github.com/IBM/fp-go/v2/option"
OR "github.com/IBM/fp-go/v2/ord"
"github.com/stretchr/testify/assert"
)
func TestAnyWithIndex(t *testing.T) {
src := []int{1, 2, 3, 4, 5}
checker := AnyWithIndex(func(i, x int) bool {
return i == 2 && x == 3
})
assert.True(t, checker(src))
checker2 := AnyWithIndex(func(i, x int) bool {
return i == 10
})
assert.False(t, checker2(src))
}
func TestSemigroup(t *testing.T) {
sg := Semigroup[int]()
result := sg.Concat([]int{1, 2}, []int{3, 4})
assert.Equal(t, []int{1, 2, 3, 4}, result)
}
func TestArrayConcatAll(t *testing.T) {
result := ArrayConcatAll(
[]int{1, 2},
[]int{3, 4},
[]int{5, 6},
)
assert.Equal(t, []int{1, 2, 3, 4, 5, 6}, result)
// Test with empty arrays
result2 := ArrayConcatAll(
[]int{},
[]int{1},
[]int{},
)
assert.Equal(t, []int{1}, result2)
}
func TestMonad(t *testing.T) {
m := Monad[int, string]()
// Test Map
mapFn := m.Map(func(x int) string {
return string(rune('a' + x - 1))
})
mapped := mapFn([]int{1, 2, 3})
assert.Equal(t, []string{"a", "b", "c"}, mapped)
// Test Chain
chainFn := m.Chain(func(x int) []string {
return []string{string(rune('a' + x - 1))}
})
chained := chainFn([]int{1, 2})
assert.Equal(t, []string{"a", "b"}, chained)
// Test Of
ofResult := m.Of(42)
assert.Equal(t, []int{42}, ofResult)
}
func TestSortByKey(t *testing.T) {
type Person struct {
Name string
Age int
}
people := []Person{
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35},
}
sorter := SortByKey(OR.FromStrictCompare[int](), func(p Person) int {
return p.Age
})
result := sorter(people)
assert.Equal(t, "Bob", result[0].Name)
assert.Equal(t, "Alice", result[1].Name)
assert.Equal(t, "Charlie", result[2].Name)
}
func TestMonadTraverse(t *testing.T) {
result := MonadTraverse(
O.Of[[]int],
O.Map[[]int, func(int) []int],
O.Ap[[]int, int],
[]int{1, 3, 5},
func(n int) O.Option[int] {
if n%2 == 1 {
return O.Some(n * 2)
}
return O.None[int]()
},
)
assert.Equal(t, O.Some([]int{2, 6, 10}), result)
// Test with None case
result2 := MonadTraverse(
O.Of[[]int],
O.Map[[]int, func(int) []int],
O.Ap[[]int, int],
[]int{1, 2, 3},
func(n int) O.Option[int] {
if n%2 == 1 {
return O.Some(n * 2)
}
return O.None[int]()
},
)
assert.Equal(t, O.None[[]int](), result2)
}
func TestUniqByKey(t *testing.T) {
type Person struct {
Name string
Age int
}
people := []Person{
{"Alice", 30},
{"Bob", 25},
{"Alice", 35},
{"Charlie", 30},
}
uniquer := Uniq(func(p Person) string {
return p.Name
})
result := uniquer(people)
assert.Equal(t, 3, len(result))
assert.Equal(t, "Alice", result[0].Name)
assert.Equal(t, "Bob", result[1].Name)
assert.Equal(t, "Charlie", result[2].Name)
}
// Made with Bob

View File

@@ -20,7 +20,20 @@ import (
"github.com/IBM/fp-go/v2/internal/monad"
)
// Monad returns the monadic operations for an array
// Monad returns the monadic operations for an array.
// This provides a structured way to access all monad operations (Map, Chain, Ap, Of)
// for arrays in a single interface.
//
// The Monad interface is useful when you need to pass monadic operations as parameters
// or when working with generic code that operates on any monad.
//
// Example:
//
// m := array.Monad[int, string]()
// result := m.Chain([]int{1, 2, 3}, func(x int) []string {
// return []string{fmt.Sprintf("%d", x), fmt.Sprintf("%d!", x)}
// })
// // Result: ["1", "1!", "2", "2!", "3", "3!"]
func Monad[A, B any]() monad.Monad[A, B, []A, []B, []func(A) B] {
return G.Monad[A, B, []A, []B, []func(A) B]()
}

View File

@@ -37,10 +37,25 @@ func concat[T any](left, right []T) []T {
return buf
}
// Monoid returns a Monoid instance for arrays.
// The Monoid combines arrays through concatenation, with an empty array as the identity element.
//
// Example:
//
// m := array.Monoid[int]()
// result := m.Concat([]int{1, 2}, []int{3, 4}) // [1, 2, 3, 4]
// empty := m.Empty() // []
func Monoid[T any]() M.Monoid[[]T] {
return M.MakeMonoid(concat[T], Empty[T]())
}
// Semigroup returns a Semigroup instance for arrays.
// The Semigroup combines arrays through concatenation.
//
// Example:
//
// s := array.Semigroup[int]()
// result := s.Concat([]int{1, 2}, []int{3, 4}) // [1, 2, 3, 4]
func Semigroup[T any]() S.Semigroup[[]T] {
return S.MakeSemigroup(concat[T])
}
@@ -49,7 +64,18 @@ func addLen[A any](count int, data []A) int {
return count + len(data)
}
// ConcatAll efficiently concatenates the input arrays into a final array
// ArrayConcatAll efficiently concatenates multiple arrays into a single array.
// This function pre-allocates the exact amount of memory needed and performs
// a single copy operation for each input array, making it more efficient than
// repeated concatenations.
//
// Example:
//
// result := array.ArrayConcatAll(
// []int{1, 2},
// []int{3, 4},
// []int{5, 6},
// ) // [1, 2, 3, 4, 5, 6]
func ArrayConcatAll[A any](data ...[]A) []A {
// get the full size
count := array.Reduce(data, addLen[A], 0)

View File

@@ -20,35 +20,72 @@ import (
O "github.com/IBM/fp-go/v2/option"
)
// We need to pass the members of the applicative explicitly, because golang does neither support higher kinded types nor template methods on structs or interfaces
// HKTA = HKT<A>
// HKTRA = HKT<[]A>
// HKTFRA = HKT<func(A)[]A>
// Sequence takes an `Array` where elements are `HKT<A>` (higher kinded type) and,
// using an applicative of that `HKT`, returns an `HKT` of `[]A`.
// e.g. it can turn an `[]Either[error, string]` into an `Either[error, []string]`.
// Sequence takes an array where elements are HKT<A> (higher kinded type) and,
// using an applicative of that HKT, returns an HKT of []A.
//
// Sequence requires an `Applicative` of the `HKT` you are targeting, e.g. to turn an
// `[]Either[E, A]` into an `Either[E, []A]`, it needs an
// Applicative` for `Either`, to to turn an `[]Option[A]` into an `Option[ []A]`,
// it needs an `Applicative` for `Option`.
// For example, it can turn:
// - []Either[error, string] into Either[error, []string]
// - []Option[int] into Option[[]int]
//
// Sequence requires an Applicative of the HKT you are targeting. To turn an
// []Either[E, A] into an Either[E, []A], it needs an Applicative for Either.
// To turn an []Option[A] into an Option[[]A], it needs an Applicative for Option.
//
// Note: We need to pass the members of the applicative explicitly because Go does not
// support higher kinded types or template methods on structs or interfaces.
//
// Type parameters:
// - HKTA = HKT<A> (e.g., Option[A], Either[E, A])
// - HKTRA = HKT<[]A> (e.g., Option[[]A], Either[E, []A])
// - HKTFRA = HKT<func(A)[]A> (e.g., Option[func(A)[]A])
//
// Example:
//
// import "github.com/IBM/fp-go/v2/option"
//
// opts := []option.Option[int]{
// option.Some(1),
// option.Some(2),
// option.Some(3),
// }
//
// seq := array.Sequence(
// option.Of[[]int],
// option.MonadMap[[]int, func(int) []int],
// option.MonadAp[[]int, int],
// )
// result := seq(opts) // Some([1, 2, 3])
func Sequence[A, HKTA, HKTRA, HKTFRA any](
_of func([]A) HKTRA,
_map func(HKTRA, func([]A) func(A) []A) HKTFRA,
_ap func(HKTFRA, HKTA) HKTRA,
) func([]HKTA) HKTRA {
ca := F.Curry2(Append[A])
empty := _of(Empty[A]())
return Reduce(func(fas HKTRA, fa HKTA) HKTRA {
return _ap(
_map(fas, ca),
fa,
)
}, _of(Empty[A]()))
return _ap(_map(fas, ca), fa)
}, empty)
}
// ArrayOption returns a function to convert sequence of options into an option of a sequence
// ArrayOption returns a function to convert a sequence of options into an option of a sequence.
// If all options are Some, returns Some containing an array of all values.
// If any option is None, returns None.
//
// Example:
//
// opts := []option.Option[int]{
// option.Some(1),
// option.Some(2),
// option.Some(3),
// }
// result := array.ArrayOption[int]()(opts) // Some([1, 2, 3])
//
// opts2 := []option.Option[int]{
// option.Some(1),
// option.None[int](),
// option.Some(3),
// }
// result2 := array.ArrayOption[int]()(opts2) // None
func ArrayOption[A any]() func([]O.Option[A]) O.Option[[]A] {
return Sequence(
O.Of[[]A],

View File

@@ -20,17 +20,73 @@ import (
O "github.com/IBM/fp-go/v2/ord"
)
// Sort implements a stable sort on the array given the provided ordering
// Sort implements a stable sort on the array given the provided ordering.
// The sort is stable, meaning that elements that compare equal retain their original order.
//
// Example:
//
// import "github.com/IBM/fp-go/v2/ord"
//
// numbers := []int{3, 1, 4, 1, 5, 9, 2, 6}
// sorted := array.Sort(ord.FromStrictCompare[int]())(numbers)
// // Result: [1, 1, 2, 3, 4, 5, 6, 9]
func Sort[T any](ord O.Ord[T]) func(ma []T) []T {
return G.Sort[[]T](ord)
}
// SortByKey implements a stable sort on the array given the provided ordering on an extracted key
// SortByKey implements a stable sort on the array given the provided ordering on an extracted key.
// This is useful when you want to sort complex types by a specific field.
//
// Example:
//
// import "github.com/IBM/fp-go/v2/ord"
//
// type Person struct {
// Name string
// Age int
// }
//
// people := []Person{
// {"Alice", 30},
// {"Bob", 25},
// {"Charlie", 35},
// }
//
// sortByAge := array.SortByKey(
// ord.FromStrictCompare[int](),
// func(p Person) int { return p.Age },
// )
// sorted := sortByAge(people)
// // Result: [{"Bob", 25}, {"Alice", 30}, {"Charlie", 35}]
func SortByKey[K, T any](ord O.Ord[K], f func(T) K) func(ma []T) []T {
return G.SortByKey[[]T](ord, f)
}
// SortBy implements a stable sort on the array given the provided ordering
// SortBy implements a stable sort on the array using multiple ordering criteria.
// The orderings are applied in sequence: if two elements are equal according to the first
// ordering, the second ordering is used, and so on.
//
// Example:
//
// import "github.com/IBM/fp-go/v2/ord"
//
// type Person struct {
// LastName string
// FirstName string
// }
//
// people := []Person{
// {"Smith", "John"},
// {"Smith", "Alice"},
// {"Jones", "Bob"},
// }
//
// sortByName := array.SortBy([]ord.Ord[Person]{
// ord.Contramap(func(p Person) string { return p.LastName })(ord.FromStrictCompare[string]()),
// ord.Contramap(func(p Person) string { return p.FirstName })(ord.FromStrictCompare[string]()),
// })
// sorted := sortByName(people)
// // Result: [{"Jones", "Bob"}, {"Smith", "Alice"}, {"Smith", "John"}]
func SortBy[T any](ord []O.Ord[T]) func(ma []T) []T {
return G.SortBy[[]T, []O.Ord[T]](ord)
}

View File

@@ -19,6 +19,39 @@ import (
"github.com/IBM/fp-go/v2/internal/array"
)
// Traverse maps each element of an array to an effect (HKT), then collects the results
// into an effect of an array. This is like a combination of Map and Sequence.
//
// Unlike Sequence which works with []HKT<A> -> HKT<[]A>, Traverse works with
// []A -> (A -> HKT<B>) -> HKT<[]B>, allowing you to transform elements while sequencing effects.
//
// Type parameters:
// - HKTB = HKT<B> (e.g., Option[B], Either[E, B])
// - HKTAB = HKT<func(B)[]B> (intermediate type for applicative)
// - HKTRB = HKT<[]B> (e.g., Option[[]B], Either[E, []B])
//
// Example:
//
// import (
// "github.com/IBM/fp-go/v2/option"
// "strconv"
// )
//
// // Parse strings to ints, returning None if any parse fails
// parseAll := array.Traverse(
// option.Of[[]int],
// option.Map[[]int, func(int) []int],
// option.Ap[[]int, int],
// func(s string) option.Option[int] {
// if n, err := strconv.Atoi(s); err == nil {
// return option.Some(n)
// }
// return option.None[int]()
// },
// )
//
// result := parseAll([]string{"1", "2", "3"}) // Some([1, 2, 3])
// result2 := parseAll([]string{"1", "x", "3"}) // None
func Traverse[A, B, HKTB, HKTAB, HKTRB any](
fof func([]B) HKTRB,
fmap func(func([]B) func(B) []B) func(HKTRB) HKTAB,
@@ -28,6 +61,11 @@ func Traverse[A, B, HKTB, HKTAB, HKTRB any](
return array.Traverse[[]A](fof, fmap, fap, f)
}
// MonadTraverse is the monadic version of Traverse that takes the array as a parameter.
// It maps each element of an array to an effect (HKT), then collects the results
// into an effect of an array.
//
// This is useful when you want to apply the traverse operation directly without currying.
func MonadTraverse[A, B, HKTB, HKTAB, HKTRB any](
fof func([]B) HKTRB,
fmap func(func([]B) func(B) []B) func(HKTRB) HKTAB,

View File

@@ -4,14 +4,44 @@ import (
G "github.com/IBM/fp-go/v2/array/generic"
)
// StrictUniq converts an array of arbitrary items into an array or unique items
// where uniqueness is determined by the built-in uniqueness constraint
// StrictUniq converts an array of arbitrary items into an array of unique items
// where uniqueness is determined by the built-in equality constraint (comparable).
// The first occurrence of each unique value is kept, subsequent duplicates are removed.
//
// Example:
//
// numbers := []int{1, 2, 2, 3, 3, 3, 4}
// unique := array.StrictUniq(numbers) // [1, 2, 3, 4]
//
// strings := []string{"a", "b", "a", "c", "b"}
// unique2 := array.StrictUniq(strings) // ["a", "b", "c"]
func StrictUniq[A comparable](as []A) []A {
return G.StrictUniq[[]A](as)
}
// Uniq converts an array of arbitrary items into an array or unique items
// where uniqueness is determined based on a key extractor function
// Uniq converts an array of arbitrary items into an array of unique items
// where uniqueness is determined based on a key extractor function.
// The first occurrence of each unique key is kept, subsequent duplicates are removed.
//
// This is useful for removing duplicates from arrays of complex types based on a specific field.
//
// Example:
//
// type Person struct {
// Name string
// Age int
// }
//
// people := []Person{
// {"Alice", 30},
// {"Bob", 25},
// {"Alice", 35}, // duplicate name
// {"Charlie", 30},
// }
//
// uniqueByName := array.Uniq(func(p Person) string { return p.Name })
// result := uniqueByName(people)
// // Result: [{"Alice", 30}, {"Bob", 25}, {"Charlie", 30}]
func Uniq[A any, K comparable](f func(A) K) func(as []A) []A {
return G.Uniq[[]A](f)
}

View File

@@ -20,19 +20,58 @@ import (
T "github.com/IBM/fp-go/v2/tuple"
)
// ZipWith applies a function to pairs of elements at the same index in two arrays, collecting the results in a new array. If one
// input array is short, excess elements of the longer array are discarded.
// ZipWith applies a function to pairs of elements at the same index in two arrays,
// collecting the results in a new array. If one input array is shorter, excess elements
// of the longer array are discarded.
//
// Example:
//
// names := []string{"Alice", "Bob", "Charlie"}
// ages := []int{30, 25, 35}
//
// result := array.ZipWith(names, ages, func(name string, age int) string {
// return fmt.Sprintf("%s is %d years old", name, age)
// })
// // Result: ["Alice is 30 years old", "Bob is 25 years old", "Charlie is 35 years old"]
func ZipWith[FCT ~func(A, B) C, A, B, C any](fa []A, fb []B, f FCT) []C {
return G.ZipWith[[]A, []B, []C, FCT](fa, fb, f)
}
// Zip takes two arrays and returns an array of corresponding pairs. If one input array is short, excess elements of the
// longer array are discarded
// Zip takes two arrays and returns an array of corresponding pairs (tuples).
// If one input array is shorter, excess elements of the longer array are discarded.
//
// Example:
//
// names := []string{"Alice", "Bob", "Charlie"}
// ages := []int{30, 25, 35}
//
// pairs := array.Zip(ages)(names)
// // Result: [(Alice, 30), (Bob, 25), (Charlie, 35)]
//
// // With different lengths
// pairs2 := array.Zip([]int{1, 2})([]string{"a", "b", "c"})
// // Result: [(a, 1), (b, 2)]
func Zip[A, B any](fb []B) func([]A) []T.Tuple2[A, B] {
return G.Zip[[]A, []B, []T.Tuple2[A, B]](fb)
}
// Unzip is the function is reverse of [Zip]. Takes an array of pairs and return two corresponding arrays
// Unzip is the reverse of Zip. It takes an array of pairs (tuples) and returns
// two corresponding arrays, one containing all first elements and one containing all second elements.
//
// Example:
//
// import "github.com/IBM/fp-go/v2/tuple"
//
// pairs := []tuple.Tuple2[string, int]{
// tuple.MakeTuple2("Alice", 30),
// tuple.MakeTuple2("Bob", 25),
// tuple.MakeTuple2("Charlie", 35),
// }
//
// result := array.Unzip(pairs)
// // Result: (["Alice", "Bob", "Charlie"], [30, 25, 35])
// names := result.Head // ["Alice", "Bob", "Charlie"]
// ages := result.Tail // [30, 25, 35]
func Unzip[A, B any](cs []T.Tuple2[A, B]) T.Tuple2[[]A, []B] {
return G.Unzip[[]A, []B, []T.Tuple2[A, B]](cs)
}

View File

@@ -102,6 +102,7 @@ func TraverseArrayWithIndex[E, A, B any](f func(int, A) Either[E, B]) func([]A)
return TraverseArrayWithIndexG[[]A, []B](f)
}
//go:inline
func SequenceArrayG[GA ~[]A, GOA ~[]Either[E, A], E, A any](ma GOA) Either[E, GA] {
return TraverseArrayG[GOA, GA](F.Identity[Either[E, A]])(ma)
}
@@ -119,6 +120,8 @@ func SequenceArrayG[GA ~[]A, GOA ~[]Either[E, A], E, A any](ma GOA) Either[E, GA
// }
// result := either.SequenceArray(eithers)
// // result is Right([]int{1, 2, 3})
//
//go:inline
func SequenceArray[E, A any](ma []Either[E, A]) Either[E, []A] {
return SequenceArrayG[[]A](ma)
}
@@ -135,6 +138,8 @@ func SequenceArray[E, A any](ma []Either[E, A]) Either[E, []A] {
// }
// result := either.CompactArrayG[[]either.Either[error, int], []int](eithers)
// // result is []int{1, 3}
//
//go:inline
func CompactArrayG[A1 ~[]Either[E, A], A2 ~[]A, E, A any](fa A1) A2 {
return RA.Reduce(fa, func(out A2, value Either[E, A]) A2 {
return MonadFold(value, F.Constant1[E](out), F.Bind1st(RA.Append[A2, A], out))
@@ -152,6 +157,8 @@ func CompactArrayG[A1 ~[]Either[E, A], A2 ~[]A, E, A any](fa A1) A2 {
// }
// result := either.CompactArray(eithers)
// // result is []int{1, 3}
//
//go:inline
func CompactArray[E, A any](fa []Either[E, A]) []A {
return CompactArrayG[[]Either[E, A], []A](fa)
}

View File

@@ -69,6 +69,8 @@ func (s Either[E, A]) Format(f fmt.State, c rune) {
//
// either.IsLeft(either.Left[int](errors.New("err"))) // true
// either.IsLeft(either.Right[error](42)) // false
//
//go:inline
func IsLeft[E, A any](val Either[E, A]) bool {
return val.isLeft
}
@@ -81,6 +83,8 @@ func IsLeft[E, A any](val Either[E, A]) bool {
//
// either.IsRight(either.Right[error](42)) // true
// either.IsRight(either.Left[int](errors.New("err"))) // false
//
//go:inline
func IsRight[E, A any](val Either[E, A]) bool {
return !val.isLeft
}
@@ -91,6 +95,8 @@ func IsRight[E, A any](val Either[E, A]) bool {
// Example:
//
// result := either.Left[int](errors.New("something went wrong"))
//
//go:inline
func Left[A, E any](value E) Either[E, A] {
return Either[E, A]{true, value}
}
@@ -101,6 +107,8 @@ func Left[A, E any](value E) Either[E, A] {
// Example:
//
// result := either.Right[error](42)
//
//go:inline
func Right[E, A any](value A) Either[E, A] {
return Either[E, A]{false, value}
}
@@ -115,6 +123,8 @@ func Right[E, A any](value A) Either[E, A] {
// func(err error) string { return "Error: " + err.Error() },
// func(n int) string { return fmt.Sprintf("Value: %d", n) },
// ) // "Value: 42"
//
//go:inline
func MonadFold[E, A, B any](ma Either[E, A], onLeft func(e E) B, onRight func(a A) B) B {
if ma.isLeft {
return onLeft(ma.value.(E))
@@ -130,6 +140,8 @@ func MonadFold[E, A, B any](ma Either[E, A], onLeft func(e E) B, onRight func(a
//
// val, err := either.Unwrap(either.Right[error](42)) // 42, nil
// val, err := either.Unwrap(either.Left[int](errors.New("fail"))) // 0, error
//
//go:inline
func Unwrap[E, A any](ma Either[E, A]) (A, E) {
if ma.isLeft {
var a A

View File

@@ -35,6 +35,8 @@ import (
// Example:
//
// result := either.Of[error](42) // Right(42)
//
//go:inline
func Of[E, A any](value A) Either[E, A] {
return F.Pipe1(value, Right[E, A])
}
@@ -81,6 +83,8 @@ func Ap[B, E, A any](fa Either[E, A]) func(fab Either[E, func(a A) B]) Either[E,
// either.Right[error](21),
// func(x int) int { return x * 2 },
// ) // Right(42)
//
//go:inline
func MonadMap[E, A, B any](fa Either[E, A], f func(a A) B) Either[E, B] {
return MonadChain(fa, F.Flow2(f, Right[E, B]))
}
@@ -157,6 +161,8 @@ func MapLeft[A, E1, E2 any](f func(E1) E2) func(fa Either[E1, A]) Either[E2, A]
// return either.Right[error](x * 2)
// },
// ) // Right(42)
//
//go:inline
func MonadChain[E, A, B any](fa Either[E, A], f func(a A) Either[E, B]) Either[E, B] {
return MonadFold(fa, Left[B, E], f)
}
@@ -300,6 +306,8 @@ func FromOption[A, E any](onNone func() E) func(Option[A]) Either[E, A] {
//
// result := either.ToOption(either.Right[error](42)) // Some(42)
// result := either.ToOption(either.Left[int](errors.New("err"))) // None
//
//go:inline
func ToOption[E, A any](ma Either[E, A]) Option[A] {
return MonadFold(ma, F.Ignore1of1[E](O.None[A]), O.Some[A])
}
@@ -351,6 +359,8 @@ func Fold[E, A, B any](onLeft func(E) B, onRight func(A) B) func(Either[E, A]) B
//
// val, err := either.UnwrapError(either.Right[error](42)) // 42, nil
// val, err := either.UnwrapError(either.Left[int](errors.New("fail"))) // zero, error
//
//go:inline
func UnwrapError[A any](ma Either[error, A]) (A, error) {
return Unwrap[error](ma)
}
@@ -495,6 +505,8 @@ func MonadSequence3[E, T1, T2, T3, R any](e1 Either[E, T1], e2 Either[E, T2], e3
//
// result := either.Swap(either.Right[error](42)) // Left(42)
// result := either.Swap(either.Left[int](errors.New("err"))) // Right(error)
//
//go:inline
func Swap[E, A any](val Either[E, A]) Either[A, E] {
return MonadFold(val, Right[A, E], Left[E, A])
}