1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-12-19 23:42:05 +02:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Dr. Carsten Leue
a6c6ea804f fix: overhaul record
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-12-18 18:32:45 +01:00
11 changed files with 1224 additions and 65 deletions

View File

@@ -23,14 +23,15 @@ import (
IOR "github.com/IBM/fp-go/v2/ioresult" IOR "github.com/IBM/fp-go/v2/ioresult"
L "github.com/IBM/fp-go/v2/lazy" L "github.com/IBM/fp-go/v2/lazy"
O "github.com/IBM/fp-go/v2/option" O "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/pair"
R "github.com/IBM/fp-go/v2/record" R "github.com/IBM/fp-go/v2/record"
T "github.com/IBM/fp-go/v2/tuple" T "github.com/IBM/fp-go/v2/tuple"
"sync" "sync"
) )
func providerToEntry(p Provider) T.Tuple2[string, ProviderFactory] { func providerToEntry(p Provider) Entry[string, ProviderFactory] {
return T.MakeTuple2(p.Provides().Id(), p.Factory()) return pair.MakePair(p.Provides().Id(), p.Factory())
} }
func itemProviderToMap(p Provider) map[string][]ProviderFactory { func itemProviderToMap(p Provider) map[string][]ProviderFactory {

View File

@@ -4,10 +4,12 @@ import (
"github.com/IBM/fp-go/v2/iooption" "github.com/IBM/fp-go/v2/iooption"
"github.com/IBM/fp-go/v2/ioresult" "github.com/IBM/fp-go/v2/ioresult"
"github.com/IBM/fp-go/v2/option" "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/record"
) )
type ( type (
Option[T any] = option.Option[T] Option[T any] = option.Option[T]
IOResult[T any] = ioresult.IOResult[T] IOResult[T any] = ioresult.IOResult[T]
IOOption[T any] = iooption.IOOption[T] IOOption[T any] = iooption.IOOption[T]
Entry[K comparable, V any] = record.Entry[K, V]
) )

View File

@@ -4,12 +4,14 @@ import (
"github.com/IBM/fp-go/v2/context/ioresult" "github.com/IBM/fp-go/v2/context/ioresult"
"github.com/IBM/fp-go/v2/iooption" "github.com/IBM/fp-go/v2/iooption"
"github.com/IBM/fp-go/v2/option" "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/record"
"github.com/IBM/fp-go/v2/result" "github.com/IBM/fp-go/v2/result"
) )
type ( type (
Option[T any] = option.Option[T] Option[T any] = option.Option[T]
Result[T any] = result.Result[T] Result[T any] = result.Result[T]
IOResult[T any] = ioresult.IOResult[T] IOResult[T any] = ioresult.IOResult[T]
IOOption[T any] = iooption.IOOption[T] IOOption[T any] = iooption.IOOption[T]
Entry[K comparable, V any] = record.Entry[K, V]
) )

View File

@@ -22,9 +22,11 @@ import (
"strings" "strings"
"testing" "testing"
A "github.com/IBM/fp-go/v2/array"
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
N "github.com/IBM/fp-go/v2/number" N "github.com/IBM/fp-go/v2/number"
O "github.com/IBM/fp-go/v2/option" O "github.com/IBM/fp-go/v2/option"
R "github.com/IBM/fp-go/v2/record"
S "github.com/IBM/fp-go/v2/string" S "github.com/IBM/fp-go/v2/string"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -42,13 +44,13 @@ func toMap[K comparable, V any](seq Seq2[K, V]) map[K]V {
func TestOf(t *testing.T) { func TestOf(t *testing.T) {
seq := Of(42) seq := Of(42)
result := toSlice(seq) result := toSlice(seq)
assert.Equal(t, []int{42}, result) assert.Equal(t, A.Of(42), result)
} }
func TestOf2(t *testing.T) { func TestOf2(t *testing.T) {
seq := Of2("key", 100) seq := Of2("key", 100)
result := toMap(seq) result := toMap(seq)
assert.Equal(t, map[string]int{"key": 100}, result) assert.Equal(t, R.Of("key", 100), result)
} }
func TestFrom(t *testing.T) { func TestFrom(t *testing.T) {
@@ -587,3 +589,29 @@ func ExampleMonoid() {
fmt.Println(result) fmt.Println(result)
// Output: [1 2 3 4 5 6] // Output: [1 2 3 4 5 6]
} }
func TestMonadMapToArray(t *testing.T) {
seq := From(1, 2, 3)
result := MonadMapToArray(seq, N.Mul(2))
assert.Equal(t, []int{2, 4, 6}, result)
}
func TestMonadMapToArrayEmpty(t *testing.T) {
seq := Empty[int]()
result := MonadMapToArray(seq, N.Mul(2))
assert.Empty(t, result)
}
func TestMapToArray(t *testing.T) {
seq := From(1, 2, 3)
mapper := MapToArray(N.Mul(2))
result := mapper(seq)
assert.Equal(t, []int{2, 4, 6}, result)
}
func TestMapToArrayIdentity(t *testing.T) {
seq := From("a", "b", "c")
mapper := MapToArray(F.Identity[string])
result := mapper(seq)
assert.Equal(t, []string{"a", "b", "c"}, result)
}

85
v2/record/coverage.out Normal file
View File

@@ -0,0 +1,85 @@
mode: set
github.com/IBM/fp-go/v2/record/bind.go:33.40,35.2 1 0
github.com/IBM/fp-go/v2/record/bind.go:71.144,73.2 1 0
github.com/IBM/fp-go/v2/record/bind.go:79.27,81.2 1 0
github.com/IBM/fp-go/v2/record/bind.go:87.27,89.2 1 0
github.com/IBM/fp-go/v2/record/bind.go:92.80,94.2 1 0
github.com/IBM/fp-go/v2/record/bind.go:129.135,131.2 1 0
github.com/IBM/fp-go/v2/record/eq.go:23.55,25.2 1 0
github.com/IBM/fp-go/v2/record/eq.go:28.56,30.2 1 1
github.com/IBM/fp-go/v2/record/monoid.go:25.75,27.2 1 1
github.com/IBM/fp-go/v2/record/monoid.go:30.63,32.2 1 1
github.com/IBM/fp-go/v2/record/monoid.go:35.64,37.2 1 1
github.com/IBM/fp-go/v2/record/monoid.go:40.59,42.2 1 1
github.com/IBM/fp-go/v2/record/record.go:28.51,30.2 1 1
github.com/IBM/fp-go/v2/record/record.go:33.54,35.2 1 1
github.com/IBM/fp-go/v2/record/record.go:38.47,40.2 1 1
github.com/IBM/fp-go/v2/record/record.go:43.49,45.2 1 1
github.com/IBM/fp-go/v2/record/record.go:48.72,50.2 1 1
github.com/IBM/fp-go/v2/record/record.go:53.92,55.2 1 1
github.com/IBM/fp-go/v2/record/record.go:57.80,59.2 1 1
github.com/IBM/fp-go/v2/record/record.go:61.92,63.2 1 1
github.com/IBM/fp-go/v2/record/record.go:65.84,67.2 1 1
github.com/IBM/fp-go/v2/record/record.go:69.96,71.2 1 1
github.com/IBM/fp-go/v2/record/record.go:73.71,75.2 1 1
github.com/IBM/fp-go/v2/record/record.go:77.83,79.2 1 1
github.com/IBM/fp-go/v2/record/record.go:81.87,83.2 1 1
github.com/IBM/fp-go/v2/record/record.go:85.75,87.2 1 1
github.com/IBM/fp-go/v2/record/record.go:89.69,91.2 1 1
github.com/IBM/fp-go/v2/record/record.go:93.73,95.2 1 1
github.com/IBM/fp-go/v2/record/record.go:97.81,99.2 1 1
github.com/IBM/fp-go/v2/record/record.go:101.85,103.2 1 1
github.com/IBM/fp-go/v2/record/record.go:106.65,108.2 1 1
github.com/IBM/fp-go/v2/record/record.go:111.67,113.2 1 1
github.com/IBM/fp-go/v2/record/record.go:116.52,118.2 1 1
github.com/IBM/fp-go/v2/record/record.go:120.84,122.2 1 0
github.com/IBM/fp-go/v2/record/record.go:125.70,127.2 1 1
github.com/IBM/fp-go/v2/record/record.go:130.43,132.2 1 1
github.com/IBM/fp-go/v2/record/record.go:135.47,137.2 1 1
github.com/IBM/fp-go/v2/record/record.go:139.60,141.2 1 1
github.com/IBM/fp-go/v2/record/record.go:143.62,145.2 1 1
github.com/IBM/fp-go/v2/record/record.go:147.65,149.2 1 1
github.com/IBM/fp-go/v2/record/record.go:151.68,153.2 1 1
github.com/IBM/fp-go/v2/record/record.go:155.63,157.2 1 1
github.com/IBM/fp-go/v2/record/record.go:160.55,162.2 1 1
github.com/IBM/fp-go/v2/record/record.go:165.103,167.2 1 1
github.com/IBM/fp-go/v2/record/record.go:170.91,172.2 1 1
github.com/IBM/fp-go/v2/record/record.go:175.72,177.2 1 1
github.com/IBM/fp-go/v2/record/record.go:180.84,182.2 1 1
github.com/IBM/fp-go/v2/record/record.go:185.49,187.2 1 1
github.com/IBM/fp-go/v2/record/record.go:190.52,192.2 1 1
github.com/IBM/fp-go/v2/record/record.go:195.46,197.2 1 1
github.com/IBM/fp-go/v2/record/record.go:199.124,201.2 1 0
github.com/IBM/fp-go/v2/record/record.go:203.112,205.2 1 1
github.com/IBM/fp-go/v2/record/record.go:207.125,209.2 1 0
github.com/IBM/fp-go/v2/record/record.go:211.113,213.2 1 1
github.com/IBM/fp-go/v2/record/record.go:216.85,218.2 1 1
github.com/IBM/fp-go/v2/record/record.go:221.141,223.2 1 1
github.com/IBM/fp-go/v2/record/record.go:226.129,228.2 1 0
github.com/IBM/fp-go/v2/record/record.go:231.86,233.2 1 1
github.com/IBM/fp-go/v2/record/record.go:236.98,238.2 1 0
github.com/IBM/fp-go/v2/record/record.go:241.64,243.2 1 1
github.com/IBM/fp-go/v2/record/record.go:246.104,248.2 1 0
github.com/IBM/fp-go/v2/record/record.go:251.92,253.2 1 0
github.com/IBM/fp-go/v2/record/record.go:256.108,258.2 1 1
github.com/IBM/fp-go/v2/record/record.go:261.86,263.2 1 0
github.com/IBM/fp-go/v2/record/record.go:266.120,268.2 1 0
github.com/IBM/fp-go/v2/record/record.go:271.69,273.2 1 1
github.com/IBM/fp-go/v2/record/record.go:276.71,278.2 1 1
github.com/IBM/fp-go/v2/record/record.go:280.78,282.2 1 1
github.com/IBM/fp-go/v2/record/record.go:284.74,286.2 1 1
github.com/IBM/fp-go/v2/record/record.go:289.51,291.2 1 1
github.com/IBM/fp-go/v2/record/record.go:294.80,296.2 1 1
github.com/IBM/fp-go/v2/record/record.go:305.88,307.2 1 0
github.com/IBM/fp-go/v2/record/record.go:314.73,316.2 1 1
github.com/IBM/fp-go/v2/record/record.go:324.60,326.2 1 0
github.com/IBM/fp-go/v2/record/record.go:332.55,334.2 1 1
github.com/IBM/fp-go/v2/record/record.go:336.105,338.2 1 1
github.com/IBM/fp-go/v2/record/record.go:340.106,342.2 1 1
github.com/IBM/fp-go/v2/record/record.go:344.48,346.2 1 1
github.com/IBM/fp-go/v2/record/semigroup.go:53.81,55.2 1 1
github.com/IBM/fp-go/v2/record/semigroup.go:80.69,82.2 1 1
github.com/IBM/fp-go/v2/record/semigroup.go:107.70,109.2 1 1
github.com/IBM/fp-go/v2/record/traverse.go:27.41,29.2 1 1
github.com/IBM/fp-go/v2/record/traverse.go:39.38,41.2 1 1
github.com/IBM/fp-go/v2/record/traverse.go:50.23,53.2 1 1

View File

@@ -26,7 +26,7 @@ import (
Mo "github.com/IBM/fp-go/v2/monoid" Mo "github.com/IBM/fp-go/v2/monoid"
O "github.com/IBM/fp-go/v2/option" O "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/ord" "github.com/IBM/fp-go/v2/ord"
T "github.com/IBM/fp-go/v2/tuple" "github.com/IBM/fp-go/v2/pair"
) )
func IsEmpty[M ~map[K]V, K comparable, V any](r M) bool { func IsEmpty[M ~map[K]V, K comparable, V any](r M) bool {
@@ -59,9 +59,9 @@ func ValuesOrd[M ~map[K]V, GV ~[]V, K comparable, V any](o ord.Ord[K]) func(r M)
func collectOrd[M ~map[K]V, GR ~[]R, K comparable, V, R any](o ord.Ord[K], r M, f func(K, V) R) GR { func collectOrd[M ~map[K]V, GR ~[]R, K comparable, V, R any](o ord.Ord[K], r M, f func(K, V) R) GR {
// create the entries // create the entries
entries := toEntriesOrd[M, []T.Tuple2[K, V]](o, r) entries := toEntriesOrd[M, []pair.Pair[K, V]](o, r)
// collect this array // collect this array
ft := T.Tupled2(f) ft := pair.Paired(f)
count := len(entries) count := len(entries)
result := make(GR, count) result := make(GR, count)
for i := count - 1; i >= 0; i-- { for i := count - 1; i >= 0; i-- {
@@ -73,13 +73,13 @@ func collectOrd[M ~map[K]V, GR ~[]R, K comparable, V, R any](o ord.Ord[K], r M,
func reduceOrd[M ~map[K]V, K comparable, V, R any](o ord.Ord[K], r M, f func(K, R, V) R, initial R) R { func reduceOrd[M ~map[K]V, K comparable, V, R any](o ord.Ord[K], r M, f func(K, R, V) R, initial R) R {
// create the entries // create the entries
entries := toEntriesOrd[M, []T.Tuple2[K, V]](o, r) entries := toEntriesOrd[M, []pair.Pair[K, V]](o, r)
// collect this array // collect this array
current := initial current := initial
count := len(entries) count := len(entries)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
t := entries[i] t := entries[i]
current = f(T.First(t), current, T.Second(t)) current = f(pair.Head(t), current, pair.Tail(t))
} }
// done // done
return current return current
@@ -318,32 +318,32 @@ func Size[M ~map[K]V, K comparable, V any](r M) int {
return len(r) return len(r)
} }
func ToArray[M ~map[K]V, GT ~[]T.Tuple2[K, V], K comparable, V any](r M) GT { func ToArray[M ~map[K]V, GT ~[]pair.Pair[K, V], K comparable, V any](r M) GT {
return collect[M, GT](r, T.MakeTuple2[K, V]) return collect[M, GT](r, pair.MakePair[K, V])
} }
func toEntriesOrd[M ~map[K]V, GT ~[]T.Tuple2[K, V], K comparable, V any](o ord.Ord[K], r M) GT { func toEntriesOrd[M ~map[K]V, GT ~[]pair.Pair[K, V], K comparable, V any](o ord.Ord[K], r M) GT {
// total number of elements // total number of elements
count := len(r) count := len(r)
// produce an array that we can sort by key // produce an array that we can sort by key
entries := make(GT, count) entries := make(GT, count)
idx := 0 idx := 0
for k, v := range r { for k, v := range r {
entries[idx] = T.MakeTuple2(k, v) entries[idx] = pair.MakePair(k, v)
idx++ idx++
} }
sort.Slice(entries, func(i, j int) bool { sort.Slice(entries, func(i, j int) bool {
return o.Compare(T.First(entries[i]), T.First(entries[j])) < 0 return o.Compare(pair.Head(entries[i]), pair.Head(entries[j])) < 0
}) })
// final entries // final entries
return entries return entries
} }
func ToEntriesOrd[M ~map[K]V, GT ~[]T.Tuple2[K, V], K comparable, V any](o ord.Ord[K]) func(r M) GT { func ToEntriesOrd[M ~map[K]V, GT ~[]pair.Pair[K, V], K comparable, V any](o ord.Ord[K]) func(r M) GT {
return F.Bind1st(toEntriesOrd[M, GT, K, V], o) return F.Bind1st(toEntriesOrd[M, GT, K, V], o)
} }
func ToEntries[M ~map[K]V, GT ~[]T.Tuple2[K, V], K comparable, V any](r M) GT { func ToEntries[M ~map[K]V, GT ~[]pair.Pair[K, V], K comparable, V any](r M) GT {
return ToArray[M, GT](r) return ToArray[M, GT](r)
} }
@@ -351,7 +351,7 @@ func ToEntries[M ~map[K]V, GT ~[]T.Tuple2[K, V], K comparable, V any](r M) GT {
// its values into a tuple. The key and value are then used to populate the map. Duplicate // its values into a tuple. The key and value are then used to populate the map. Duplicate
// values are resolved via the provided [Mg.Magma] // values are resolved via the provided [Mg.Magma]
func FromFoldableMap[ func FromFoldableMap[
FCT ~func(A) T.Tuple2[K, V], FCT ~func(A) pair.Pair[K, V],
HKTA any, HKTA any,
FOLDABLE ~func(func(M, A) M, M) func(HKTA) M, FOLDABLE ~func(func(M, A) M, M) func(HKTA) M,
M ~map[K]V, M ~map[K]V,
@@ -364,12 +364,12 @@ func FromFoldableMap[
dst = make(M) dst = make(M)
} }
e := f(a) e := f(a)
k := T.First(e) k := pair.Head(e)
old, ok := dst[k] old, ok := dst[k]
if ok { if ok {
dst[k] = m.Concat(old, T.Second(e)) dst[k] = m.Concat(old, pair.Tail(e))
} else { } else {
dst[k] = T.Second(e) dst[k] = pair.Tail(e)
} }
return dst return dst
}, Empty[M]()) }, Empty[M]())
@@ -378,15 +378,15 @@ func FromFoldableMap[
func FromFoldable[ func FromFoldable[
HKTA any, HKTA any,
FOLDABLE ~func(func(M, T.Tuple2[K, V]) M, M) func(HKTA) M, FOLDABLE ~func(func(M, pair.Pair[K, V]) M, M) func(HKTA) M,
M ~map[K]V, M ~map[K]V,
K comparable, K comparable,
V any](m Mg.Magma[V], red FOLDABLE) func(fa HKTA) M { V any](m Mg.Magma[V], red FOLDABLE) func(fa HKTA) M {
return FromFoldableMap[func(T.Tuple2[K, V]) T.Tuple2[K, V]](m, red)(F.Identity[T.Tuple2[K, V]]) return FromFoldableMap[func(pair.Pair[K, V]) pair.Pair[K, V]](m, red)(F.Identity[pair.Pair[K, V]])
} }
func FromArrayMap[ func FromArrayMap[
FCT ~func(A) T.Tuple2[K, V], FCT ~func(A) pair.Pair[K, V],
GA ~[]A, GA ~[]A,
M ~map[K]V, M ~map[K]V,
A any, A any,
@@ -396,17 +396,17 @@ func FromArrayMap[
} }
func FromArray[ func FromArray[
GA ~[]T.Tuple2[K, V], GA ~[]pair.Pair[K, V],
M ~map[K]V, M ~map[K]V,
K comparable, K comparable,
V any](m Mg.Magma[V]) func(fa GA) M { V any](m Mg.Magma[V]) func(fa GA) M {
return FromFoldable(m, F.Bind23of3(RAG.Reduce[GA, T.Tuple2[K, V], M])) return FromFoldable(m, F.Bind23of3(RAG.Reduce[GA, pair.Pair[K, V], M]))
} }
func FromEntries[M ~map[K]V, GT ~[]T.Tuple2[K, V], K comparable, V any](fa GT) M { func FromEntries[M ~map[K]V, GT ~[]pair.Pair[K, V], K comparable, V any](fa GT) M {
m := make(M) m := make(M)
for _, t := range fa { for _, t := range fa {
upsertAtReadWrite(m, t.F1, t.F2) upsertAtReadWrite(m, pair.Head(t), pair.Tail(t))
} }
return m return m
} }

View File

@@ -22,7 +22,6 @@ import (
O "github.com/IBM/fp-go/v2/option" O "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/ord" "github.com/IBM/fp-go/v2/ord"
G "github.com/IBM/fp-go/v2/record/generic" G "github.com/IBM/fp-go/v2/record/generic"
T "github.com/IBM/fp-go/v2/tuple"
) )
// IsEmpty tests if a map is empty // IsEmpty tests if a map is empty
@@ -55,51 +54,63 @@ func CollectOrd[V, R any, K comparable](o ord.Ord[K]) func(func(K, V) R) func(ma
return G.CollectOrd[map[K]V, []R](o) return G.CollectOrd[map[K]V, []R](o)
} }
// Reduce reduces a map to a single value by applying a reducer function to each value
func Reduce[K comparable, V, R any](f func(R, V) R, initial R) func(map[K]V) R { func Reduce[K comparable, V, R any](f func(R, V) R, initial R) func(map[K]V) R {
return G.Reduce[map[K]V](f, initial) return G.Reduce[map[K]V](f, initial)
} }
// ReduceWithIndex reduces a map to a single value by applying a reducer function to each key-value pair
func ReduceWithIndex[K comparable, V, R any](f func(K, R, V) R, initial R) func(map[K]V) R { func ReduceWithIndex[K comparable, V, R any](f func(K, R, V) R, initial R) func(map[K]V) R {
return G.ReduceWithIndex[map[K]V](f, initial) return G.ReduceWithIndex[map[K]V](f, initial)
} }
// ReduceRef reduces a map to a single value by applying a reducer function to each value reference
func ReduceRef[K comparable, V, R any](f func(R, *V) R, initial R) func(map[K]V) R { func ReduceRef[K comparable, V, R any](f func(R, *V) R, initial R) func(map[K]V) R {
return G.ReduceRef[map[K]V](f, initial) return G.ReduceRef[map[K]V](f, initial)
} }
// ReduceRefWithIndex reduces a map to a single value by applying a reducer function to each key-value pair with value references
func ReduceRefWithIndex[K comparable, V, R any](f func(K, R, *V) R, initial R) func(map[K]V) R { func ReduceRefWithIndex[K comparable, V, R any](f func(K, R, *V) R, initial R) func(map[K]V) R {
return G.ReduceRefWithIndex[map[K]V](f, initial) return G.ReduceRefWithIndex[map[K]V](f, initial)
} }
// MonadMap transforms each value in a map using the provided function
func MonadMap[K comparable, V, R any](r map[K]V, f func(V) R) map[K]R { func MonadMap[K comparable, V, R any](r map[K]V, f func(V) R) map[K]R {
return G.MonadMap[map[K]V, map[K]R](r, f) return G.MonadMap[map[K]V, map[K]R](r, f)
} }
// MonadMapWithIndex transforms each key-value pair in a map using the provided function
func MonadMapWithIndex[K comparable, V, R any](r map[K]V, f func(K, V) R) map[K]R { func MonadMapWithIndex[K comparable, V, R any](r map[K]V, f func(K, V) R) map[K]R {
return G.MonadMapWithIndex[map[K]V, map[K]R](r, f) return G.MonadMapWithIndex[map[K]V, map[K]R](r, f)
} }
// MonadMapRefWithIndex transforms each key-value pair in a map using the provided function with value references
func MonadMapRefWithIndex[K comparable, V, R any](r map[K]V, f func(K, *V) R) map[K]R { func MonadMapRefWithIndex[K comparable, V, R any](r map[K]V, f func(K, *V) R) map[K]R {
return G.MonadMapRefWithIndex[map[K]V, map[K]R](r, f) return G.MonadMapRefWithIndex[map[K]V, map[K]R](r, f)
} }
// MonadMapRef transforms each value in a map using the provided function with value references
func MonadMapRef[K comparable, V, R any](r map[K]V, f func(*V) R) map[K]R { func MonadMapRef[K comparable, V, R any](r map[K]V, f func(*V) R) map[K]R {
return G.MonadMapRef[map[K]V, map[K]R](r, f) return G.MonadMapRef[map[K]V, map[K]R](r, f)
} }
func Map[K comparable, V, R any](f func(V) R) func(map[K]V) map[K]R { // Map returns a function that transforms each value in a map using the provided function
func Map[K comparable, V, R any](f func(V) R) Operator[K, V, R] {
return G.Map[map[K]V, map[K]R](f) return G.Map[map[K]V, map[K]R](f)
} }
func MapRef[K comparable, V, R any](f func(*V) R) func(map[K]V) map[K]R { // MapRef returns a function that transforms each value in a map using the provided function with value references
func MapRef[K comparable, V, R any](f func(*V) R) Operator[K, V, R] {
return G.MapRef[map[K]V, map[K]R](f) return G.MapRef[map[K]V, map[K]R](f)
} }
func MapWithIndex[K comparable, V, R any](f func(K, V) R) func(map[K]V) map[K]R { // MapWithIndex returns a function that transforms each key-value pair in a map using the provided function
func MapWithIndex[K comparable, V, R any](f func(K, V) R) Operator[K, V, R] {
return G.MapWithIndex[map[K]V, map[K]R](f) return G.MapWithIndex[map[K]V, map[K]R](f)
} }
func MapRefWithIndex[K comparable, V, R any](f func(K, *V) R) func(map[K]V) map[K]R { // MapRefWithIndex returns a function that transforms each key-value pair in a map using the provided function with value references
func MapRefWithIndex[K comparable, V, R any](f func(K, *V) R) Operator[K, V, R] {
return G.MapRefWithIndex[map[K]V, map[K]R](f) return G.MapRefWithIndex[map[K]V, map[K]R](f)
} }
@@ -118,12 +129,13 @@ func Has[K comparable, V any](k K, r map[K]V) bool {
return G.Has(k, r) return G.Has(k, r)
} }
func Union[K comparable, V any](m Mg.Magma[V]) func(map[K]V) func(map[K]V) map[K]V { // Union combines two maps using the provided Magma to resolve conflicts for duplicate keys
func Union[K comparable, V any](m Mg.Magma[V]) func(map[K]V) Operator[K, V, V] {
return G.Union[map[K]V](m) return G.Union[map[K]V](m)
} }
// Merge combines two maps giving the values in the right one precedence. Also refer to [MergeMonoid] // Merge combines two maps giving the values in the right one precedence. Also refer to [MergeMonoid]
func Merge[K comparable, V any](right map[K]V) func(map[K]V) map[K]V { func Merge[K comparable, V any](right map[K]V) Operator[K, V, V] {
return G.Merge(right) return G.Merge(right)
} }
@@ -137,23 +149,28 @@ func Size[K comparable, V any](r map[K]V) int {
return G.Size(r) return G.Size(r)
} }
func ToArray[K comparable, V any](r map[K]V) []T.Tuple2[K, V] { // ToArray converts a map to an array of key-value pairs
return G.ToArray[map[K]V, []T.Tuple2[K, V]](r) func ToArray[K comparable, V any](r map[K]V) Entries[K, V] {
return G.ToArray[map[K]V, Entries[K, V]](r)
} }
func ToEntries[K comparable, V any](r map[K]V) []T.Tuple2[K, V] { // ToEntries converts a map to an array of key-value pairs (alias for ToArray)
return G.ToEntries[map[K]V, []T.Tuple2[K, V]](r) func ToEntries[K comparable, V any](r map[K]V) Entries[K, V] {
return G.ToEntries[map[K]V, Entries[K, V]](r)
} }
func FromEntries[K comparable, V any](fa []T.Tuple2[K, V]) map[K]V { // FromEntries creates a map from an array of key-value pairs
func FromEntries[K comparable, V any](fa Entries[K, V]) map[K]V {
return G.FromEntries[map[K]V](fa) return G.FromEntries[map[K]V](fa)
} }
func UpsertAt[K comparable, V any](k K, v V) func(map[K]V) map[K]V { // UpsertAt returns a function that inserts or updates a key-value pair in a map
func UpsertAt[K comparable, V any](k K, v V) Operator[K, V, V] {
return G.UpsertAt[map[K]V](k, v) return G.UpsertAt[map[K]V](k, v)
} }
func DeleteAt[K comparable, V any](k K) func(map[K]V) map[K]V { // DeleteAt returns a function that removes a key from a map
func DeleteAt[K comparable, V any](k K) Operator[K, V, V] {
return G.DeleteAt[map[K]V](k) return G.DeleteAt[map[K]V](k)
} }
@@ -163,22 +180,22 @@ func Singleton[K comparable, V any](k K, v V) map[K]V {
} }
// FilterMapWithIndex creates a new map with only the elements for which the transformation function creates a Some // FilterMapWithIndex creates a new map with only the elements for which the transformation function creates a Some
func FilterMapWithIndex[K comparable, V1, V2 any](f func(K, V1) O.Option[V2]) func(map[K]V1) map[K]V2 { func FilterMapWithIndex[K comparable, V1, V2 any](f func(K, V1) O.Option[V2]) Operator[K, V1, V2] {
return G.FilterMapWithIndex[map[K]V1, map[K]V2](f) return G.FilterMapWithIndex[map[K]V1, map[K]V2](f)
} }
// FilterMap creates a new map with only the elements for which the transformation function creates a Some // FilterMap creates a new map with only the elements for which the transformation function creates a Some
func FilterMap[K comparable, V1, V2 any](f func(V1) O.Option[V2]) func(map[K]V1) map[K]V2 { func FilterMap[K comparable, V1, V2 any](f func(V1) O.Option[V2]) Operator[K, V1, V2] {
return G.FilterMap[map[K]V1, map[K]V2](f) return G.FilterMap[map[K]V1, map[K]V2](f)
} }
// Filter creates a new map with only the elements that match the predicate // Filter creates a new map with only the elements that match the predicate
func Filter[K comparable, V any](f func(K) bool) func(map[K]V) map[K]V { func Filter[K comparable, V any](f func(K) bool) Operator[K, V, V] {
return G.Filter[map[K]V](f) return G.Filter[map[K]V](f)
} }
// FilterWithIndex creates a new map with only the elements that match the predicate // FilterWithIndex creates a new map with only the elements that match the predicate
func FilterWithIndex[K comparable, V any](f func(K, V) bool) func(map[K]V) map[K]V { func FilterWithIndex[K comparable, V any](f func(K, V) bool) Operator[K, V, V] {
return G.FilterWithIndex[map[K]V](f) return G.FilterWithIndex[map[K]V](f)
} }
@@ -197,19 +214,23 @@ func ConstNil[K comparable, V any]() map[K]V {
return map[K]V(nil) return map[K]V(nil)
} }
// MonadChainWithIndex chains a map transformation function that produces maps, combining results using the provided Monoid
func MonadChainWithIndex[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2], r map[K]V1, f func(K, V1) map[K]V2) map[K]V2 { func MonadChainWithIndex[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2], r map[K]V1, f func(K, V1) map[K]V2) map[K]V2 {
return G.MonadChainWithIndex(m, r, f) return G.MonadChainWithIndex(m, r, f)
} }
// MonadChain chains a map transformation function that produces maps, combining results using the provided Monoid
func MonadChain[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2], r map[K]V1, f func(V1) map[K]V2) map[K]V2 { func MonadChain[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2], r map[K]V1, f func(V1) map[K]V2) map[K]V2 {
return G.MonadChain(m, r, f) return G.MonadChain(m, r, f)
} }
func ChainWithIndex[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2]) func(func(K, V1) map[K]V2) func(map[K]V1) map[K]V2 { // ChainWithIndex returns a function that chains a map transformation function that produces maps, combining results using the provided Monoid
func ChainWithIndex[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2]) func(func(K, V1) map[K]V2) Operator[K, V1, V2] {
return G.ChainWithIndex[map[K]V1](m) return G.ChainWithIndex[map[K]V1](m)
} }
func Chain[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2]) func(func(V1) map[K]V2) func(map[K]V1) map[K]V2 { // Chain returns a function that chains a map transformation function that produces maps, combining results using the provided Monoid
func Chain[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2]) func(func(V1) map[K]V2) Operator[K, V1, V2] {
return G.Chain[map[K]V1](m) return G.Chain[map[K]V1](m)
} }
@@ -219,12 +240,12 @@ func Flatten[K comparable, V any](m Mo.Monoid[map[K]V]) func(map[K]map[K]V) map[
} }
// FilterChainWithIndex creates a new map with only the elements for which the transformation function creates a Some // FilterChainWithIndex creates a new map with only the elements for which the transformation function creates a Some
func FilterChainWithIndex[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2]) func(func(K, V1) O.Option[map[K]V2]) func(map[K]V1) map[K]V2 { func FilterChainWithIndex[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2]) func(func(K, V1) O.Option[map[K]V2]) Operator[K, V1, V2] {
return G.FilterChainWithIndex[map[K]V1](m) return G.FilterChainWithIndex[map[K]V1](m)
} }
// FilterChain creates a new map with only the elements for which the transformation function creates a Some // FilterChain creates a new map with only the elements for which the transformation function creates a Some
func FilterChain[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2]) func(func(V1) O.Option[map[K]V2]) func(map[K]V1) map[K]V2 { func FilterChain[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2]) func(func(V1) O.Option[map[K]V2]) Operator[K, V1, V2] {
return G.FilterChain[map[K]V1](m) return G.FilterChain[map[K]V1](m)
} }
@@ -278,10 +299,12 @@ func ValuesOrd[V any, K comparable](o ord.Ord[K]) func(r map[K]V) []V {
return G.ValuesOrd[map[K]V, []V](o) return G.ValuesOrd[map[K]V, []V](o)
} }
// MonadFlap applies a value to a map of functions, producing a map of results
func MonadFlap[B any, K comparable, A any](fab map[K]func(A) B, a A) map[K]B { func MonadFlap[B any, K comparable, A any](fab map[K]func(A) B, a A) map[K]B {
return G.MonadFlap[map[K]func(A) B, map[K]B](fab, a) return G.MonadFlap[map[K]func(A) B, map[K]B](fab, a)
} }
// Flap returns a function that applies a value to a map of functions, producing a map of results
func Flap[B any, K comparable, A any](a A) func(map[K]func(A) B) map[K]B { func Flap[B any, K comparable, A any](a A) func(map[K]func(A) B) map[K]B {
return G.Flap[map[K]func(A) B, map[K]B](a) return G.Flap[map[K]func(A) B, map[K]B](a)
} }
@@ -303,8 +326,8 @@ func FromFoldableMap[
A any, A any,
HKTA any, HKTA any,
K comparable, K comparable,
V any](m Mg.Magma[V], red FOLDABLE) func(f func(A) T.Tuple2[K, V]) func(fa HKTA) map[K]V { V any](m Mg.Magma[V], red FOLDABLE) func(f func(A) Entry[K, V]) func(fa HKTA) map[K]V {
return G.FromFoldableMap[func(A) T.Tuple2[K, V]](m, red) return G.FromFoldableMap[func(A) Entry[K, V]](m, red)
} }
// FromArrayMap converts from an array to a map // FromArrayMap converts from an array to a map
@@ -312,15 +335,15 @@ func FromFoldableMap[
func FromArrayMap[ func FromArrayMap[
A any, A any,
K comparable, K comparable,
V any](m Mg.Magma[V]) func(f func(A) T.Tuple2[K, V]) func(fa []A) map[K]V { V any](m Mg.Magma[V]) func(f func(A) Entry[K, V]) func(fa []A) map[K]V {
return G.FromArrayMap[func(A) T.Tuple2[K, V], []A, map[K]V](m) return G.FromArrayMap[func(A) Entry[K, V], []A, map[K]V](m)
} }
// FromFoldable converts from a reducer to a map // FromFoldable converts from a reducer to a map
// Duplicate keys are resolved by the provided [Mg.Magma] // Duplicate keys are resolved by the provided [Mg.Magma]
func FromFoldable[ func FromFoldable[
HKTA any, HKTA any,
FOLDABLE ~func(func(map[K]V, T.Tuple2[K, V]) map[K]V, map[K]V) func(HKTA) map[K]V, // the reduce function FOLDABLE ~func(func(map[K]V, Entry[K, V]) map[K]V, map[K]V) func(HKTA) map[K]V, // the reduce function
K comparable, K comparable,
V any](m Mg.Magma[V], red FOLDABLE) func(fa HKTA) map[K]V { V any](m Mg.Magma[V], red FOLDABLE) func(fa HKTA) map[K]V {
return G.FromFoldable(m, red) return G.FromFoldable(m, red)
@@ -330,14 +353,21 @@ func FromFoldable[
// Duplicate keys are resolved by the provided [Mg.Magma] // Duplicate keys are resolved by the provided [Mg.Magma]
func FromArray[ func FromArray[
K comparable, K comparable,
V any](m Mg.Magma[V]) func(fa []T.Tuple2[K, V]) map[K]V { V any](m Mg.Magma[V]) func(fa Entries[K, V]) map[K]V {
return G.FromArray[[]T.Tuple2[K, V], map[K]V](m) return G.FromArray[Entries[K, V], map[K]V](m)
} }
// MonadAp applies a map of functions to a map of values, combining results using the provided Monoid
func MonadAp[A any, K comparable, B any](m Mo.Monoid[map[K]B], fab map[K]func(A) B, fa map[K]A) map[K]B { func MonadAp[A any, K comparable, B any](m Mo.Monoid[map[K]B], fab map[K]func(A) B, fa map[K]A) map[K]B {
return G.MonadAp(m, fab, fa) return G.MonadAp(m, fab, fa)
} }
// Ap returns a function that applies a map of functions to a map of values, combining results using the provided Monoid
func Ap[A any, K comparable, B any](m Mo.Monoid[map[K]B]) func(fa map[K]A) func(map[K]func(A) B) map[K]B { func Ap[A any, K comparable, B any](m Mo.Monoid[map[K]B]) func(fa map[K]A) func(map[K]func(A) B) map[K]B {
return G.Ap[map[K]B, map[K]func(A) B, map[K]A](m) return G.Ap[map[K]B, map[K]func(A) B, map[K]A](m)
} }
// Of creates a map with a single key-value pair
func Of[K comparable, A any](k K, a A) map[K]A {
return map[K]A{k: a}
}

View File

@@ -25,8 +25,8 @@ import (
"github.com/IBM/fp-go/v2/internal/utils" "github.com/IBM/fp-go/v2/internal/utils"
Mg "github.com/IBM/fp-go/v2/magma" Mg "github.com/IBM/fp-go/v2/magma"
O "github.com/IBM/fp-go/v2/option" O "github.com/IBM/fp-go/v2/option"
P "github.com/IBM/fp-go/v2/pair"
S "github.com/IBM/fp-go/v2/string" S "github.com/IBM/fp-go/v2/string"
T "github.com/IBM/fp-go/v2/tuple"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -156,7 +156,7 @@ func TestFromArrayMap(t *testing.T) {
src1 := A.From("a", "b", "c", "a") src1 := A.From("a", "b", "c", "a")
frm := FromArrayMap[string, string](Mg.Second[string]()) frm := FromArrayMap[string, string](Mg.Second[string]())
f := frm(T.Replicate2[string]) f := frm(P.Of[string])
res1 := f(src1) res1 := f(src1)
@@ -198,3 +198,555 @@ func TestHas(t *testing.T) {
assert.True(t, Has("a", nonEmpty)) assert.True(t, Has("a", nonEmpty))
assert.False(t, Has("c", nonEmpty)) assert.False(t, Has("c", nonEmpty))
} }
func TestCollect(t *testing.T) {
data := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
collector := Collect[string, int, string](func(k string, v int) string {
return fmt.Sprintf("%s=%d", k, v)
})
result := collector(data)
sort.Strings(result)
assert.Equal(t, []string{"a=1", "b=2", "c=3"}, result)
}
func TestCollectOrd(t *testing.T) {
data := map[string]int{
"c": 3,
"a": 1,
"b": 2,
}
collector := CollectOrd[int, string](S.Ord)(func(k string, v int) string {
return fmt.Sprintf("%s=%d", k, v)
})
result := collector(data)
assert.Equal(t, []string{"a=1", "b=2", "c=3"}, result)
}
func TestReduce(t *testing.T) {
data := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
sum := Reduce[string, int, int](func(acc, v int) int {
return acc + v
}, 0)
result := sum(data)
assert.Equal(t, 6, result)
}
func TestReduceWithIndex(t *testing.T) {
data := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
concat := ReduceWithIndex[string, int, string](func(k string, acc string, v int) string {
if acc == "" {
return fmt.Sprintf("%s:%d", k, v)
}
return fmt.Sprintf("%s,%s:%d", acc, k, v)
}, "")
result := concat(data)
// Result order is non-deterministic, so check it contains all parts
assert.Contains(t, result, "a:1")
assert.Contains(t, result, "b:2")
assert.Contains(t, result, "c:3")
}
func TestMonadMap(t *testing.T) {
data := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
result := MonadMap(data, func(v int) int { return v * 2 })
assert.Equal(t, map[string]int{"a": 2, "b": 4, "c": 6}, result)
}
func TestMonadMapWithIndex(t *testing.T) {
data := map[string]int{
"a": 1,
"b": 2,
}
result := MonadMapWithIndex(data, func(k string, v int) string {
return fmt.Sprintf("%s=%d", k, v)
})
assert.Equal(t, map[string]string{"a": "a=1", "b": "b=2"}, result)
}
func TestMapWithIndex(t *testing.T) {
data := map[string]int{
"a": 1,
"b": 2,
}
mapper := MapWithIndex[string, int, string](func(k string, v int) string {
return fmt.Sprintf("%s=%d", k, v)
})
result := mapper(data)
assert.Equal(t, map[string]string{"a": "a=1", "b": "b=2"}, result)
}
func TestMonadLookup(t *testing.T) {
data := map[string]int{
"a": 1,
"b": 2,
}
assert.Equal(t, O.Some(1), MonadLookup(data, "a"))
assert.Equal(t, O.None[int](), MonadLookup(data, "c"))
}
func TestMerge(t *testing.T) {
left := map[string]int{"a": 1, "b": 2}
right := map[string]int{"b": 3, "c": 4}
result := Merge(right)(left)
assert.Equal(t, map[string]int{"a": 1, "b": 3, "c": 4}, result)
}
func TestSize(t *testing.T) {
data := map[string]int{"a": 1, "b": 2, "c": 3}
assert.Equal(t, 3, Size(data))
assert.Equal(t, 0, Size(Empty[string, int]()))
}
func TestToArray(t *testing.T) {
data := map[string]int{"a": 1, "b": 2}
result := ToArray(data)
assert.Len(t, result, 2)
// Check both entries exist (order is non-deterministic)
found := make(map[string]int)
for _, entry := range result {
found[P.Head(entry)] = P.Tail(entry)
}
assert.Equal(t, data, found)
}
func TestToEntries(t *testing.T) {
data := map[string]int{"a": 1, "b": 2}
result := ToEntries(data)
assert.Len(t, result, 2)
}
func TestFromEntries(t *testing.T) {
entries := Entries[string, int]{
P.MakePair("a", 1),
P.MakePair("b", 2),
}
result := FromEntries(entries)
assert.Equal(t, map[string]int{"a": 1, "b": 2}, result)
}
func TestUpsertAt(t *testing.T) {
data := map[string]int{"a": 1, "b": 2}
result := UpsertAt("c", 3)(data)
assert.Equal(t, map[string]int{"a": 1, "b": 2, "c": 3}, result)
// Original should be unchanged
assert.Equal(t, map[string]int{"a": 1, "b": 2}, data)
// Update existing
result2 := UpsertAt("a", 10)(data)
assert.Equal(t, map[string]int{"a": 10, "b": 2}, result2)
}
func TestDeleteAt(t *testing.T) {
data := map[string]int{"a": 1, "b": 2, "c": 3}
result := DeleteAt[string, int]("b")(data)
assert.Equal(t, map[string]int{"a": 1, "c": 3}, result)
// Original should be unchanged
assert.Equal(t, map[string]int{"a": 1, "b": 2, "c": 3}, data)
}
func TestSingleton(t *testing.T) {
result := Singleton("key", 42)
assert.Equal(t, map[string]int{"key": 42}, result)
}
func TestFilterMapWithIndex(t *testing.T) {
data := map[string]int{"a": 1, "b": 2, "c": 3}
filter := FilterMapWithIndex[string, int, int](func(k string, v int) O.Option[int] {
if v%2 == 0 {
return O.Some(v * 10)
}
return O.None[int]()
})
result := filter(data)
assert.Equal(t, map[string]int{"b": 20}, result)
}
func TestFilterMap(t *testing.T) {
data := map[string]int{"a": 1, "b": 2, "c": 3}
filter := FilterMap[string, int, int](func(v int) O.Option[int] {
if v%2 == 0 {
return O.Some(v * 10)
}
return O.None[int]()
})
result := filter(data)
assert.Equal(t, map[string]int{"b": 20}, result)
}
func TestFilter(t *testing.T) {
data := map[string]int{"a": 1, "b": 2, "c": 3}
filter := Filter[string, int](func(k string) bool {
return k != "b"
})
result := filter(data)
assert.Equal(t, map[string]int{"a": 1, "c": 3}, result)
}
func TestFilterWithIndex(t *testing.T) {
data := map[string]int{"a": 1, "b": 2, "c": 3}
filter := FilterWithIndex[string, int](func(k string, v int) bool {
return v%2 == 0
})
result := filter(data)
assert.Equal(t, map[string]int{"b": 2}, result)
}
func TestIsNil(t *testing.T) {
var nilMap map[string]int
nonNilMap := map[string]int{}
assert.True(t, IsNil(nilMap))
assert.False(t, IsNil(nonNilMap))
}
func TestIsNonNil(t *testing.T) {
var nilMap map[string]int
nonNilMap := map[string]int{}
assert.False(t, IsNonNil(nilMap))
assert.True(t, IsNonNil(nonNilMap))
}
func TestConstNil(t *testing.T) {
result := ConstNil[string, int]()
assert.Nil(t, result)
assert.True(t, IsNil(result))
}
func TestMonadChain(t *testing.T) {
data := map[string]int{"a": 1, "b": 2}
monoid := MergeMonoid[string, int]()
result := MonadChain(monoid, data, func(v int) map[string]int {
return map[string]int{
fmt.Sprintf("x%d", v): v * 10,
}
})
assert.Equal(t, map[string]int{"x1": 10, "x2": 20}, result)
}
func TestChain(t *testing.T) {
data := map[string]int{"a": 1, "b": 2}
monoid := MergeMonoid[string, int]()
chain := Chain[int, string, int](monoid)(func(v int) map[string]int {
return map[string]int{
fmt.Sprintf("x%d", v): v * 10,
}
})
result := chain(data)
assert.Equal(t, map[string]int{"x1": 10, "x2": 20}, result)
}
func TestFlatten(t *testing.T) {
nested := map[string]map[string]int{
"a": {"x": 1, "y": 2},
"b": {"z": 3},
}
monoid := MergeMonoid[string, int]()
flatten := Flatten(monoid)
result := flatten(nested)
assert.Equal(t, map[string]int{"x": 1, "y": 2, "z": 3}, result)
}
func TestFoldMap(t *testing.T) {
data := map[string]int{"a": 1, "b": 2, "c": 3}
// Use string monoid for simplicity
fold := FoldMap[string, int, string](S.Monoid)(func(v int) string {
return fmt.Sprintf("%d", v)
})
result := fold(data)
// Result contains all digits but order is non-deterministic
assert.Contains(t, result, "1")
assert.Contains(t, result, "2")
assert.Contains(t, result, "3")
}
func TestFold(t *testing.T) {
data := map[string]string{"a": "A", "b": "B", "c": "C"}
fold := Fold[string](S.Monoid)
result := fold(data)
// Result contains all letters but order is non-deterministic
assert.Contains(t, result, "A")
assert.Contains(t, result, "B")
assert.Contains(t, result, "C")
}
func TestKeysOrd(t *testing.T) {
data := map[string]int{"c": 3, "a": 1, "b": 2}
keys := KeysOrd[int](S.Ord)(data)
assert.Equal(t, []string{"a", "b", "c"}, keys)
}
func TestMonadFlap(t *testing.T) {
fns := map[string]func(int) int{
"double": func(x int) int { return x * 2 },
"triple": func(x int) int { return x * 3 },
}
result := MonadFlap(fns, 5)
assert.Equal(t, map[string]int{"double": 10, "triple": 15}, result)
}
func TestFlap(t *testing.T) {
fns := map[string]func(int) int{
"double": func(x int) int { return x * 2 },
"triple": func(x int) int { return x * 3 },
}
flap := Flap[int, string, int](5)
result := flap(fns)
assert.Equal(t, map[string]int{"double": 10, "triple": 15}, result)
}
func TestFromArray(t *testing.T) {
entries := Entries[string, int]{
P.MakePair("a", 1),
P.MakePair("b", 2),
P.MakePair("a", 3), // Duplicate key
}
// Use Second magma to keep last value
from := FromArray[string, int](Mg.Second[int]())
result := from(entries)
assert.Equal(t, map[string]int{"a": 3, "b": 2}, result)
}
func TestMonadAp(t *testing.T) {
fns := map[string]func(int) int{
"double": func(x int) int { return x * 2 },
}
vals := map[string]int{
"double": 5,
}
monoid := MergeMonoid[string, int]()
result := MonadAp(monoid, fns, vals)
assert.Equal(t, map[string]int{"double": 10}, result)
}
func TestAp(t *testing.T) {
fns := map[string]func(int) int{
"double": func(x int) int { return x * 2 },
}
vals := map[string]int{
"double": 5,
}
monoid := MergeMonoid[string, int]()
ap := Ap[int, string, int](monoid)(vals)
result := ap(fns)
assert.Equal(t, map[string]int{"double": 10}, result)
}
func TestOf(t *testing.T) {
result := Of("key", 42)
assert.Equal(t, map[string]int{"key": 42}, result)
}
func TestReduceRef(t *testing.T) {
data := map[string]int{"a": 1, "b": 2, "c": 3}
sum := ReduceRef[string, int, int](func(acc int, v *int) int {
return acc + *v
}, 0)
result := sum(data)
assert.Equal(t, 6, result)
}
func TestReduceRefWithIndex(t *testing.T) {
data := map[string]int{"a": 1, "b": 2}
concat := ReduceRefWithIndex[string, int, string](func(k string, acc string, v *int) string {
if acc == "" {
return fmt.Sprintf("%s:%d", k, *v)
}
return fmt.Sprintf("%s,%s:%d", acc, k, *v)
}, "")
result := concat(data)
assert.Contains(t, result, "a:1")
assert.Contains(t, result, "b:2")
}
func TestMonadMapRef(t *testing.T) {
data := map[string]int{"a": 1, "b": 2}
result := MonadMapRef(data, func(v *int) int { return *v * 2 })
assert.Equal(t, map[string]int{"a": 2, "b": 4}, result)
}
func TestMapRef(t *testing.T) {
data := map[string]int{"a": 1, "b": 2}
mapper := MapRef[string, int, int](func(v *int) int { return *v * 2 })
result := mapper(data)
assert.Equal(t, map[string]int{"a": 2, "b": 4}, result)
}
func TestMonadMapRefWithIndex(t *testing.T) {
data := map[string]int{"a": 1, "b": 2}
result := MonadMapRefWithIndex(data, func(k string, v *int) string {
return fmt.Sprintf("%s=%d", k, *v)
})
assert.Equal(t, map[string]string{"a": "a=1", "b": "b=2"}, result)
}
func TestMapRefWithIndex(t *testing.T) {
data := map[string]int{"a": 1, "b": 2}
mapper := MapRefWithIndex[string, int, string](func(k string, v *int) string {
return fmt.Sprintf("%s=%d", k, *v)
})
result := mapper(data)
assert.Equal(t, map[string]string{"a": "a=1", "b": "b=2"}, result)
}
func TestUnion(t *testing.T) {
left := map[string]int{"a": 1, "b": 2}
right := map[string]int{"b": 3, "c": 4}
// Union combines maps, with the magma resolving conflicts
// The order is union(left)(right), which means right is merged into left
// First magma keeps the first value (from right in this case)
union := Union[string, int](Mg.First[int]())
result := union(left)(right)
assert.Equal(t, map[string]int{"a": 1, "b": 3, "c": 4}, result)
// Second magma keeps the second value (from left in this case)
union2 := Union[string, int](Mg.Second[int]())
result2 := union2(left)(right)
assert.Equal(t, map[string]int{"a": 1, "b": 2, "c": 4}, result2)
}
func TestMonadChainWithIndex(t *testing.T) {
data := map[string]int{"a": 1, "b": 2}
monoid := MergeMonoid[string, string]()
result := MonadChainWithIndex(monoid, data, func(k string, v int) map[string]string {
return map[string]string{
fmt.Sprintf("%s%d", k, v): fmt.Sprintf("val%d", v),
}
})
assert.Equal(t, map[string]string{"a1": "val1", "b2": "val2"}, result)
}
func TestChainWithIndex(t *testing.T) {
data := map[string]int{"a": 1, "b": 2}
monoid := MergeMonoid[string, string]()
chain := ChainWithIndex[int, string, string](monoid)(func(k string, v int) map[string]string {
return map[string]string{
fmt.Sprintf("%s%d", k, v): fmt.Sprintf("val%d", v),
}
})
result := chain(data)
assert.Equal(t, map[string]string{"a1": "val1", "b2": "val2"}, result)
}
func TestFilterChainWithIndex(t *testing.T) {
src := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
f := func(k string, value int) O.Option[map[string]string] {
if value%2 != 0 {
return O.Of(map[string]string{
k: fmt.Sprintf("%s%d", k, value),
})
}
return O.None[map[string]string]()
}
monoid := MergeMonoid[string, string]()
res := FilterChainWithIndex[int](monoid)(f)(src)
assert.Equal(t, map[string]string{
"a": "a1",
"c": "c3",
}, res)
}
func TestFoldMapWithIndex(t *testing.T) {
data := map[string]int{"a": 1, "b": 2, "c": 3}
fold := FoldMapWithIndex[string, int, string](S.Monoid)(func(k string, v int) string {
return fmt.Sprintf("%s:%d", k, v)
})
result := fold(data)
// Result contains all pairs but order is non-deterministic
assert.Contains(t, result, "a:1")
assert.Contains(t, result, "b:2")
assert.Contains(t, result, "c:3")
}
func TestReduceOrd(t *testing.T) {
data := map[string]int{"c": 3, "a": 1, "b": 2}
sum := ReduceOrd[int, int](S.Ord)(func(acc, v int) int {
return acc + v
}, 0)
result := sum(data)
assert.Equal(t, 6, result)
}
func TestReduceOrdWithIndex(t *testing.T) {
data := map[string]int{"c": 3, "a": 1, "b": 2}
concat := ReduceOrdWithIndex[int, string](S.Ord)(func(k string, acc string, v int) string {
if acc == "" {
return fmt.Sprintf("%s:%d", k, v)
}
return fmt.Sprintf("%s,%s:%d", acc, k, v)
}, "")
result := concat(data)
// With Ord, keys should be in order
assert.Equal(t, "a:1,b:2,c:3", result)
}
func TestFoldMapOrdWithIndex(t *testing.T) {
data := map[string]int{"c": 3, "a": 1, "b": 2}
fold := FoldMapOrdWithIndex[string, int, string](S.Ord)(S.Monoid)(func(k string, v int) string {
return fmt.Sprintf("%s:%d,", k, v)
})
result := fold(data)
assert.Equal(t, "a:1,b:2,c:3,", result)
}
func TestFoldOrd(t *testing.T) {
data := map[string]string{"c": "C", "a": "A", "b": "B"}
fold := FoldOrd[string](S.Ord)(S.Monoid)
result := fold(data)
assert.Equal(t, "ABC", result)
}
func TestFromFoldableMap(t *testing.T) {
src := A.From("a", "b", "c", "a")
// Create a reducer function
reducer := A.Reduce[string, map[string]string]
from := FromFoldableMap[func(func(map[string]string, string) map[string]string, map[string]string) func([]string) map[string]string, string, []string, string, string](
Mg.Second[string](),
reducer,
)
f := from(P.Of[string])
result := f(src)
assert.Equal(t, map[string]string{
"a": "a",
"b": "b",
"c": "c",
}, result)
}
func TestFromFoldable(t *testing.T) {
entries := Entries[string, int]{
P.MakePair("a", 1),
P.MakePair("b", 2),
P.MakePair("a", 3), // Duplicate key
}
reducer := A.Reduce[Entry[string, int], map[string]int]
from := FromFoldable[[]Entry[string, int], func(func(map[string]int, Entry[string, int]) map[string]int, map[string]int) func([]Entry[string, int]) map[string]int, string, int](
Mg.Second[int](),
reducer,
)
result := from(entries)
assert.Equal(t, map[string]int{"a": 3, "b": 2}, result)
}

View File

@@ -20,14 +20,90 @@ import (
S "github.com/IBM/fp-go/v2/semigroup" S "github.com/IBM/fp-go/v2/semigroup"
) )
// UnionSemigroup creates a semigroup for maps that combines two maps using the provided
// semigroup for resolving conflicts when the same key exists in both maps.
//
// When concatenating two maps:
// - Keys that exist in only one map are included in the result
// - Keys that exist in both maps have their values combined using the provided semigroup
//
// This is useful when you want custom conflict resolution logic beyond simple "first wins"
// or "last wins" semantics.
//
// Example:
//
// // Create a semigroup that sums values for duplicate keys
// sumSemigroup := number.SemigroupSum[int]()
// mapSemigroup := UnionSemigroup[string, int](sumSemigroup)
//
// map1 := map[string]int{"a": 1, "b": 2}
// map2 := map[string]int{"b": 3, "c": 4}
// result := mapSemigroup.Concat(map1, map2)
// // result: {"a": 1, "b": 5, "c": 4} // b values are summed: 2 + 3 = 5
//
// Example with string concatenation:
//
// stringSemigroup := string.Semigroup
// mapSemigroup := UnionSemigroup[string, string](stringSemigroup)
//
// map1 := map[string]string{"a": "Hello", "b": "World"}
// map2 := map[string]string{"b": "!", "c": "Goodbye"}
// result := mapSemigroup.Concat(map1, map2)
// // result: {"a": "Hello", "b": "World!", "c": "Goodbye"}
func UnionSemigroup[K comparable, V any](s S.Semigroup[V]) S.Semigroup[map[K]V] { func UnionSemigroup[K comparable, V any](s S.Semigroup[V]) S.Semigroup[map[K]V] {
return G.UnionSemigroup[map[K]V](s) return G.UnionSemigroup[map[K]V](s)
} }
// UnionLastSemigroup creates a semigroup for maps where the last (right) value wins
// when the same key exists in both maps being concatenated.
//
// This is the most common conflict resolution strategy and is equivalent to using
// the standard map merge operation where right-side values take precedence.
//
// When concatenating two maps:
// - Keys that exist in only one map are included in the result
// - Keys that exist in both maps take the value from the second (right) map
//
// Example:
//
// semigroup := UnionLastSemigroup[string, int]()
//
// map1 := map[string]int{"a": 1, "b": 2}
// map2 := map[string]int{"b": 3, "c": 4}
// result := semigroup.Concat(map1, map2)
// // result: {"a": 1, "b": 3, "c": 4} // b takes value from map2 (last wins)
//
// This is useful for:
// - Configuration overrides (later configs override earlier ones)
// - Applying updates to a base map
// - Merging user preferences where newer values should win
func UnionLastSemigroup[K comparable, V any]() S.Semigroup[map[K]V] { func UnionLastSemigroup[K comparable, V any]() S.Semigroup[map[K]V] {
return G.UnionLastSemigroup[map[K]V]() return G.UnionLastSemigroup[map[K]V]()
} }
// UnionFirstSemigroup creates a semigroup for maps where the first (left) value wins
// when the same key exists in both maps being concatenated.
//
// This is useful when you want to preserve original values and ignore updates for
// keys that already exist.
//
// When concatenating two maps:
// - Keys that exist in only one map are included in the result
// - Keys that exist in both maps keep the value from the first (left) map
//
// Example:
//
// semigroup := UnionFirstSemigroup[string, int]()
//
// map1 := map[string]int{"a": 1, "b": 2}
// map2 := map[string]int{"b": 3, "c": 4}
// result := semigroup.Concat(map1, map2)
// // result: {"a": 1, "b": 2, "c": 4} // b keeps value from map1 (first wins)
//
// This is useful for:
// - Default values (defaults are set first, user values don't override)
// - Caching (first cached value is kept, subsequent updates ignored)
// - Immutable registries (first registration wins, duplicates are ignored)
func UnionFirstSemigroup[K comparable, V any]() S.Semigroup[map[K]V] { func UnionFirstSemigroup[K comparable, V any]() S.Semigroup[map[K]V] {
return G.UnionFirstSemigroup[map[K]V]() return G.UnionFirstSemigroup[map[K]V]()
} }

229
v2/record/semigroup_test.go Normal file
View File

@@ -0,0 +1,229 @@
// Copyright (c) 2023 - 2025 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 record
import (
"testing"
N "github.com/IBM/fp-go/v2/number"
S "github.com/IBM/fp-go/v2/string"
"github.com/stretchr/testify/assert"
)
func TestUnionSemigroup(t *testing.T) {
// Test with sum semigroup - values should be added for duplicate keys
sumSemigroup := N.SemigroupSum[int]()
mapSemigroup := UnionSemigroup[string, int](sumSemigroup)
map1 := map[string]int{"a": 1, "b": 2}
map2 := map[string]int{"b": 3, "c": 4}
result := mapSemigroup.Concat(map1, map2)
expected := map[string]int{"a": 1, "b": 5, "c": 4}
assert.Equal(t, expected, result)
}
func TestUnionSemigroupString(t *testing.T) {
// Test with string semigroup - strings should be concatenated
stringSemigroup := S.Semigroup
mapSemigroup := UnionSemigroup[string, string](stringSemigroup)
map1 := map[string]string{"a": "Hello", "b": "World"}
map2 := map[string]string{"b": "!", "c": "Goodbye"}
result := mapSemigroup.Concat(map1, map2)
expected := map[string]string{"a": "Hello", "b": "World!", "c": "Goodbye"}
assert.Equal(t, expected, result)
}
func TestUnionSemigroupProduct(t *testing.T) {
// Test with product semigroup - values should be multiplied
prodSemigroup := N.SemigroupProduct[int]()
mapSemigroup := UnionSemigroup[string, int](prodSemigroup)
map1 := map[string]int{"a": 2, "b": 3}
map2 := map[string]int{"b": 4, "c": 5}
result := mapSemigroup.Concat(map1, map2)
expected := map[string]int{"a": 2, "b": 12, "c": 5}
assert.Equal(t, expected, result)
}
func TestUnionSemigroupEmpty(t *testing.T) {
// Test with empty maps
sumSemigroup := N.SemigroupSum[int]()
mapSemigroup := UnionSemigroup[string, int](sumSemigroup)
map1 := map[string]int{"a": 1}
empty := map[string]int{}
result1 := mapSemigroup.Concat(map1, empty)
assert.Equal(t, map1, result1)
result2 := mapSemigroup.Concat(empty, map1)
assert.Equal(t, map1, result2)
}
func TestUnionLastSemigroup(t *testing.T) {
// Test that last (right) value wins for duplicate keys
semigroup := UnionLastSemigroup[string, int]()
map1 := map[string]int{"a": 1, "b": 2}
map2 := map[string]int{"b": 3, "c": 4}
result := semigroup.Concat(map1, map2)
expected := map[string]int{"a": 1, "b": 3, "c": 4}
assert.Equal(t, expected, result)
}
func TestUnionLastSemigroupNoOverlap(t *testing.T) {
// Test with no overlapping keys
semigroup := UnionLastSemigroup[string, int]()
map1 := map[string]int{"a": 1, "b": 2}
map2 := map[string]int{"c": 3, "d": 4}
result := semigroup.Concat(map1, map2)
expected := map[string]int{"a": 1, "b": 2, "c": 3, "d": 4}
assert.Equal(t, expected, result)
}
func TestUnionLastSemigroupAllOverlap(t *testing.T) {
// Test with all keys overlapping
semigroup := UnionLastSemigroup[string, int]()
map1 := map[string]int{"a": 1, "b": 2}
map2 := map[string]int{"a": 10, "b": 20}
result := semigroup.Concat(map1, map2)
expected := map[string]int{"a": 10, "b": 20}
assert.Equal(t, expected, result)
}
func TestUnionLastSemigroupEmpty(t *testing.T) {
// Test with empty maps
semigroup := UnionLastSemigroup[string, int]()
map1 := map[string]int{"a": 1}
empty := map[string]int{}
result1 := semigroup.Concat(map1, empty)
assert.Equal(t, map1, result1)
result2 := semigroup.Concat(empty, map1)
assert.Equal(t, map1, result2)
}
func TestUnionFirstSemigroup(t *testing.T) {
// Test that first (left) value wins for duplicate keys
semigroup := UnionFirstSemigroup[string, int]()
map1 := map[string]int{"a": 1, "b": 2}
map2 := map[string]int{"b": 3, "c": 4}
result := semigroup.Concat(map1, map2)
expected := map[string]int{"a": 1, "b": 2, "c": 4}
assert.Equal(t, expected, result)
}
func TestUnionFirstSemigroupNoOverlap(t *testing.T) {
// Test with no overlapping keys
semigroup := UnionFirstSemigroup[string, int]()
map1 := map[string]int{"a": 1, "b": 2}
map2 := map[string]int{"c": 3, "d": 4}
result := semigroup.Concat(map1, map2)
expected := map[string]int{"a": 1, "b": 2, "c": 3, "d": 4}
assert.Equal(t, expected, result)
}
func TestUnionFirstSemigroupAllOverlap(t *testing.T) {
// Test with all keys overlapping
semigroup := UnionFirstSemigroup[string, int]()
map1 := map[string]int{"a": 1, "b": 2}
map2 := map[string]int{"a": 10, "b": 20}
result := semigroup.Concat(map1, map2)
expected := map[string]int{"a": 1, "b": 2}
assert.Equal(t, expected, result)
}
func TestUnionFirstSemigroupEmpty(t *testing.T) {
// Test with empty maps
semigroup := UnionFirstSemigroup[string, int]()
map1 := map[string]int{"a": 1}
empty := map[string]int{}
result1 := semigroup.Concat(map1, empty)
assert.Equal(t, map1, result1)
result2 := semigroup.Concat(empty, map1)
assert.Equal(t, map1, result2)
}
// Test associativity law for UnionSemigroup
func TestUnionSemigroupAssociativity(t *testing.T) {
sumSemigroup := N.SemigroupSum[int]()
mapSemigroup := UnionSemigroup[string, int](sumSemigroup)
map1 := map[string]int{"a": 1}
map2 := map[string]int{"a": 2, "b": 3}
map3 := map[string]int{"b": 4, "c": 5}
// (map1 + map2) + map3
left := mapSemigroup.Concat(mapSemigroup.Concat(map1, map2), map3)
// map1 + (map2 + map3)
right := mapSemigroup.Concat(map1, mapSemigroup.Concat(map2, map3))
assert.Equal(t, left, right)
}
// Test associativity law for UnionLastSemigroup
func TestUnionLastSemigroupAssociativity(t *testing.T) {
semigroup := UnionLastSemigroup[string, int]()
map1 := map[string]int{"a": 1}
map2 := map[string]int{"a": 2, "b": 3}
map3 := map[string]int{"b": 4, "c": 5}
// (map1 + map2) + map3
left := semigroup.Concat(semigroup.Concat(map1, map2), map3)
// map1 + (map2 + map3)
right := semigroup.Concat(map1, semigroup.Concat(map2, map3))
assert.Equal(t, left, right)
}
// Test associativity law for UnionFirstSemigroup
func TestUnionFirstSemigroupAssociativity(t *testing.T) {
semigroup := UnionFirstSemigroup[string, int]()
map1 := map[string]int{"a": 1}
map2 := map[string]int{"a": 2, "b": 3}
map3 := map[string]int{"b": 4, "c": 5}
// (map1 + map2) + map3
left := semigroup.Concat(semigroup.Concat(map1, map2), map3)
// map1 + (map2 + map3)
right := semigroup.Concat(map1, semigroup.Concat(map2, map3))
assert.Equal(t, left, right)
}
// Made with Bob

154
v2/record/types.go Normal file
View File

@@ -0,0 +1,154 @@
// Copyright (c) 2023 - 2025 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 record
import (
"github.com/IBM/fp-go/v2/pair"
"github.com/IBM/fp-go/v2/predicate"
)
type (
// Record represents a map with comparable keys and values of any type.
// This is the primary data structure for the record package, providing
// functional operations over Go's native map type.
//
// Example:
//
// type UserRecord = Record[string, User]
// users := UserRecord{
// "alice": User{Name: "Alice", Age: 30},
// "bob": User{Name: "Bob", Age: 25},
// }
Record[K comparable, V any] = map[K]V
// Predicate is a function that tests whether a key satisfies a condition.
// Used in filtering operations to determine which entries to keep.
//
// Example:
//
// isVowel := func(k string) bool {
// return strings.ContainsAny(k, "aeiou")
// }
Predicate[K any] = predicate.Predicate[K]
// PredicateWithIndex is a function that tests whether a key-value pair satisfies a condition.
// Used in filtering operations that need access to both key and value.
//
// Example:
//
// isAdult := func(name string, user User) bool {
// return user.Age >= 18
// }
PredicateWithIndex[K comparable, V any] = func(K, V) bool
// Operator transforms a record from one value type to another while preserving keys.
// This is the fundamental transformation type for record operations.
//
// Example:
//
// doubleValues := Map(func(x int) int { return x * 2 })
// result := doubleValues(Record[string, int]{"a": 1, "b": 2})
// // result: {"a": 2, "b": 4}
Operator[K comparable, V1, V2 any] = func(Record[K, V1]) Record[K, V2]
// OperatorWithIndex transforms a record using both key and value information.
// Useful when the transformation depends on the key.
//
// Example:
//
// prefixWithKey := MapWithIndex(func(k string, v string) string {
// return k + ":" + v
// })
OperatorWithIndex[K comparable, V1, V2 any] = func(func(K, V1) V2) Operator[K, V1, V2]
// Kleisli represents a monadic function that transforms a value into a record.
// Used in chain operations for composing record-producing functions.
//
// Example:
//
// expand := func(x int) Record[string, int] {
// return Record[string, int]{
// "double": x * 2,
// "triple": x * 3,
// }
// }
Kleisli[K comparable, V1, V2 any] = func(V1) Record[K, V2]
// KleisliWithIndex is a monadic function that uses both key and value to produce a record.
//
// Example:
//
// expandWithKey := func(k string, v int) Record[string, int] {
// return Record[string, int]{
// k + "_double": v * 2,
// k + "_triple": v * 3,
// }
// }
KleisliWithIndex[K comparable, V1, V2 any] = func(K, V1) Record[K, V2]
// Reducer accumulates values from a record into a single result.
// The function receives the accumulator and current value, returning the new accumulator.
//
// Example:
//
// sum := Reduce(func(acc int, v int) int {
// return acc + v
// }, 0)
Reducer[K comparable, V, R any] = func(R, V) R
// ReducerWithIndex accumulates values using both key and value information.
//
// Example:
//
// weightedSum := ReduceWithIndex(func(k string, acc int, v int) int {
// weight := len(k)
// return acc + (v * weight)
// }, 0)
ReducerWithIndex[K comparable, V, R any] = func(K, R, V) R
// Collector transforms key-value pairs into a result type and collects them into an array.
//
// Example:
//
// toStrings := Collect(func(k string, v int) string {
// return fmt.Sprintf("%s=%d", k, v)
// })
Collector[K comparable, V, R any] = func(K, V) R
// Entry represents a single key-value pair from a record.
// This is an alias for Tuple2 to provide semantic clarity.
//
// Example:
//
// entries := ToEntries(record)
// for _, entry := range entries {
// key := entry.F1
// value := entry.F2
// }
Entry[K comparable, V any] = pair.Pair[K, V]
// Entries is a slice of key-value pairs.
//
// Example:
//
// entries := Entries[string, int]{
// T.MakeTuple2("a", 1),
// T.MakeTuple2("b", 2),
// }
// record := FromEntries(entries)
Entries[K comparable, V any] = []Entry[K, V]
)