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:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
325
v2/array/array_extended_test.go
Normal file
325
v2/array/array_extended_test.go
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
80
v2/array/bind_extended_test.go
Normal file
80
v2/array/bind_extended_test.go
Normal 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
134
v2/array/coverage.out
Normal 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
253
v2/array/doc.go
Normal 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
|
||||
@@ -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
46
v2/array/eq_test.go
Normal 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
|
||||
@@ -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
107
v2/array/find_test.go
Normal 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
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
163
v2/array/misc_test.go
Normal 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
|
||||
@@ -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]()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user