1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-12-15 23:33:46 +02:00

Compare commits

...

6 Commits

Author SHA1 Message Date
Dr. Carsten Leue
57794ccb34 fix: add idiomatic go options package
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-17 11:10:27 +01:00
Dr. Carsten Leue
404eb875d3 fix: add idiomatic version
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-16 17:27:16 +01:00
Dr. Carsten Leue
ed108812d6 fix: modernize codebase
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-15 17:00:22 +01:00
Dr. Carsten Leue
ab868315d4 fix: traverse
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-15 12:13:37 +01:00
Dr. Carsten Leue
02d0be9dad fix: add traversal for sequences
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-14 14:12:44 +01:00
Dr. Carsten Leue
2c1d8196b4 fix: support go iterators and cleanup types
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-14 12:56:12 +01:00
158 changed files with 7497 additions and 716 deletions

View File

@@ -69,7 +69,7 @@ func main() {
none := option.None[int]()
// Map over values
doubled := option.Map(func(x int) int { return x * 2 })(some)
doubled := option.Map(N.Mul(2))(some)
fmt.Println(option.GetOrElse(0)(doubled)) // Output: 84
// Chain operations
@@ -187,7 +187,7 @@ Monadic operations for `Pair` now operate on the **second argument** to align wi
```go
// Operations on first element
pair := MakePair(1, "hello")
result := Map(func(x int) int { return x * 2 })(pair) // Pair(2, "hello")
result := Map(N.Mul(2))(pair) // Pair(2, "hello")
```
**V2:**
@@ -204,7 +204,7 @@ The `Compose` function for endomorphisms now follows **mathematical function com
**V1:**
```go
// Compose executed left-to-right
double := func(x int) int { return x * 2 }
double := N.Mul(2)
increment := func(x int) int { return x + 1 }
composed := Compose(double, increment)
result := composed(5) // (5 * 2) + 1 = 11
@@ -213,7 +213,7 @@ result := composed(5) // (5 * 2) + 1 = 11
**V2:**
```go
// Compose executes RIGHT-TO-LEFT (mathematical composition)
double := func(x int) int { return x * 2 }
double := N.Mul(2)
increment := func(x int) int { return x + 1 }
composed := Compose(double, increment)
result := composed(5) // (5 + 1) * 2 = 12
@@ -368,7 +368,7 @@ If you're using `Pair`, update operations to work on the second element:
```go
pair := MakePair(42, "data")
// Map operates on first element
result := Map(func(x int) int { return x * 2 })(pair)
result := Map(N.Mul(2))(pair)
```
**After (V2):**

View File

@@ -17,11 +17,10 @@ package array
import (
G "github.com/IBM/fp-go/v2/array/generic"
EM "github.com/IBM/fp-go/v2/endomorphism"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/array"
M "github.com/IBM/fp-go/v2/monoid"
O "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/tuple"
)
@@ -50,16 +49,16 @@ func Replicate[A any](n int, a A) []A {
// This is the monadic version of Map that takes the array as the first parameter.
//
//go:inline
func MonadMap[A, B any](as []A, f func(a A) B) []B {
func MonadMap[A, B any](as []A, f func(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 {
func MonadMapRef[A, B any](as []A, f func(*A) B) []B {
count := len(as)
bs := make([]B, count)
for i := count - 1; i >= 0; i-- {
for i := range count {
bs[i] = f(&as[i])
}
return bs
@@ -68,7 +67,7 @@ func MonadMapRef[A, B any](as []A, f func(a *A) B) []B {
// MapWithIndex applies a function to each element and its index in an array, returning a new array with the results.
//
//go:inline
func MapWithIndex[A, B any](f func(int, A) B) func([]A) []B {
func MapWithIndex[A, B any](f func(int, A) B) Operator[A, B] {
return G.MapWithIndex[[]A, []B](f)
}
@@ -77,39 +76,39 @@ func MapWithIndex[A, B any](f func(int, A) B) func([]A) []B {
//
// Example:
//
// double := array.Map(func(x int) int { return x * 2 })
// double := array.Map(N.Mul(2))
// result := double([]int{1, 2, 3}) // [2, 4, 6]
//
//go:inline
func Map[A, B any](f func(a A) B) func([]A) []B {
func Map[A, B any](f func(A) B) Operator[A, B] {
return G.Map[[]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 {
func MapRef[A, B any](f func(*A) B) Operator[A, B] {
return F.Bind2nd(MonadMapRef[A, B], f)
}
func filterRef[A any](fa []A, pred func(a *A) bool) []A {
var result []A
func filterRef[A any](fa []A, pred func(*A) bool) []A {
count := len(fa)
for i := 0; i < count; i++ {
a := fa[i]
if pred(&a) {
result = append(result, a)
var result []A = make([]A, 0, count)
for i := range count {
a := &fa[i]
if pred(a) {
result = append(result, *a)
}
}
return result
}
func filterMapRef[A, B any](fa []A, pred func(a *A) bool, f func(a *A) B) []B {
var result []B
func filterMapRef[A, B any](fa []A, pred func(*A) bool, f func(*A) B) []B {
count := len(fa)
for i := 0; i < count; i++ {
a := fa[i]
if pred(&a) {
result = append(result, f(&a))
var result []B = make([]B, 0, count)
for i := range count {
a := &fa[i]
if pred(a) {
result = append(result, f(a))
}
}
return result
@@ -118,19 +117,19 @@ func filterMapRef[A, B any](fa []A, pred func(a *A) bool, f func(a *A) B) []B {
// Filter returns a new array with all elements from the original array that match a predicate
//
//go:inline
func Filter[A any](pred func(A) bool) EM.Endomorphism[[]A] {
func Filter[A any](pred func(A) bool) Operator[A, A] {
return G.Filter[[]A](pred)
}
// FilterWithIndex returns a new array with all elements from the original array that match a predicate
//
//go:inline
func FilterWithIndex[A any](pred func(int, A) bool) EM.Endomorphism[[]A] {
func FilterWithIndex[A any](pred func(int, A) bool) Operator[A, 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] {
func FilterRef[A any](pred func(*A) bool) Operator[A, A] {
return F.Bind2nd(filterRef[A], pred)
}
@@ -138,7 +137,7 @@ func FilterRef[A any](pred func(*A) bool) EM.Endomorphism[[]A] {
// This is the monadic version that takes the array as the first parameter.
//
//go:inline
func MonadFilterMap[A, B any](fa []A, f func(A) O.Option[B]) []B {
func MonadFilterMap[A, B any](fa []A, f option.Kleisli[A, B]) []B {
return G.MonadFilterMap[[]A, []B](fa, f)
}
@@ -146,33 +145,33 @@ func MonadFilterMap[A, B any](fa []A, f func(A) O.Option[B]) []B {
// keeping only the Some values. This is the monadic version that takes the array as the first parameter.
//
//go:inline
func MonadFilterMapWithIndex[A, B any](fa []A, f func(int, A) O.Option[B]) []B {
func MonadFilterMapWithIndex[A, B any](fa []A, f func(int, A) Option[B]) []B {
return G.MonadFilterMapWithIndex[[]A, []B](fa, f)
}
// FilterMap maps an array with an iterating function that returns an [O.Option] and it keeps only the Some values discarding the Nones.
// FilterMap maps an array with an iterating function that returns an [Option] and it keeps only the Some values discarding the Nones.
//
//go:inline
func FilterMap[A, B any](f func(A) O.Option[B]) func([]A) []B {
func FilterMap[A, B any](f option.Kleisli[A, B]) Operator[A, B] {
return G.FilterMap[[]A, []B](f)
}
// FilterMapWithIndex maps an array with an iterating function that returns an [O.Option] and it keeps only the Some values discarding the Nones.
// FilterMapWithIndex maps an array with an iterating function that returns an [Option] and it keeps only the Some values discarding the Nones.
//
//go:inline
func FilterMapWithIndex[A, B any](f func(int, A) O.Option[B]) func([]A) []B {
func FilterMapWithIndex[A, B any](f func(int, A) Option[B]) Operator[A, B] {
return G.FilterMapWithIndex[[]A, []B](f)
}
// FilterChain maps an array with an iterating function that returns an [O.Option] of an array. It keeps only the Some values discarding the Nones and then flattens the result.
// FilterChain maps an array with an iterating function that returns an [Option] of an array. It keeps only the Some values discarding the Nones and then flattens the result.
//
//go:inline
func FilterChain[A, B any](f func(A) O.Option[[]B]) func([]A) []B {
func FilterChain[A, B any](f option.Kleisli[A, []B]) Operator[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 {
func FilterMapRef[A, B any](pred func(a *A) bool, f func(*A) B) Operator[A, B] {
return func(fa []A) []B {
return filterMapRef(fa, pred, f)
}
@@ -180,8 +179,7 @@ func FilterMapRef[A, B any](pred func(a *A) bool, f func(a *A) B) func([]A) []B
func reduceRef[A, B any](fa []A, f func(B, *A) B, initial B) B {
current := initial
count := len(fa)
for i := 0; i < count; i++ {
for i := range len(fa) {
current = f(current, &fa[i])
}
return current
@@ -262,6 +260,8 @@ func Empty[A any]() []A {
}
// Zero returns an empty array of type A (alias for Empty).
//
//go:inline
func Zero[A any]() []A {
return Empty[A]()
}
@@ -277,7 +277,7 @@ func Of[A any](a A) []A {
// This is the monadic version that takes the array as the first parameter (also known as FlatMap).
//
//go:inline
func MonadChain[A, B any](fa []A, f func(a A) []B) []B {
func MonadChain[A, B any](fa []A, f Kleisli[A, B]) []B {
return G.MonadChain(fa, f)
}
@@ -290,7 +290,7 @@ func MonadChain[A, B any](fa []A, f func(a A) []B) []B {
// result := duplicate([]int{1, 2, 3}) // [1, 1, 2, 2, 3, 3]
//
//go:inline
func Chain[A, B any](f func(A) []B) func([]A) []B {
func Chain[A, B any](f Kleisli[A, B]) Operator[A, B] {
return G.Chain[[]A](f)
}
@@ -306,7 +306,7 @@ func MonadAp[B, A any](fab []func(A) B, fa []A) []B {
// This is the curried version.
//
//go:inline
func Ap[B, A any](fa []A) func([]func(A) B) []B {
func Ap[B, A any](fa []A) Operator[func(A) B, B] {
return G.Ap[[]B, []func(A) B](fa)
}
@@ -328,7 +328,7 @@ func MatchLeft[A, B any](onEmpty func() B, onNonEmpty func(A, []A) B) func([]A)
// Returns None if the array is empty.
//
//go:inline
func Tail[A any](as []A) O.Option[[]A] {
func Tail[A any](as []A) Option[[]A] {
return G.Tail(as)
}
@@ -336,7 +336,7 @@ func Tail[A any](as []A) O.Option[[]A] {
// Returns None if the array is empty.
//
//go:inline
func Head[A any](as []A) O.Option[A] {
func Head[A any](as []A) Option[A] {
return G.Head(as)
}
@@ -344,7 +344,7 @@ func Head[A any](as []A) O.Option[A] {
// Returns None if the array is empty.
//
//go:inline
func First[A any](as []A) O.Option[A] {
func First[A any](as []A) Option[A] {
return G.First(as)
}
@@ -352,12 +352,12 @@ func First[A any](as []A) O.Option[A] {
// Returns None if the array is empty.
//
//go:inline
func Last[A any](as []A) O.Option[A] {
func Last[A any](as []A) 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] {
func PrependAll[A any](middle A) Operator[A, A] {
return func(as []A) []A {
count := len(as)
dst := count * 2
@@ -377,7 +377,7 @@ func PrependAll[A any](middle A) EM.Endomorphism[[]A] {
// Example:
//
// result := array.Intersperse(0)([]int{1, 2, 3}) // [1, 0, 2, 0, 3]
func Intersperse[A any](middle A) EM.Endomorphism[[]A] {
func Intersperse[A any](middle A) Operator[A, A] {
prepend := PrependAll(middle)
return func(as []A) []A {
if IsEmpty(as) {
@@ -406,7 +406,7 @@ func Flatten[A any](mma [][]A) []A {
}
// Slice extracts a subarray from index low (inclusive) to high (exclusive).
func Slice[A any](low, high int) func(as []A) []A {
func Slice[A any](low, high int) Operator[A, A] {
return array.Slice[[]A](low, high)
}
@@ -414,7 +414,7 @@ func Slice[A any](low, high int) func(as []A) []A {
// Returns None if the index is out of bounds.
//
//go:inline
func Lookup[A any](idx int) func([]A) O.Option[A] {
func Lookup[A any](idx int) func([]A) Option[A] {
return G.Lookup[[]A](idx)
}
@@ -422,7 +422,7 @@ func Lookup[A any](idx int) func([]A) O.Option[A] {
// If the index is out of bounds, the element is appended.
//
//go:inline
func UpsertAt[A any](a A) EM.Endomorphism[[]A] {
func UpsertAt[A any](a A) Operator[A, A] {
return G.UpsertAt[[]A](a)
}
@@ -468,7 +468,7 @@ func ConstNil[A any]() []A {
// SliceRight extracts a subarray from the specified start index to the end.
//
//go:inline
func SliceRight[A any](start int) EM.Endomorphism[[]A] {
func SliceRight[A any](start int) Operator[A, A] {
return G.SliceRight[[]A](start)
}
@@ -482,7 +482,7 @@ func Copy[A any](b []A) []A {
// Clone creates a deep copy of the array using the provided endomorphism to clone the values
//
//go:inline
func Clone[A any](f func(A) A) func(as []A) []A {
func Clone[A any](f func(A) A) Operator[A, A] {
return G.Clone[[]A](f)
}
@@ -510,8 +510,8 @@ func Fold[A any](m M.Monoid[A]) func([]A) A {
// Push adds an element to the end of an array (alias for Append).
//
//go:inline
func Push[A any](a A) EM.Endomorphism[[]A] {
return G.Push[EM.Endomorphism[[]A]](a)
func Push[A any](a A) Operator[A, A] {
return G.Push[Operator[A, A]](a)
}
// MonadFlap applies a value to an array of functions, producing an array of results.
@@ -526,13 +526,13 @@ func MonadFlap[B, A any](fab []func(A) B, a A) []B {
// This is the curried version.
//
//go:inline
func Flap[B, A any](a A) func([]func(A) B) []B {
func Flap[B, A any](a A) Operator[func(A) B, B] {
return G.Flap[func(A) B, []func(A) B, []B](a)
}
// Prepend adds an element to the beginning of an array, returning a new array.
//
//go:inline
func Prepend[A any](head A) EM.Endomorphism[[]A] {
return G.Prepend[EM.Endomorphism[[]A]](head)
func Prepend[A any](head A) Operator[A, A] {
return G.Prepend[Operator[A, A]](head)
}

View File

@@ -56,8 +56,8 @@ func Do[S any](
//go:inline
func Bind[S1, S2, T any](
setter func(T) func(S1) S2,
f func(S1) []T,
) func([]S1) []S2 {
f Kleisli[S1, T],
) Operator[S1, S2] {
return G.Bind[[]S1, []S2](setter, f)
}
@@ -79,7 +79,7 @@ func Bind[S1, S2, T any](
func Let[S1, S2, T any](
setter func(T) func(S1) S2,
f func(S1) T,
) func([]S1) []S2 {
) Operator[S1, S2] {
return G.Let[[]S1, []S2](setter, f)
}
@@ -101,7 +101,7 @@ func Let[S1, S2, T any](
func LetTo[S1, S2, T any](
setter func(T) func(S1) S2,
b T,
) func([]S1) []S2 {
) Operator[S1, S2] {
return G.LetTo[[]S1, []S2](setter, b)
}
@@ -120,7 +120,7 @@ func LetTo[S1, S2, T any](
//go:inline
func BindTo[S1, T any](
setter func(T) S1,
) func([]T) []S1 {
) Operator[T, S1] {
return G.BindTo[[]S1, []T](setter)
}
@@ -143,6 +143,6 @@ func BindTo[S1, T any](
func ApS[S1, S2, T any](
setter func(T) func(S1) S2,
fa []T,
) func([]S1) []S2 {
) Operator[S1, S2] {
return G.ApS[[]S1, []S2](setter, fa)
}

View File

@@ -36,7 +36,7 @@
// generated := array.MakeBy(5, func(i int) int { return i * 2 })
//
// // Transforming arrays
// doubled := array.Map(func(x int) int { return x * 2 })(arr)
// doubled := array.Map(N.Mul(2))(arr)
// filtered := array.Filter(func(x int) bool { return x > 2 })(arr)
//
// // Combining arrays
@@ -50,7 +50,7 @@
// numbers := []int{1, 2, 3, 4, 5}
//
// // Map transforms each element
// doubled := array.Map(func(x int) int { return x * 2 })(numbers)
// doubled := array.Map(N.Mul(2))(numbers)
// // Result: [2, 4, 6, 8, 10]
//
// // Filter keeps elements matching a predicate

View File

@@ -17,7 +17,7 @@ package array
import (
G "github.com/IBM/fp-go/v2/array/generic"
O "github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/option"
)
// FindFirst finds the first element which satisfies a predicate function.
@@ -30,7 +30,7 @@ import (
// result2 := findGreaterThan3([]int{1, 2, 3}) // None
//
//go:inline
func FindFirst[A any](pred func(A) bool) func([]A) O.Option[A] {
func FindFirst[A any](pred func(A) bool) option.Kleisli[[]A, A] {
return G.FindFirst[[]A](pred)
}
@@ -45,7 +45,7 @@ func FindFirst[A any](pred func(A) bool) func([]A) O.Option[A] {
// result := findEvenAtEvenIndex([]int{1, 3, 4, 5}) // Some(4)
//
//go:inline
func FindFirstWithIndex[A any](pred func(int, A) bool) func([]A) O.Option[A] {
func FindFirstWithIndex[A any](pred func(int, A) bool) option.Kleisli[[]A, A] {
return G.FindFirstWithIndex[[]A](pred)
}
@@ -65,7 +65,7 @@ func FindFirstWithIndex[A any](pred func(int, A) bool) func([]A) O.Option[A] {
// result := parseFirst([]string{"a", "42", "b"}) // Some(42)
//
//go:inline
func FindFirstMap[A, B any](sel func(A) O.Option[B]) func([]A) O.Option[B] {
func FindFirstMap[A, B any](sel option.Kleisli[A, B]) option.Kleisli[[]A, B] {
return G.FindFirstMap[[]A](sel)
}
@@ -73,7 +73,7 @@ func FindFirstMap[A, B any](sel func(A) O.Option[B]) func([]A) O.Option[B] {
// The selector receives both the index and the element.
//
//go:inline
func FindFirstMapWithIndex[A, B any](sel func(int, A) O.Option[B]) func([]A) O.Option[B] {
func FindFirstMapWithIndex[A, B any](sel func(int, A) Option[B]) option.Kleisli[[]A, B] {
return G.FindFirstMapWithIndex[[]A](sel)
}
@@ -86,7 +86,7 @@ func FindFirstMapWithIndex[A, B any](sel func(int, A) O.Option[B]) func([]A) O.O
// result := findGreaterThan3([]int{1, 4, 2, 5}) // Some(5)
//
//go:inline
func FindLast[A any](pred func(A) bool) func([]A) O.Option[A] {
func FindLast[A any](pred func(A) bool) option.Kleisli[[]A, A] {
return G.FindLast[[]A](pred)
}
@@ -94,7 +94,7 @@ func FindLast[A any](pred func(A) bool) func([]A) O.Option[A] {
// Returns Some(element) if found, None if no element matches.
//
//go:inline
func FindLastWithIndex[A any](pred func(int, A) bool) func([]A) O.Option[A] {
func FindLastWithIndex[A any](pred func(int, A) bool) option.Kleisli[[]A, A] {
return G.FindLastWithIndex[[]A](pred)
}
@@ -102,7 +102,7 @@ func FindLastWithIndex[A any](pred func(int, A) bool) func([]A) O.Option[A] {
// This combines finding and mapping in a single operation, searching from the end.
//
//go:inline
func FindLastMap[A, B any](sel func(A) O.Option[B]) func([]A) O.Option[B] {
func FindLastMap[A, B any](sel option.Kleisli[A, B]) option.Kleisli[[]A, B] {
return G.FindLastMap[[]A](sel)
}
@@ -110,6 +110,6 @@ func FindLastMap[A, B any](sel func(A) O.Option[B]) func([]A) O.Option[B] {
// The selector receives both the index and the element, searching from the end.
//
//go:inline
func FindLastMapWithIndex[A, B any](sel func(int, A) O.Option[B]) func([]A) O.Option[B] {
func FindLastMapWithIndex[A, B any](sel func(int, A) Option[B]) option.Kleisli[[]A, B] {
return G.FindLastMapWithIndex[[]A](sel)
}

View File

@@ -25,8 +25,10 @@ import (
)
// Of constructs a single element array
//
//go:inline
func Of[GA ~[]A, A any](value A) GA {
return GA{value}
return array.Of[GA](value)
}
func Reduce[GA ~[]A, A, B any](f func(B, A) B, initial B) func(GA) B {
@@ -82,7 +84,7 @@ func MakeBy[AS ~[]A, F ~func(int) A, A any](n int, f F) AS {
}
// run the generator function across the input
as := make(AS, n)
for i := n - 1; i >= 0; i-- {
for i := range n {
as[i] = f(i)
}
return as
@@ -165,10 +167,9 @@ 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 {
result := make(GB, 0, len(fa))
for _, a := range fa {
O.Map(func(b B) B {
if b, ok := O.Unwrap(f(a)); ok {
result = append(result, b)
return b
})(f(a))
}
}
return result
}
@@ -176,10 +177,9 @@ func filterMap[GA ~[]A, GB ~[]B, A, B any](fa GA, f func(A) O.Option[B]) GB {
func filterMapWithIndex[GA ~[]A, GB ~[]B, A, B any](fa GA, f func(int, A) O.Option[B]) GB {
result := make(GB, 0, len(fa))
for i, a := range fa {
O.Map(func(b B) B {
if b, ok := O.Unwrap(f(i, a)); ok {
result = append(result, b)
return b
})(f(i, a))
}
}
return result
}

View File

@@ -42,8 +42,7 @@ func FindFirst[AS ~[]A, PRED ~func(A) bool, A any](pred PRED) func(AS) O.Option[
func FindFirstMapWithIndex[AS ~[]A, PRED ~func(int, A) O.Option[B], A, B any](pred PRED) func(AS) O.Option[B] {
none := O.None[B]()
return func(as AS) O.Option[B] {
count := len(as)
for i := 0; i < count; i++ {
for i := range len(as) {
out := pred(i, as[i])
if O.IsSome(out) {
return out

View File

@@ -0,0 +1,34 @@
package generic
import (
"github.com/IBM/fp-go/v2/internal/array"
M "github.com/IBM/fp-go/v2/monoid"
S "github.com/IBM/fp-go/v2/semigroup"
)
// 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() // []
//
//go:inline
func Monoid[GT ~[]T, T any]() M.Monoid[GT] {
return M.MakeMonoid(array.Concat[GT], Empty[GT]())
}
// 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]
//
//go:inline
func Semigroup[GT ~[]T, T any]() S.Semigroup[GT] {
return S.MakeSemigroup(array.Concat[GT])
}

View File

@@ -26,7 +26,7 @@ import (
func ZipWith[AS ~[]A, BS ~[]B, CS ~[]C, FCT ~func(A, B) C, A, B, C any](fa AS, fb BS, f FCT) CS {
l := N.Min(len(fa), len(fb))
res := make(CS, l)
for i := l - 1; i >= 0; i-- {
for i := range l {
res[i] = f(fa[i], fb[i])
}
return res
@@ -43,7 +43,7 @@ func Unzip[AS ~[]A, BS ~[]B, CS ~[]T.Tuple2[A, B], A, B any](cs CS) T.Tuple2[AS,
l := len(cs)
as := make(AS, l)
bs := make(BS, l)
for i := l - 1; i >= 0; i-- {
for i := range l {
t := cs[i]
as[i] = t.F1
bs[i] = t.F2

View File

@@ -18,7 +18,6 @@ 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"
)
@@ -103,39 +102,6 @@ func TestSortByKey(t *testing.T) {
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

View File

@@ -16,27 +16,12 @@
package array
import (
G "github.com/IBM/fp-go/v2/array/generic"
"github.com/IBM/fp-go/v2/internal/array"
M "github.com/IBM/fp-go/v2/monoid"
S "github.com/IBM/fp-go/v2/semigroup"
)
func concat[T any](left, right []T) []T {
// some performance checks
ll := len(left)
if ll == 0 {
return right
}
lr := len(right)
if lr == 0 {
return left
}
// need to copy
buf := make([]T, ll+lr)
copy(buf[copy(buf, left):], right)
return buf
}
// Monoid returns a Monoid instance for arrays.
// The Monoid combines arrays through concatenation, with an empty array as the identity element.
//
@@ -45,8 +30,10 @@ func concat[T any](left, right []T) []T {
// m := array.Monoid[int]()
// result := m.Concat([]int{1, 2}, []int{3, 4}) // [1, 2, 3, 4]
// empty := m.Empty() // []
//
//go:inline
func Monoid[T any]() M.Monoid[[]T] {
return M.MakeMonoid(concat[T], Empty[T]())
return G.Monoid[[]T]()
}
// Semigroup returns a Semigroup instance for arrays.
@@ -56,8 +43,10 @@ func Monoid[T any]() M.Monoid[[]T] {
//
// s := array.Semigroup[int]()
// result := s.Concat([]int{1, 2}, []int{3, 4}) // [1, 2, 3, 4]
//
//go:inline
func Semigroup[T any]() S.Semigroup[[]T] {
return S.MakeSemigroup(concat[T])
return G.Semigroup[[]T]()
}
func addLen[A any](count int, data []A) int {

View File

@@ -16,10 +16,18 @@
package array
import (
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/array"
M "github.com/IBM/fp-go/v2/monoid"
O "github.com/IBM/fp-go/v2/option"
)
func MonadSequence[HKTA, HKTRA any](
fof func(HKTA) HKTRA,
m M.Monoid[HKTRA],
ma []HKTA) HKTRA {
return array.MonadSequence(fof, m.Empty(), m.Concat, ma)
}
// Sequence takes an array where elements are HKT<A> (higher kinded type) and,
// using an applicative of that HKT, returns an HKT of []A.
//
@@ -55,16 +63,11 @@ import (
// 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 Sequence[HKTA, HKTRA any](
fof func(HKTA) HKTRA,
m M.Monoid[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)
}, empty)
return array.Sequence[[]HKTA](fof, m.Empty(), m.Concat)
}
// ArrayOption returns a function to convert a sequence of options into an option of a sequence.
@@ -86,10 +89,10 @@ func Sequence[A, HKTA, HKTRA, HKTFRA any](
// 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],
O.MonadMap[[]A, func(A) []A],
O.MonadAp[[]A, A],
func ArrayOption[A any](ma []Option[A]) Option[[]A] {
return MonadSequence(
O.Map(Of[A]),
O.ApplicativeMonoid(Monoid[A]()),
ma,
)
}

View File

@@ -24,8 +24,7 @@ import (
)
func TestSequenceOption(t *testing.T) {
seq := ArrayOption[int]()
assert.Equal(t, O.Of([]int{1, 3}), seq([]O.Option[int]{O.Of(1), O.Of(3)}))
assert.Equal(t, O.None[[]int](), seq([]O.Option[int]{O.Of(1), O.None[int]()}))
assert.Equal(t, O.Of([]int{1, 3}), ArrayOption([]O.Option[int]{O.Of(1), O.Of(3)}))
assert.Equal(t, O.None[[]int](), ArrayOption([]O.Option[int]{O.Of(1), O.None[int]()}))
}

View File

@@ -18,6 +18,7 @@ package array
import (
"testing"
N "github.com/IBM/fp-go/v2/number"
"github.com/stretchr/testify/assert"
)
@@ -243,7 +244,7 @@ func TestSliceComposition(t *testing.T) {
t.Run("slice then map", func(t *testing.T) {
sliced := Slice[int](2, 5)(data)
mapped := Map(func(x int) int { return x * 2 })(sliced)
mapped := Map(N.Mul(2))(sliced)
assert.Equal(t, []int{4, 6, 8}, mapped)
})

View File

@@ -32,7 +32,7 @@ import (
// // Result: [1, 1, 2, 3, 4, 5, 6, 9]
//
//go:inline
func Sort[T any](ord O.Ord[T]) func(ma []T) []T {
func Sort[T any](ord O.Ord[T]) Operator[T, T] {
return G.Sort[[]T](ord)
}
@@ -62,7 +62,7 @@ func Sort[T any](ord O.Ord[T]) func(ma []T) []T {
// // Result: [{"Bob", 25}, {"Alice", 30}, {"Charlie", 35}]
//
//go:inline
func SortByKey[K, T any](ord O.Ord[K], f func(T) K) func(ma []T) []T {
func SortByKey[K, T any](ord O.Ord[K], f func(T) K) Operator[T, T] {
return G.SortByKey[[]T](ord, f)
}
@@ -93,6 +93,6 @@ func SortByKey[K, T any](ord O.Ord[K], f func(T) K) func(ma []T) []T {
// // Result: [{"Jones", "Bob"}, {"Smith", "Alice"}, {"Smith", "John"}]
//
//go:inline
func SortBy[T any](ord []O.Ord[T]) func(ma []T) []T {
func SortBy[T any](ord []O.Ord[T]) Operator[T, T] {
return G.SortBy[[]T](ord)
}

View File

@@ -80,3 +80,25 @@ func MonadTraverse[A, B, HKTB, HKTAB, HKTRB any](
return array.MonadTraverse(fof, fmap, fap, ta, f)
}
//go:inline
func TraverseWithIndex[A, B, HKTB, HKTAB, HKTRB any](
fof func([]B) HKTRB,
fmap func(func([]B) func(B) []B) func(HKTRB) HKTAB,
fap func(HKTB) func(HKTAB) HKTRB,
f func(int, A) HKTB) func([]A) HKTRB {
return array.TraverseWithIndex[[]A](fof, fmap, fap, f)
}
//go:inline
func MonadTraverseWithIndex[A, B, HKTB, HKTAB, HKTRB any](
fof func([]B) HKTRB,
fmap func(func([]B) func(B) []B) func(HKTRB) HKTAB,
fap func(HKTB) func(HKTAB) HKTRB,
ta []A,
f func(int, A) HKTB) HKTRB {
return array.MonadTraverseWithIndex(fof, fmap, fap, ta, f)
}

9
v2/array/types.go Normal file
View File

@@ -0,0 +1,9 @@
package array
import "github.com/IBM/fp-go/v2/option"
type (
Kleisli[A, B any] = func(A) []B
Operator[A, B any] = Kleisli[[]A, B]
Option[A any] = option.Option[A]
)

View File

@@ -46,6 +46,6 @@ func StrictUniq[A comparable](as []A) []A {
// // Result: [{"Alice", 30}, {"Bob", 25}, {"Charlie", 30}]
//
//go:inline
func Uniq[A any, K comparable](f func(A) K) func(as []A) []A {
func Uniq[A any, K comparable](f func(A) K) Operator[A, A] {
return G.Uniq[[]A](f)
}

View File

@@ -382,7 +382,7 @@ func BenchmarkToString(b *testing.B) {
data := []byte("Hello, World!")
b.Run("small", func(b *testing.B) {
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = ToString(data)
}
})
@@ -393,7 +393,7 @@ func BenchmarkToString(b *testing.B) {
large[i] = byte(i % 256)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = ToString(large)
}
})
@@ -402,7 +402,7 @@ func BenchmarkToString(b *testing.B) {
func BenchmarkSize(b *testing.B) {
data := []byte("Hello, World!")
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = Size(data)
}
}
@@ -412,7 +412,7 @@ func BenchmarkMonoidConcat(b *testing.B) {
c := []byte(" World")
b.Run("small slices", func(b *testing.B) {
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = Monoid.Concat(a, c)
}
})
@@ -421,7 +421,7 @@ func BenchmarkMonoidConcat(b *testing.B) {
large1 := make([]byte, 10000)
large2 := make([]byte, 10000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = Monoid.Concat(large1, large2)
}
})
@@ -436,7 +436,7 @@ func BenchmarkConcatAll(b *testing.B) {
}
b.Run("few slices", func(b *testing.B) {
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = ConcatAll(slices...)
}
})
@@ -447,7 +447,7 @@ func BenchmarkConcatAll(b *testing.B) {
many[i] = []byte{byte(i)}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = ConcatAll(many...)
}
})
@@ -458,13 +458,13 @@ func BenchmarkOrdCompare(b *testing.B) {
c := []byte("abd")
b.Run("equal", func(b *testing.B) {
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = Ord.Compare(a, a)
}
})
b.Run("different", func(b *testing.B) {
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = Ord.Compare(a, c)
}
})
@@ -474,7 +474,7 @@ func BenchmarkOrdCompare(b *testing.B) {
large2 := make([]byte, 10000)
large2[9999] = 1
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = Ord.Compare(large1, large2)
}
})

11
v2/constant/monoid.go Normal file
View File

@@ -0,0 +1,11 @@
package constant
import (
"github.com/IBM/fp-go/v2/function"
M "github.com/IBM/fp-go/v2/monoid"
)
// Monoid returns a [M.Monoid] that returns a constant value in all operations
func Monoid[A any](a A) M.Monoid[A] {
return M.MakeMonoid(function.Constant2[A, A](a), a)
}

View File

@@ -24,6 +24,7 @@ import (
E "github.com/IBM/fp-go/v2/either"
F "github.com/IBM/fp-go/v2/function"
IOE "github.com/IBM/fp-go/v2/ioeither"
N "github.com/IBM/fp-go/v2/number"
)
var (
@@ -37,21 +38,21 @@ var (
// Benchmark core constructors
func BenchmarkLeft(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = Left[int](benchErr)
}
}
func BenchmarkRight(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = Right(42)
}
}
func BenchmarkOf(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = Of(42)
}
}
@@ -60,7 +61,7 @@ func BenchmarkFromEither_Right(b *testing.B) {
either := E.Right[error](42)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = FromEither(either)
}
}
@@ -69,7 +70,7 @@ func BenchmarkFromEither_Left(b *testing.B) {
either := E.Left[int](benchErr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = FromEither(either)
}
}
@@ -77,7 +78,7 @@ func BenchmarkFromEither_Left(b *testing.B) {
func BenchmarkFromIO(b *testing.B) {
io := func() int { return 42 }
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = FromIO(io)
}
}
@@ -85,7 +86,7 @@ func BenchmarkFromIO(b *testing.B) {
func BenchmarkFromIOEither_Right(b *testing.B) {
ioe := IOE.Of[error](42)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = FromIOEither(ioe)
}
}
@@ -93,7 +94,7 @@ func BenchmarkFromIOEither_Right(b *testing.B) {
func BenchmarkFromIOEither_Left(b *testing.B) {
ioe := IOE.Left[int](benchErr)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = FromIOEither(ioe)
}
}
@@ -103,7 +104,7 @@ func BenchmarkExecute_Right(b *testing.B) {
rioe := Right(42)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = rioe(benchCtx)()
}
}
@@ -112,7 +113,7 @@ func BenchmarkExecute_Left(b *testing.B) {
rioe := Left[int](benchErr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = rioe(benchCtx)()
}
}
@@ -123,7 +124,7 @@ func BenchmarkExecute_WithContext(b *testing.B) {
defer cancel()
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = rioe(ctx)()
}
}
@@ -131,40 +132,40 @@ func BenchmarkExecute_WithContext(b *testing.B) {
// Benchmark functor operations
func BenchmarkMonadMap_Right(b *testing.B) {
rioe := Right(42)
mapper := func(a int) int { return a * 2 }
mapper := N.Mul(2)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = MonadMap(rioe, mapper)
}
}
func BenchmarkMonadMap_Left(b *testing.B) {
rioe := Left[int](benchErr)
mapper := func(a int) int { return a * 2 }
mapper := N.Mul(2)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = MonadMap(rioe, mapper)
}
}
func BenchmarkMap_Right(b *testing.B) {
rioe := Right(42)
mapper := Map(func(a int) int { return a * 2 })
mapper := Map(N.Mul(2))
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = mapper(rioe)
}
}
func BenchmarkMap_Left(b *testing.B) {
rioe := Left[int](benchErr)
mapper := Map(func(a int) int { return a * 2 })
mapper := Map(N.Mul(2))
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = mapper(rioe)
}
}
@@ -174,7 +175,7 @@ func BenchmarkMapTo_Right(b *testing.B) {
mapper := MapTo[int](99)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = mapper(rioe)
}
}
@@ -185,7 +186,7 @@ func BenchmarkMonadChain_Right(b *testing.B) {
chainer := func(a int) ReaderIOResult[int] { return Right(a * 2) }
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = MonadChain(rioe, chainer)
}
}
@@ -195,7 +196,7 @@ func BenchmarkMonadChain_Left(b *testing.B) {
chainer := func(a int) ReaderIOResult[int] { return Right(a * 2) }
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = MonadChain(rioe, chainer)
}
}
@@ -205,7 +206,7 @@ func BenchmarkChain_Right(b *testing.B) {
chainer := Chain(func(a int) ReaderIOResult[int] { return Right(a * 2) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = chainer(rioe)
}
}
@@ -215,7 +216,7 @@ func BenchmarkChain_Left(b *testing.B) {
chainer := Chain(func(a int) ReaderIOResult[int] { return Right(a * 2) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = chainer(rioe)
}
}
@@ -225,7 +226,7 @@ func BenchmarkChainFirst_Right(b *testing.B) {
chainer := ChainFirst(func(a int) ReaderIOResult[string] { return Right("logged") })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = chainer(rioe)
}
}
@@ -235,7 +236,7 @@ func BenchmarkChainFirst_Left(b *testing.B) {
chainer := ChainFirst(func(a int) ReaderIOResult[string] { return Right("logged") })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = chainer(rioe)
}
}
@@ -244,7 +245,7 @@ func BenchmarkFlatten_Right(b *testing.B) {
nested := Right(Right(42))
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = Flatten(nested)
}
}
@@ -253,28 +254,28 @@ func BenchmarkFlatten_Left(b *testing.B) {
nested := Left[ReaderIOResult[int]](benchErr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = Flatten(nested)
}
}
// Benchmark applicative operations
func BenchmarkMonadApSeq_RightRight(b *testing.B) {
fab := Right(func(a int) int { return a * 2 })
fab := Right(N.Mul(2))
fa := Right(42)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = MonadApSeq(fab, fa)
}
}
func BenchmarkMonadApSeq_RightLeft(b *testing.B) {
fab := Right(func(a int) int { return a * 2 })
fab := Right(N.Mul(2))
fa := Left[int](benchErr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = MonadApSeq(fab, fa)
}
}
@@ -284,27 +285,27 @@ func BenchmarkMonadApSeq_LeftRight(b *testing.B) {
fa := Right(42)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = MonadApSeq(fab, fa)
}
}
func BenchmarkMonadApPar_RightRight(b *testing.B) {
fab := Right(func(a int) int { return a * 2 })
fab := Right(N.Mul(2))
fa := Right(42)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = MonadApPar(fab, fa)
}
}
func BenchmarkMonadApPar_RightLeft(b *testing.B) {
fab := Right(func(a int) int { return a * 2 })
fab := Right(N.Mul(2))
fa := Left[int](benchErr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = MonadApPar(fab, fa)
}
}
@@ -314,30 +315,30 @@ func BenchmarkMonadApPar_LeftRight(b *testing.B) {
fa := Right(42)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = MonadApPar(fab, fa)
}
}
// Benchmark execution of applicative operations
func BenchmarkExecuteApSeq_RightRight(b *testing.B) {
fab := Right(func(a int) int { return a * 2 })
fab := Right(N.Mul(2))
fa := Right(42)
rioe := MonadApSeq(fab, fa)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = rioe(benchCtx)()
}
}
func BenchmarkExecuteApPar_RightRight(b *testing.B) {
fab := Right(func(a int) int { return a * 2 })
fab := Right(N.Mul(2))
fa := Right(42)
rioe := MonadApPar(fab, fa)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = rioe(benchCtx)()
}
}
@@ -348,7 +349,7 @@ func BenchmarkAlt_RightRight(b *testing.B) {
alternative := Alt(func() ReaderIOResult[int] { return Right(99) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = alternative(rioe)
}
}
@@ -358,7 +359,7 @@ func BenchmarkAlt_LeftRight(b *testing.B) {
alternative := Alt(func() ReaderIOResult[int] { return Right(99) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = alternative(rioe)
}
}
@@ -368,7 +369,7 @@ func BenchmarkOrElse_Right(b *testing.B) {
recover := OrElse(func(e error) ReaderIOResult[int] { return Right(0) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = recover(rioe)
}
}
@@ -378,7 +379,7 @@ func BenchmarkOrElse_Left(b *testing.B) {
recover := OrElse(func(e error) ReaderIOResult[int] { return Right(0) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = recover(rioe)
}
}
@@ -389,7 +390,7 @@ func BenchmarkChainEitherK_Right(b *testing.B) {
chainer := ChainEitherK(func(a int) Either[int] { return E.Right[error](a * 2) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = chainer(rioe)
}
}
@@ -399,7 +400,7 @@ func BenchmarkChainEitherK_Left(b *testing.B) {
chainer := ChainEitherK(func(a int) Either[int] { return E.Right[error](a * 2) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = chainer(rioe)
}
}
@@ -409,7 +410,7 @@ func BenchmarkChainIOK_Right(b *testing.B) {
chainer := ChainIOK(func(a int) func() int { return func() int { return a * 2 } })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = chainer(rioe)
}
}
@@ -419,7 +420,7 @@ func BenchmarkChainIOK_Left(b *testing.B) {
chainer := ChainIOK(func(a int) func() int { return func() int { return a * 2 } })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = chainer(rioe)
}
}
@@ -429,7 +430,7 @@ func BenchmarkChainIOEitherK_Right(b *testing.B) {
chainer := ChainIOEitherK(func(a int) IOEither[int] { return IOE.Of[error](a * 2) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = chainer(rioe)
}
}
@@ -439,7 +440,7 @@ func BenchmarkChainIOEitherK_Left(b *testing.B) {
chainer := ChainIOEitherK(func(a int) IOEither[int] { return IOE.Of[error](a * 2) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = chainer(rioe)
}
}
@@ -447,7 +448,7 @@ func BenchmarkChainIOEitherK_Left(b *testing.B) {
// Benchmark context operations
func BenchmarkAsk(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = Ask()
}
}
@@ -455,7 +456,7 @@ func BenchmarkAsk(b *testing.B) {
func BenchmarkDefer(b *testing.B) {
gen := func() ReaderIOResult[int] { return Right(42) }
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = Defer(gen)
}
}
@@ -463,7 +464,7 @@ func BenchmarkDefer(b *testing.B) {
func BenchmarkMemoize(b *testing.B) {
rioe := Right(42)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = Memoize(rioe)
}
}
@@ -472,14 +473,14 @@ func BenchmarkMemoize(b *testing.B) {
func BenchmarkDelay_Construction(b *testing.B) {
rioe := Right(42)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = Delay[int](time.Millisecond)(rioe)
}
}
func BenchmarkTimer_Construction(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = Timer(time.Millisecond)
}
}
@@ -490,7 +491,7 @@ func BenchmarkTryCatch_Success(b *testing.B) {
return func() (int, error) { return 42, nil }
}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = TryCatch(f)
}
}
@@ -500,7 +501,7 @@ func BenchmarkTryCatch_Error(b *testing.B) {
return func() (int, error) { return 0, benchErr }
}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = TryCatch(f)
}
}
@@ -512,7 +513,7 @@ func BenchmarkExecuteTryCatch_Success(b *testing.B) {
rioe := TryCatch(f)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = rioe(benchCtx)()
}
}
@@ -524,7 +525,7 @@ func BenchmarkExecuteTryCatch_Error(b *testing.B) {
rioe := TryCatch(f)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = rioe(benchCtx)()
}
}
@@ -534,10 +535,10 @@ func BenchmarkPipeline_Map_Right(b *testing.B) {
rioe := Right(21)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = F.Pipe1(
rioe,
Map(func(x int) int { return x * 2 }),
Map(N.Mul(2)),
)
}
}
@@ -546,10 +547,10 @@ func BenchmarkPipeline_Map_Left(b *testing.B) {
rioe := Left[int](benchErr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = F.Pipe1(
rioe,
Map(func(x int) int { return x * 2 }),
Map(N.Mul(2)),
)
}
}
@@ -558,7 +559,7 @@ func BenchmarkPipeline_Chain_Right(b *testing.B) {
rioe := Right(21)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = F.Pipe1(
rioe,
Chain(func(x int) ReaderIOResult[int] { return Right(x * 2) }),
@@ -570,7 +571,7 @@ func BenchmarkPipeline_Chain_Left(b *testing.B) {
rioe := Left[int](benchErr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = F.Pipe1(
rioe,
Chain(func(x int) ReaderIOResult[int] { return Right(x * 2) }),
@@ -582,12 +583,12 @@ func BenchmarkPipeline_Complex_Right(b *testing.B) {
rioe := Right(10)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = F.Pipe3(
rioe,
Map(func(x int) int { return x * 2 }),
Map(N.Mul(2)),
Chain(func(x int) ReaderIOResult[int] { return Right(x + 1) }),
Map(func(x int) int { return x * 2 }),
Map(N.Mul(2)),
)
}
}
@@ -596,12 +597,12 @@ func BenchmarkPipeline_Complex_Left(b *testing.B) {
rioe := Left[int](benchErr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchRIOE = F.Pipe3(
rioe,
Map(func(x int) int { return x * 2 }),
Map(N.Mul(2)),
Chain(func(x int) ReaderIOResult[int] { return Right(x + 1) }),
Map(func(x int) int { return x * 2 }),
Map(N.Mul(2)),
)
}
}
@@ -609,13 +610,13 @@ func BenchmarkPipeline_Complex_Left(b *testing.B) {
func BenchmarkExecutePipeline_Complex_Right(b *testing.B) {
rioe := F.Pipe3(
Right(10),
Map(func(x int) int { return x * 2 }),
Map(N.Mul(2)),
Chain(func(x int) ReaderIOResult[int] { return Right(x + 1) }),
Map(func(x int) int { return x * 2 }),
Map(N.Mul(2)),
)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = rioe(benchCtx)()
}
}
@@ -624,7 +625,7 @@ func BenchmarkExecutePipeline_Complex_Right(b *testing.B) {
func BenchmarkDo(b *testing.B) {
type State struct{ value int }
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = Do(State{})
}
}
@@ -642,7 +643,7 @@ func BenchmarkBind_Right(b *testing.B) {
)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = binder(initial)
}
}
@@ -658,7 +659,7 @@ func BenchmarkLet_Right(b *testing.B) {
)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = letter(initial)
}
}
@@ -674,7 +675,7 @@ func BenchmarkApS_Right(b *testing.B) {
)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = aps(initial)
}
}
@@ -687,7 +688,7 @@ func BenchmarkTraverseArray_Empty(b *testing.B) {
})
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = traverser(arr)
}
}
@@ -699,7 +700,7 @@ func BenchmarkTraverseArray_Small(b *testing.B) {
})
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = traverser(arr)
}
}
@@ -714,7 +715,7 @@ func BenchmarkTraverseArray_Medium(b *testing.B) {
})
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = traverser(arr)
}
}
@@ -726,7 +727,7 @@ func BenchmarkTraverseArraySeq_Small(b *testing.B) {
})
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = traverser(arr)
}
}
@@ -738,7 +739,7 @@ func BenchmarkTraverseArrayPar_Small(b *testing.B) {
})
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = traverser(arr)
}
}
@@ -751,7 +752,7 @@ func BenchmarkSequenceArray_Small(b *testing.B) {
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = SequenceArray(arr)
}
}
@@ -763,7 +764,7 @@ func BenchmarkExecuteTraverseArray_Small(b *testing.B) {
})(arr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = rioe(benchCtx)()
}
}
@@ -775,7 +776,7 @@ func BenchmarkExecuteTraverseArraySeq_Small(b *testing.B) {
})(arr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = rioe(benchCtx)()
}
}
@@ -787,7 +788,7 @@ func BenchmarkExecuteTraverseArrayPar_Small(b *testing.B) {
})(arr)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = rioe(benchCtx)()
}
}
@@ -800,7 +801,7 @@ func BenchmarkTraverseRecord_Small(b *testing.B) {
})
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = traverser(rec)
}
}
@@ -813,7 +814,7 @@ func BenchmarkSequenceRecord_Small(b *testing.B) {
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = SequenceRecord(rec)
}
}
@@ -826,7 +827,7 @@ func BenchmarkWithResource_Success(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = WithResource[int](acquire, release)(body)
}
}
@@ -839,7 +840,7 @@ func BenchmarkExecuteWithResource_Success(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = rioe(benchCtx)()
}
}
@@ -852,7 +853,7 @@ func BenchmarkExecuteWithResource_ErrorInBody(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = rioe(benchCtx)()
}
}
@@ -865,13 +866,13 @@ func BenchmarkExecute_CanceledContext(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = rioe(ctx)()
}
}
func BenchmarkExecuteApPar_CanceledContext(b *testing.B) {
fab := Right(func(a int) int { return a * 2 })
fab := Right(N.Mul(2))
fa := Right(42)
rioe := MonadApPar(fab, fa)
ctx, cancel := context.WithCancel(benchCtx)
@@ -879,7 +880,7 @@ func BenchmarkExecuteApPar_CanceledContext(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = rioe(ctx)()
}
}

View File

@@ -26,6 +26,7 @@ import (
IOG "github.com/IBM/fp-go/v2/io"
IOE "github.com/IBM/fp-go/v2/ioeither"
M "github.com/IBM/fp-go/v2/monoid"
N "github.com/IBM/fp-go/v2/number"
O "github.com/IBM/fp-go/v2/option"
R "github.com/IBM/fp-go/v2/reader"
"github.com/stretchr/testify/assert"
@@ -77,27 +78,27 @@ func TestOf(t *testing.T) {
func TestMonadMap(t *testing.T) {
t.Run("Map over Right", func(t *testing.T) {
result := MonadMap(Of(5), func(x int) int { return x * 2 })
result := MonadMap(Of(5), N.Mul(2))
assert.Equal(t, E.Right[error](10), result(context.Background())())
})
t.Run("Map over Left", func(t *testing.T) {
err := errors.New("test error")
result := MonadMap(Left[int](err), func(x int) int { return x * 2 })
result := MonadMap(Left[int](err), N.Mul(2))
assert.Equal(t, E.Left[int](err), result(context.Background())())
})
}
func TestMap(t *testing.T) {
t.Run("Map with success", func(t *testing.T) {
mapper := Map(func(x int) int { return x * 2 })
mapper := Map(N.Mul(2))
result := mapper(Of(5))
assert.Equal(t, E.Right[error](10), result(context.Background())())
})
t.Run("Map with error", func(t *testing.T) {
err := errors.New("test error")
mapper := Map(func(x int) int { return x * 2 })
mapper := Map(N.Mul(2))
result := mapper(Left[int](err))
assert.Equal(t, E.Left[int](err), result(context.Background())())
})
@@ -182,7 +183,7 @@ func TestChainFirst(t *testing.T) {
func TestMonadApSeq(t *testing.T) {
t.Run("ApSeq with success", func(t *testing.T) {
fab := Of(func(x int) int { return x * 2 })
fab := Of(N.Mul(2))
fa := Of(5)
result := MonadApSeq(fab, fa)
assert.Equal(t, E.Right[error](10), result(context.Background())())
@@ -198,7 +199,7 @@ func TestMonadApSeq(t *testing.T) {
t.Run("ApSeq with error in value", func(t *testing.T) {
err := errors.New("test error")
fab := Of(func(x int) int { return x * 2 })
fab := Of(N.Mul(2))
fa := Left[int](err)
result := MonadApSeq(fab, fa)
assert.Equal(t, E.Left[int](err), result(context.Background())())
@@ -207,7 +208,7 @@ func TestMonadApSeq(t *testing.T) {
func TestApSeq(t *testing.T) {
fa := Of(5)
fab := Of(func(x int) int { return x * 2 })
fab := Of(N.Mul(2))
result := MonadApSeq(fab, fa)
assert.Equal(t, E.Right[error](10), result(context.Background())())
}
@@ -215,7 +216,7 @@ func TestApSeq(t *testing.T) {
func TestApPar(t *testing.T) {
t.Run("ApPar with success", func(t *testing.T) {
fa := Of(5)
fab := Of(func(x int) int { return x * 2 })
fab := Of(N.Mul(2))
result := MonadApPar(fab, fa)
assert.Equal(t, E.Right[error](10), result(context.Background())())
})
@@ -224,7 +225,7 @@ func TestApPar(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
fa := Of(5)
fab := Of(func(x int) int { return x * 2 })
fab := Of(N.Mul(2))
result := MonadApPar(fab, fa)
res := result(ctx)()
assert.True(t, E.IsLeft(res))
@@ -587,14 +588,14 @@ func TestFlatten(t *testing.T) {
}
func TestMonadFlap(t *testing.T) {
fab := Of(func(x int) int { return x * 2 })
fab := Of(N.Mul(2))
result := MonadFlap(fab, 5)
assert.Equal(t, E.Right[error](10), result(context.Background())())
}
func TestFlap(t *testing.T) {
flapper := Flap[int](5)
result := flapper(Of(func(x int) int { return x * 2 }))
result := flapper(Of(N.Mul(2)))
assert.Equal(t, E.Right[error](10), result(context.Background())())
}

View File

@@ -312,7 +312,7 @@ func TestMonadChainFirstLeft(t *testing.T) {
Left[int](originalErr),
func(e error) ReaderIOResult[int] {
capturedError = e
return Right[int](999) // This Right value is ignored
return Right(999) // This Right value is ignored
},
)
actualResult := result(ctx)()
@@ -324,7 +324,7 @@ func TestMonadChainFirstLeft(t *testing.T) {
t.Run("Right value passes through", func(t *testing.T) {
sideEffectCalled := false
result := MonadChainFirstLeft(
Right[int](42),
Right(42),
func(e error) ReaderIOResult[int] {
sideEffectCalled = true
return Left[int](fmt.Errorf("should not be called"))
@@ -343,7 +343,7 @@ func TestMonadChainFirstLeft(t *testing.T) {
func(e error) ReaderIOResult[int] {
effectCount++
// Try to return Right, but original Left should still be returned
return Right[int](999)
return Right(999)
},
)
actualResult := result(ctx)()
@@ -378,7 +378,7 @@ func TestChainFirstLeft(t *testing.T) {
originalErr := fmt.Errorf("test error")
chainFn := ChainFirstLeft[int](func(e error) ReaderIOResult[int] {
captured = e
return Right[int](42) // This Right is ignored
return Right(42) // This Right is ignored
})
result := F.Pipe1(
Left[int](originalErr),
@@ -394,10 +394,10 @@ func TestChainFirstLeft(t *testing.T) {
called := false
chainFn := ChainFirstLeft[int](func(e error) ReaderIOResult[int] {
called = true
return Right[int](0)
return Right(0)
})
result := F.Pipe1(
Right[int](100),
Right(100),
chainFn,
)
assert.False(t, called)
@@ -409,7 +409,7 @@ func TestChainFirstLeft(t *testing.T) {
originalErr := fmt.Errorf("original")
chainFn := ChainFirstLeft[int](func(e error) ReaderIOResult[int] {
// Try to return Right, but original Left should still be returned
return Right[int](999)
return Right(999)
})
result := F.Pipe1(

View File

@@ -16,8 +16,8 @@
package readerioresult
import (
"github.com/IBM/fp-go/v2/array"
"github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/array"
"github.com/IBM/fp-go/v2/internal/record"
)
@@ -29,7 +29,7 @@ import (
//
// Returns a function that transforms an array into a ReaderIOResult of an array.
func TraverseArray[A, B any](f Kleisli[A, B]) Kleisli[[]A, []B] {
return array.Traverse[[]A](
return array.Traverse(
Of[[]B],
Map[[]B, func(B) []B],
Ap[[]B, B],
@@ -46,7 +46,7 @@ func TraverseArray[A, B any](f Kleisli[A, B]) Kleisli[[]A, []B] {
//
// Returns a function that transforms an array into a ReaderIOResult of an array.
func TraverseArrayWithIndex[A, B any](f func(int, A) ReaderIOResult[B]) Kleisli[[]A, []B] {
return array.TraverseWithIndex[[]A](
return array.TraverseWithIndex(
Of[[]B],
Map[[]B, func(B) []B],
Ap[[]B, B],
@@ -135,22 +135,20 @@ func MonadTraverseArraySeq[A, B any](as []A, f Kleisli[A, B]) ReaderIOResult[[]B
//
// Returns a function that transforms an array into a ReaderIOResult of an array.
func TraverseArraySeq[A, B any](f Kleisli[A, B]) Kleisli[[]A, []B] {
return array.Traverse[[]A](
return array.Traverse(
Of[[]B],
Map[[]B, func(B) []B],
ApSeq[[]B, B],
f,
)
}
// TraverseArrayWithIndexSeq uses transforms an array [[]A] into [[]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[[]B]]
func TraverseArrayWithIndexSeq[A, B any](f func(int, A) ReaderIOResult[B]) Kleisli[[]A, []B] {
return array.TraverseWithIndex[[]A](
return array.TraverseWithIndex(
Of[[]B],
Map[[]B, func(B) []B],
ApSeq[[]B, B],
f,
)
}
@@ -230,22 +228,20 @@ func MonadTraverseArrayPar[A, B any](as []A, f Kleisli[A, B]) ReaderIOResult[[]B
//
// Returns a function that transforms an array into a ReaderIOResult of an array.
func TraverseArrayPar[A, B any](f Kleisli[A, B]) Kleisli[[]A, []B] {
return array.Traverse[[]A](
return array.Traverse(
Of[[]B],
Map[[]B, func(B) []B],
ApPar[[]B, B],
f,
)
}
// TraverseArrayWithIndexPar uses transforms an array [[]A] into [[]ReaderIOResult[B]] and then resolves that into a [ReaderIOResult[[]B]]
func TraverseArrayWithIndexPar[A, B any](f func(int, A) ReaderIOResult[B]) Kleisli[[]A, []B] {
return array.TraverseWithIndex[[]A](
return array.TraverseWithIndex(
Of[[]B],
Map[[]B, func(B) []B],
ApPar[[]B, B],
f,
)
}

View File

@@ -249,7 +249,7 @@ func TestMultiTokenStringRepresentation(t *testing.T) {
// Benchmark tests
func BenchmarkMakeToken(b *testing.B) {
for i := 0; i < b.N; i++ {
for b.Loop() {
MakeToken[int]("BenchToken")
}
}
@@ -259,13 +259,13 @@ func BenchmarkTokenUnerase(b *testing.B) {
value := any(42)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
token.Unerase(value)
}
}
func BenchmarkMakeMultiToken(b *testing.B) {
for i := 0; i < b.N; i++ {
for b.Loop() {
MakeMultiToken[int]("BenchMulti")
}
}

View File

@@ -4,16 +4,17 @@ import (
"fmt"
"testing"
A "github.com/IBM/fp-go/v2/array"
TST "github.com/IBM/fp-go/v2/internal/testing"
"github.com/stretchr/testify/assert"
)
func TestCompactArray(t *testing.T) {
ar := []Either[string, string]{
ar := A.From(
Of[string]("ok"),
Left[string]("err"),
Of[string]("ok"),
}
)
res := CompactArray(ar)
assert.Equal(t, 2, len(res))

View File

@@ -58,7 +58,7 @@ func FromIO[E any, IO ~func() A, A any](f IO) Either[E, A] {
//
// Example:
//
// fab := either.Right[error](func(x int) int { return x * 2 })
// fab := either.Right[error](N.Mul(2))
// fa := either.Right[error](21)
// result := either.MonadAp(fab, fa) // Right(42)
func MonadAp[B, E, A any](fab Either[E, func(a A) B], fa Either[E, A]) Either[E, B] {
@@ -81,7 +81,7 @@ func Ap[B, E, A any](fa Either[E, A]) Operator[E, func(A) B, B] {
//
// result := either.MonadMap(
// either.Right[error](21),
// func(x int) int { return x * 2 },
// N.Mul(2),
// ) // Right(42)
//
//go:inline

View File

@@ -20,6 +20,7 @@ import (
"testing"
F "github.com/IBM/fp-go/v2/function"
N "github.com/IBM/fp-go/v2/number"
)
var (
@@ -33,21 +34,21 @@ var (
// Benchmark core constructors
func BenchmarkLeft(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = Left[int](errBench)
}
}
func BenchmarkRight(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = Right[error](42)
}
}
func BenchmarkOf(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = Of[error](42)
}
}
@@ -57,7 +58,7 @@ func BenchmarkIsLeft(b *testing.B) {
left := Left[int](errBench)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchBool = IsLeft(left)
}
}
@@ -66,7 +67,7 @@ func BenchmarkIsRight(b *testing.B) {
right := Right[error](42)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchBool = IsRight(right)
}
}
@@ -75,10 +76,10 @@ func BenchmarkIsRight(b *testing.B) {
func BenchmarkMonadFold_Right(b *testing.B) {
right := Right[error](42)
onLeft := func(e error) int { return 0 }
onRight := func(a int) int { return a * 2 }
onRight := N.Mul(2)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchInt = MonadFold(right, onLeft, onRight)
}
}
@@ -86,10 +87,10 @@ func BenchmarkMonadFold_Right(b *testing.B) {
func BenchmarkMonadFold_Left(b *testing.B) {
left := Left[int](errBench)
onLeft := func(e error) int { return 0 }
onRight := func(a int) int { return a * 2 }
onRight := N.Mul(2)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchInt = MonadFold(left, onLeft, onRight)
}
}
@@ -98,11 +99,11 @@ func BenchmarkFold_Right(b *testing.B) {
right := Right[error](42)
folder := Fold(
func(e error) int { return 0 },
func(a int) int { return a * 2 },
N.Mul(2),
)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchInt = folder(right)
}
}
@@ -111,11 +112,11 @@ func BenchmarkFold_Left(b *testing.B) {
left := Left[int](errBench)
folder := Fold(
func(e error) int { return 0 },
func(a int) int { return a * 2 },
N.Mul(2),
)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchInt = folder(left)
}
}
@@ -125,7 +126,7 @@ func BenchmarkUnwrap_Right(b *testing.B) {
right := Right[error](42)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchInt, _ = Unwrap(right)
}
}
@@ -134,7 +135,7 @@ func BenchmarkUnwrap_Left(b *testing.B) {
left := Left[int](errBench)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchInt, _ = Unwrap(left)
}
}
@@ -143,7 +144,7 @@ func BenchmarkUnwrapError_Right(b *testing.B) {
right := Right[error](42)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchInt, _ = UnwrapError(right)
}
}
@@ -152,7 +153,7 @@ func BenchmarkUnwrapError_Left(b *testing.B) {
left := Left[int](errBench)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchInt, _ = UnwrapError(left)
}
}
@@ -160,40 +161,40 @@ func BenchmarkUnwrapError_Left(b *testing.B) {
// Benchmark functor operations
func BenchmarkMonadMap_Right(b *testing.B) {
right := Right[error](42)
mapper := func(a int) int { return a * 2 }
mapper := N.Mul(2)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = MonadMap(right, mapper)
}
}
func BenchmarkMonadMap_Left(b *testing.B) {
left := Left[int](errBench)
mapper := func(a int) int { return a * 2 }
mapper := N.Mul(2)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = MonadMap(left, mapper)
}
}
func BenchmarkMap_Right(b *testing.B) {
right := Right[error](42)
mapper := Map[error](func(a int) int { return a * 2 })
mapper := Map[error](N.Mul(2))
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = mapper(right)
}
}
func BenchmarkMap_Left(b *testing.B) {
left := Left[int](errBench)
mapper := Map[error](func(a int) int { return a * 2 })
mapper := Map[error](N.Mul(2))
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = mapper(left)
}
}
@@ -203,7 +204,7 @@ func BenchmarkMapLeft_Right(b *testing.B) {
mapper := MapLeft[int](error.Error)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = mapper(right)
}
}
@@ -213,7 +214,7 @@ func BenchmarkMapLeft_Left(b *testing.B) {
mapper := MapLeft[int](error.Error)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = mapper(left)
}
}
@@ -226,7 +227,7 @@ func BenchmarkBiMap_Right(b *testing.B) {
)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = mapper(right)
}
}
@@ -239,7 +240,7 @@ func BenchmarkBiMap_Left(b *testing.B) {
)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = mapper(left)
}
}
@@ -250,7 +251,7 @@ func BenchmarkMonadChain_Right(b *testing.B) {
chainer := func(a int) Either[error, int] { return Right[error](a * 2) }
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = MonadChain(right, chainer)
}
}
@@ -260,7 +261,7 @@ func BenchmarkMonadChain_Left(b *testing.B) {
chainer := func(a int) Either[error, int] { return Right[error](a * 2) }
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = MonadChain(left, chainer)
}
}
@@ -270,7 +271,7 @@ func BenchmarkChain_Right(b *testing.B) {
chainer := Chain(func(a int) Either[error, int] { return Right[error](a * 2) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = chainer(right)
}
}
@@ -280,7 +281,7 @@ func BenchmarkChain_Left(b *testing.B) {
chainer := Chain(func(a int) Either[error, int] { return Right[error](a * 2) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = chainer(left)
}
}
@@ -290,7 +291,7 @@ func BenchmarkChainFirst_Right(b *testing.B) {
chainer := ChainFirst(func(a int) Either[error, string] { return Right[error]("logged") })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = chainer(right)
}
}
@@ -300,7 +301,7 @@ func BenchmarkChainFirst_Left(b *testing.B) {
chainer := ChainFirst(func(a int) Either[error, string] { return Right[error]("logged") })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = chainer(left)
}
}
@@ -309,7 +310,7 @@ func BenchmarkFlatten_Right(b *testing.B) {
nested := Right[error](Right[error](42))
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = Flatten(nested)
}
}
@@ -318,28 +319,28 @@ func BenchmarkFlatten_Left(b *testing.B) {
nested := Left[Either[error, int]](errBench)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = Flatten(nested)
}
}
// Benchmark applicative operations
func BenchmarkMonadAp_RightRight(b *testing.B) {
fab := Right[error](func(a int) int { return a * 2 })
fab := Right[error](N.Mul(2))
fa := Right[error](42)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = MonadAp(fab, fa)
}
}
func BenchmarkMonadAp_RightLeft(b *testing.B) {
fab := Right[error](func(a int) int { return a * 2 })
fab := Right[error](N.Mul(2))
fa := Left[int](errBench)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = MonadAp(fab, fa)
}
}
@@ -349,18 +350,18 @@ func BenchmarkMonadAp_LeftRight(b *testing.B) {
fa := Right[error](42)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = MonadAp(fab, fa)
}
}
func BenchmarkAp_RightRight(b *testing.B) {
fab := Right[error](func(a int) int { return a * 2 })
fab := Right[error](N.Mul(2))
fa := Right[error](42)
ap := Ap[int](fa)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = ap(fab)
}
}
@@ -371,7 +372,7 @@ func BenchmarkAlt_RightRight(b *testing.B) {
alternative := Alt(func() Either[error, int] { return Right[error](99) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = alternative(right)
}
}
@@ -381,7 +382,7 @@ func BenchmarkAlt_LeftRight(b *testing.B) {
alternative := Alt(func() Either[error, int] { return Right[error](99) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = alternative(left)
}
}
@@ -391,7 +392,7 @@ func BenchmarkOrElse_Right(b *testing.B) {
recover := OrElse(func(e error) Either[error, int] { return Right[error](0) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = recover(right)
}
}
@@ -401,7 +402,7 @@ func BenchmarkOrElse_Left(b *testing.B) {
recover := OrElse(func(e error) Either[error, int] { return Right[error](0) })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = recover(left)
}
}
@@ -410,7 +411,7 @@ func BenchmarkOrElse_Left(b *testing.B) {
func BenchmarkTryCatch_Success(b *testing.B) {
onThrow := func(err error) error { return err }
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = TryCatch(42, nil, onThrow)
}
}
@@ -418,21 +419,21 @@ func BenchmarkTryCatch_Success(b *testing.B) {
func BenchmarkTryCatch_Error(b *testing.B) {
onThrow := func(err error) error { return err }
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = TryCatch(0, errBench, onThrow)
}
}
func BenchmarkTryCatchError_Success(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = TryCatchError(42, nil)
}
}
func BenchmarkTryCatchError_Error(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = TryCatchError(0, errBench)
}
}
@@ -441,7 +442,7 @@ func BenchmarkSwap_Right(b *testing.B) {
right := Right[error](42)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = Swap(right)
}
}
@@ -450,7 +451,7 @@ func BenchmarkSwap_Left(b *testing.B) {
left := Left[int](errBench)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = Swap(left)
}
}
@@ -460,7 +461,7 @@ func BenchmarkGetOrElse_Right(b *testing.B) {
getter := GetOrElse(func(e error) int { return 0 })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchInt = getter(right)
}
}
@@ -470,7 +471,7 @@ func BenchmarkGetOrElse_Left(b *testing.B) {
getter := GetOrElse(func(e error) int { return 0 })
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchInt = getter(left)
}
}
@@ -480,10 +481,10 @@ func BenchmarkPipeline_Map_Right(b *testing.B) {
right := Right[error](21)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = F.Pipe1(
right,
Map[error](func(x int) int { return x * 2 }),
Map[error](N.Mul(2)),
)
}
}
@@ -492,10 +493,10 @@ func BenchmarkPipeline_Map_Left(b *testing.B) {
left := Left[int](errBench)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = F.Pipe1(
left,
Map[error](func(x int) int { return x * 2 }),
Map[error](N.Mul(2)),
)
}
}
@@ -504,7 +505,7 @@ func BenchmarkPipeline_Chain_Right(b *testing.B) {
right := Right[error](21)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = F.Pipe1(
right,
Chain(func(x int) Either[error, int] { return Right[error](x * 2) }),
@@ -516,7 +517,7 @@ func BenchmarkPipeline_Chain_Left(b *testing.B) {
left := Left[int](errBench)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = F.Pipe1(
left,
Chain(func(x int) Either[error, int] { return Right[error](x * 2) }),
@@ -528,12 +529,12 @@ func BenchmarkPipeline_Complex_Right(b *testing.B) {
right := Right[error](10)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = F.Pipe3(
right,
Map[error](func(x int) int { return x * 2 }),
Map[error](N.Mul(2)),
Chain(func(x int) Either[error, int] { return Right[error](x + 1) }),
Map[error](func(x int) int { return x * 2 }),
Map[error](N.Mul(2)),
)
}
}
@@ -542,12 +543,12 @@ func BenchmarkPipeline_Complex_Left(b *testing.B) {
left := Left[int](errBench)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = F.Pipe3(
left,
Map[error](func(x int) int { return x * 2 }),
Map[error](N.Mul(2)),
Chain(func(x int) Either[error, int] { return Right[error](x + 1) }),
Map[error](func(x int) int { return x * 2 }),
Map[error](N.Mul(2)),
)
}
}
@@ -559,7 +560,7 @@ func BenchmarkMonadSequence2_RightRight(b *testing.B) {
f := func(a, b int) Either[error, int] { return Right[error](a + b) }
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = MonadSequence2(e1, e2, f)
}
}
@@ -570,7 +571,7 @@ func BenchmarkMonadSequence2_LeftRight(b *testing.B) {
f := func(a, b int) Either[error, int] { return Right[error](a + b) }
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = MonadSequence2(e1, e2, f)
}
}
@@ -582,7 +583,7 @@ func BenchmarkMonadSequence3_RightRightRight(b *testing.B) {
f := func(a, b, c int) Either[error, int] { return Right[error](a + b + c) }
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchResult = MonadSequence3(e1, e2, e3, f)
}
}
@@ -591,7 +592,7 @@ func BenchmarkMonadSequence3_RightRightRight(b *testing.B) {
func BenchmarkDo(b *testing.B) {
type State struct{ value int }
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = Do[error](State{})
}
}
@@ -609,7 +610,7 @@ func BenchmarkBind_Right(b *testing.B) {
)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = binder(initial)
}
}
@@ -625,7 +626,7 @@ func BenchmarkLet_Right(b *testing.B) {
)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = letter(initial)
}
}
@@ -635,7 +636,7 @@ func BenchmarkString_Right(b *testing.B) {
right := Right[error](42)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchString = right.String()
}
}
@@ -644,7 +645,7 @@ func BenchmarkString_Left(b *testing.B) {
left := Left[int](errBench)
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchString = left.String()
}
}

View File

@@ -66,7 +66,7 @@ func TestUnwrapError(t *testing.T) {
func TestReduce(t *testing.T) {
s := S.Semigroup()
s := S.Semigroup
assert.Equal(t, "foobar", F.Pipe1(Right[string]("bar"), Reduce[string](s.Concat, "foo")))
assert.Equal(t, "foo", F.Pipe1(Left[string]("bar"), Reduce[string](s.Concat, "foo")))

View File

@@ -46,7 +46,7 @@ func _log[E, A any](left func(string, ...any), right func(string, ...any), prefi
// result := F.Pipe2(
// either.Right[error](42),
// logger("Processing"),
// either.Map(func(x int) int { return x * 2 }),
// either.Map(N.Mul(2)),
// )
// // Logs: "Processing: 42"
// // result is Right(84)

142
v2/either/validation.go Normal file
View File

@@ -0,0 +1,142 @@
// Copyright (c) 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 either
import (
F "github.com/IBM/fp-go/v2/function"
S "github.com/IBM/fp-go/v2/semigroup"
)
// MonadApV is the applicative validation functor that combines errors using a semigroup.
//
// Unlike the standard [MonadAp] which short-circuits on the first Left (error),
// MonadApV accumulates all errors using the provided semigroup's Concat operation.
// This is particularly useful for validation scenarios where you want to collect
// all validation errors rather than stopping at the first one.
//
// The function takes a semigroup for combining errors and returns a function that
// applies a wrapped function to a wrapped value, accumulating errors if both are Left.
//
// Behavior:
// - If both fab and fa are Left, combines their errors using sg.Concat
// - If only fab is Left, returns Left with fab's error
// - If only fa is Left, returns Left with fa's error
// - If both are Right, applies the function and returns Right with the result
//
// Type Parameters:
// - B: The result type after applying the function
// - E: The error type (must support the semigroup operation)
// - A: The input type to the function
//
// Parameters:
// - sg: A semigroup that defines how to combine two error values
//
// Returns:
// - A function that takes a wrapped function and a wrapped value, returning
// Either[E, B] with accumulated errors or the computed result
//
// Example:
//
// // Define a semigroup that concatenates error messages
// errorSemigroup := semigroup.MakeSemigroup(func(e1, e2 string) string {
// return e1 + "; " + e2
// })
//
// // Create the validation applicative
// applyV := either.MonadApV[int](errorSemigroup)
//
// // Both are errors - errors get combined
// fab := either.Left[func(int) int]("error1")
// fa := either.Left[int]("error2")
// result := applyV(fab, fa) // Left("error1; error2")
//
// // One error - returns that error
// fab2 := either.Right[string](N.Mul(2))
// fa2 := either.Left[int]("validation failed")
// result2 := applyV(fab2, fa2) // Left("validation failed")
//
// // Both success - applies function
// fab3 := either.Right[string](N.Mul(2))
// fa3 := either.Right[string](21)
// result3 := applyV(fab3, fa3) // Right(42)
func MonadApV[B, E, A any](sg S.Semigroup[E]) func(fab Either[E, func(a A) B], fa Either[E, A]) Either[E, B] {
c := F.Bind2of2(sg.Concat)
return func(fab Either[E, func(a A) B], fa Either[E, A]) Either[E, B] {
return MonadFold(fab, func(eab E) Either[E, B] {
return MonadFold(fa, F.Flow2(c(eab), Left[B]), F.Constant1[A](Left[B](eab)))
}, func(ab func(A) B) Either[E, B] {
return MonadFold(fa, Left[B, E], F.Flow2(ab, Right[E, B]))
})
}
}
// ApV is the curried version of [MonadApV] that combines errors using a semigroup.
//
// This function provides a more convenient API for validation scenarios by currying
// the arguments. It first takes the value to validate, then returns a function that
// takes the validation function. This allows for a more natural composition style.
//
// Like [MonadApV], this accumulates all errors using the provided semigroup instead
// of short-circuiting on the first error. This is the key difference from the
// standard [Ap] function.
//
// Type Parameters:
// - B: The result type after applying the function
// - E: The error type (must support the semigroup operation)
// - A: The input type to the function
//
// Parameters:
// - sg: A semigroup that defines how to combine two error values
//
// Returns:
// - A function that takes a value Either[E, A] and returns an Operator that
// applies validation functions while accumulating errors
//
// Example:
//
// // Define a semigroup for combining validation errors
// type ValidationError struct {
// Errors []string
// }
// errorSemigroup := semigroup.MakeSemigroup(func(e1, e2 ValidationError) ValidationError {
// return ValidationError{Errors: append(e1.Errors, e2.Errors...)}
// })
//
// // Create validators
// validatePositive := func(x int) either.Either[ValidationError, int] {
// if x > 0 {
// return either.Right[ValidationError](x)
// }
// return either.Left[int](ValidationError{Errors: []string{"must be positive"}})
// }
//
// // Use ApV for validation
// applyValidation := either.ApV[int](errorSemigroup)
// value := either.Left[int](ValidationError{Errors: []string{"invalid input"}})
// validator := either.Left[func(int) int](ValidationError{Errors: []string{"invalid validator"}})
//
// result := applyValidation(value)(validator)
// // Left(ValidationError{Errors: []string{"invalid validator", "invalid input"}})
func ApV[B, E, A any](sg S.Semigroup[E]) func(fa Either[E, A]) Operator[E, func(A) B, B] {
c := F.Bind2of2(sg.Concat)
return func(fa Either[E, A]) Operator[E, func(A) B, B] {
return Fold(func(eab E) Either[E, B] {
return MonadFold(fa, F.Flow2(c(eab), Left[B]), F.Constant1[A](Left[B](eab)))
}, func(ab func(A) B) Either[E, B] {
return MonadFold(fa, Left[B, E], F.Flow2(ab, Right[E, B]))
})
}
}

View File

@@ -0,0 +1,381 @@
// Copyright (c) 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 either
import (
"testing"
F "github.com/IBM/fp-go/v2/function"
N "github.com/IBM/fp-go/v2/number"
S "github.com/IBM/fp-go/v2/semigroup"
"github.com/stretchr/testify/assert"
)
// TestMonadApV_BothRight tests MonadApV when both function and value are Right
func TestMonadApV_BothRight(t *testing.T) {
// Create a semigroup for string concatenation
sg := S.MakeSemigroup(func(a, b string) string {
return a + "; " + b
})
// Create the validation applicative
applyV := MonadApV[int, string, int](sg)
// Both are Right - should apply function
fab := Right[string](N.Mul(2))
fa := Right[string](21)
result := applyV(fab, fa)
assert.True(t, IsRight(result))
assert.Equal(t, Right[string](42), result)
}
// TestMonadApV_BothLeft tests MonadApV when both function and value are Left
func TestMonadApV_BothLeft(t *testing.T) {
// Create a semigroup for string concatenation
sg := S.MakeSemigroup(func(a, b string) string {
return a + "; " + b
})
// Create the validation applicative
applyV := MonadApV[int, string, int](sg)
// Both are Left - should combine errors
fab := Left[func(int) int]("error1")
fa := Left[int]("error2")
result := applyV(fab, fa)
assert.True(t, IsLeft(result))
// When both are Left, errors are combined as: fa error + fab error
assert.Equal(t, Left[int]("error2; error1"), result)
}
// TestMonadApV_LeftFunction tests MonadApV when function is Left and value is Right
func TestMonadApV_LeftFunction(t *testing.T) {
// Create a semigroup for string concatenation
sg := S.MakeSemigroup(func(a, b string) string {
return a + "; " + b
})
// Create the validation applicative
applyV := MonadApV[int, string, int](sg)
// Function is Left, value is Right - should return function's error
fab := Left[func(int) int]("function error")
fa := Right[string](21)
result := applyV(fab, fa)
assert.True(t, IsLeft(result))
assert.Equal(t, Left[int]("function error"), result)
}
// TestMonadApV_LeftValue tests MonadApV when function is Right and value is Left
func TestMonadApV_LeftValue(t *testing.T) {
// Create a semigroup for string concatenation
sg := S.MakeSemigroup(func(a, b string) string {
return a + "; " + b
})
// Create the validation applicative
applyV := MonadApV[int, string, int](sg)
// Function is Right, value is Left - should return value's error
fab := Right[string](N.Mul(2))
fa := Left[int]("value error")
result := applyV(fab, fa)
assert.True(t, IsLeft(result))
assert.Equal(t, Left[int]("value error"), result)
}
// TestMonadApV_WithSliceSemigroup tests MonadApV with a slice-based semigroup
func TestMonadApV_WithSliceSemigroup(t *testing.T) {
// Create a semigroup that concatenates slices
sg := S.MakeSemigroup(func(a, b []string) []string {
return append(a, b...)
})
// Create the validation applicative
applyV := MonadApV[string, []string, string](sg)
// Both are Left with slice errors
fab := Left[func(string) string]([]string{"error1", "error2"})
fa := Left[string]([]string{"error3", "error4"})
result := applyV(fab, fa)
assert.True(t, IsLeft(result))
// When both are Left, errors are combined as: fa errors + fab errors
expected := Left[string]([]string{"error3", "error4", "error1", "error2"})
assert.Equal(t, expected, result)
}
// TestMonadApV_ComplexFunction tests MonadApV with a more complex function
func TestMonadApV_ComplexFunction(t *testing.T) {
// Create a semigroup for string concatenation
sg := S.MakeSemigroup(func(a, b string) string {
return a + " | " + b
})
// Create the validation applicative
applyV := MonadApV[string, string, int](sg)
// Test with a function that transforms the value
fab := Right[string](func(x int) string {
if x > 0 {
return "positive"
}
return "non-positive"
})
fa := Right[string](42)
result := applyV(fab, fa)
assert.True(t, IsRight(result))
assert.Equal(t, Right[string]("positive"), result)
}
// TestApV_BothRight tests ApV when both function and value are Right
func TestApV_BothRight(t *testing.T) {
// Create a semigroup for string concatenation
sg := S.MakeSemigroup(func(a, b string) string {
return a + "; " + b
})
// Create the validation applicative
applyV := ApV[int, string, int](sg)
// Both are Right - should apply function
fa := Right[string](21)
fab := Right[string](N.Mul(2))
result := applyV(fa)(fab)
assert.True(t, IsRight(result))
assert.Equal(t, Right[string](42), result)
}
// TestApV_BothLeft tests ApV when both function and value are Left
func TestApV_BothLeft(t *testing.T) {
// Create a semigroup for string concatenation
sg := S.MakeSemigroup(func(a, b string) string {
return a + "; " + b
})
// Create the validation applicative
applyV := ApV[int, string, int](sg)
// Both are Left - should combine errors
fa := Left[int]("error2")
fab := Left[func(int) int]("error1")
result := applyV(fa)(fab)
assert.True(t, IsLeft(result))
// When both are Left, errors are combined as: fa error + fab error
assert.Equal(t, Left[int]("error2; error1"), result)
}
// TestApV_LeftFunction tests ApV when function is Left and value is Right
func TestApV_LeftFunction(t *testing.T) {
// Create a semigroup for string concatenation
sg := S.MakeSemigroup(func(a, b string) string {
return a + "; " + b
})
// Create the validation applicative
applyV := ApV[int, string, int](sg)
// Function is Left, value is Right - should return function's error
fa := Right[string](21)
fab := Left[func(int) int]("function error")
result := applyV(fa)(fab)
assert.True(t, IsLeft(result))
assert.Equal(t, Left[int]("function error"), result)
}
// TestApV_LeftValue tests ApV when function is Right and value is Left
func TestApV_LeftValue(t *testing.T) {
// Create a semigroup for string concatenation
sg := S.MakeSemigroup(func(a, b string) string {
return a + "; " + b
})
// Create the validation applicative
applyV := ApV[int, string, int](sg)
// Function is Right, value is Left - should return value's error
fa := Left[int]("value error")
fab := Right[string](N.Mul(2))
result := applyV(fa)(fab)
assert.True(t, IsLeft(result))
assert.Equal(t, Left[int]("value error"), result)
}
// TestApV_Composition tests ApV with function composition
func TestApV_Composition(t *testing.T) {
// Create a semigroup for string concatenation
sg := S.MakeSemigroup(func(a, b string) string {
return a + " & " + b
})
// Create the validation applicative
applyV := ApV[string, string, int](sg)
// Test composition with pipe
fa := Right[string](10)
fab := Right[string](func(x int) string {
return F.Pipe1(x, func(n int) string {
if n >= 10 {
return "large"
}
return "small"
})
})
result := F.Pipe1(fa, applyV)(fab)
assert.True(t, IsRight(result))
assert.Equal(t, Right[string]("large"), result)
}
// TestApV_WithStructSemigroup tests ApV with a custom struct semigroup
func TestApV_WithStructSemigroup(t *testing.T) {
type ValidationErrors struct {
Errors []string
}
// Create a semigroup that combines validation errors
sg := S.MakeSemigroup(func(a, b ValidationErrors) ValidationErrors {
return ValidationErrors{
Errors: append(append([]string{}, a.Errors...), b.Errors...),
}
})
// Create the validation applicative
applyV := ApV[int, ValidationErrors, int](sg)
// Both are Left with validation errors
fa := Left[int](ValidationErrors{Errors: []string{"field1: required"}})
fab := Left[func(int) int](ValidationErrors{Errors: []string{"field2: invalid"}})
result := applyV(fa)(fab)
assert.True(t, IsLeft(result))
// When both are Left, errors are combined as: fa errors + fab errors
expected := Left[int](ValidationErrors{
Errors: []string{"field1: required", "field2: invalid"},
})
assert.Equal(t, expected, result)
}
// TestApV_MultipleValidations tests ApV with multiple validation steps
func TestApV_MultipleValidations(t *testing.T) {
// Create a semigroup for string concatenation
sg := S.MakeSemigroup(func(a, b string) string {
return a + ", " + b
})
// Create the validation applicative
applyV := ApV[int, string, int](sg)
// Simulate multiple validation failures
validation1 := Left[int]("age must be positive")
validation2 := Left[func(int) int]("name is required")
result := applyV(validation1)(validation2)
assert.True(t, IsLeft(result))
// When both are Left, errors are combined as: validation1 error + validation2 error
assert.Equal(t, Left[int]("age must be positive, name is required"), result)
}
// TestMonadApV_DifferentTypes tests MonadApV with different input and output types
func TestMonadApV_DifferentTypes(t *testing.T) {
// Create a semigroup for string concatenation
sg := S.MakeSemigroup(func(a, b string) string {
return a + " + " + b
})
// Create the validation applicative
applyV := MonadApV[string, string, int](sg)
// Function converts int to string
fab := Right[string](func(x int) string {
return F.Pipe1(x, func(n int) string {
if n == 0 {
return "zero"
} else if n > 0 {
return "positive"
}
return "negative"
})
})
fa := Right[string](-5)
result := applyV(fab, fa)
assert.True(t, IsRight(result))
assert.Equal(t, Right[string]("negative"), result)
}
// TestApV_FirstSemigroup tests ApV with First semigroup (always returns first error)
func TestApV_FirstSemigroup(t *testing.T) {
// Use First semigroup which always returns the first value
sg := S.First[string]()
// Create the validation applicative
applyV := ApV[int, string, int](sg)
// Both are Left - should return first error
fa := Left[int]("error2")
fab := Left[func(int) int]("error1")
result := applyV(fa)(fab)
assert.True(t, IsLeft(result))
// First semigroup returns the first value, which is fa's error
assert.Equal(t, Left[int]("error2"), result)
}
// TestApV_LastSemigroup tests ApV with Last semigroup (always returns last error)
func TestApV_LastSemigroup(t *testing.T) {
// Use Last semigroup which always returns the last value
sg := S.Last[string]()
// Create the validation applicative
applyV := ApV[int, string, int](sg)
// Both are Left - should return last error
fa := Left[int]("error2")
fab := Left[func(int) int]("error1")
result := applyV(fa)(fab)
assert.True(t, IsLeft(result))
// Last semigroup returns the last value, which is fab's error
assert.Equal(t, Left[int]("error1"), result)
}
// Made with Bob

View File

@@ -36,7 +36,7 @@
// )
//
// // Define some endomorphisms
// double := func(x int) int { return x * 2 }
// double := N.Mul(2)
// increment := func(x int) int { return x + 1 }
//
// // Compose them (RIGHT-TO-LEFT execution)
@@ -62,7 +62,7 @@
//
// // Combine multiple endomorphisms (RIGHT-TO-LEFT execution)
// combined := M.ConcatAll(monoid)(
// func(x int) int { return x * 2 }, // applied third
// N.Mul(2), // applied third
// func(x int) int { return x + 1 }, // applied second
// func(x int) int { return x * 3 }, // applied first
// )
@@ -74,7 +74,7 @@
// MonadChain executes LEFT-TO-RIGHT, unlike Compose:
//
// // Chain allows sequencing of endomorphisms (LEFT-TO-RIGHT)
// f := func(x int) int { return x * 2 }
// f := N.Mul(2)
// g := func(x int) int { return x + 1 }
// chained := endomorphism.MonadChain(f, g) // f first, then g
// result := chained(5) // (5 * 2) + 1 = 11
@@ -83,7 +83,7 @@
//
// The key difference between Compose and Chain/MonadChain is execution order:
//
// double := func(x int) int { return x * 2 }
// double := N.Mul(2)
// increment := func(x int) int { return x + 1 }
//
// // Compose: RIGHT-TO-LEFT (mathematical composition)

View File

@@ -37,7 +37,7 @@ import (
//
// Example:
//
// double := func(x int) int { return x * 2 }
// double := N.Mul(2)
// increment := func(x int) int { return x + 1 }
// result := endomorphism.MonadAp(double, increment) // Composes: double ∘ increment
// // result(5) = double(increment(5)) = double(6) = 12
@@ -64,7 +64,7 @@ func MonadAp[A any](fab Endomorphism[A], fa Endomorphism[A]) Endomorphism[A] {
//
// increment := func(x int) int { return x + 1 }
// applyIncrement := endomorphism.Ap(increment)
// double := func(x int) int { return x * 2 }
// double := N.Mul(2)
// composed := applyIncrement(double) // double ∘ increment
// // composed(5) = double(increment(5)) = double(6) = 12
func Ap[A any](fa Endomorphism[A]) Operator[A] {
@@ -91,7 +91,7 @@ func Ap[A any](fa Endomorphism[A]) Operator[A] {
//
// Example:
//
// double := func(x int) int { return x * 2 }
// double := N.Mul(2)
// increment := func(x int) int { return x + 1 }
//
// // MonadCompose executes RIGHT-TO-LEFT: increment first, then double
@@ -123,7 +123,7 @@ func MonadCompose[A any](f, g Endomorphism[A]) Endomorphism[A] {
//
// Example:
//
// double := func(x int) int { return x * 2 }
// double := N.Mul(2)
// increment := func(x int) int { return x + 1 }
// mapped := endomorphism.MonadMap(double, increment)
// // mapped(5) = double(increment(5)) = double(6) = 12
@@ -153,7 +153,7 @@ func MonadMap[A any](f, g Endomorphism[A]) Endomorphism[A] {
//
// increment := func(x int) int { return x + 1 }
// composeWithIncrement := endomorphism.Compose(increment)
// double := func(x int) int { return x * 2 }
// double := N.Mul(2)
//
// // Composes double with increment (RIGHT-TO-LEFT: increment first, then double)
// composed := composeWithIncrement(double)
@@ -186,7 +186,7 @@ func Compose[A any](g Endomorphism[A]) Operator[A] {
//
// Example:
//
// double := func(x int) int { return x * 2 }
// double := N.Mul(2)
// mapDouble := endomorphism.Map(double)
// increment := func(x int) int { return x + 1 }
// mapped := mapDouble(increment)
@@ -215,7 +215,7 @@ func Map[A any](f Endomorphism[A]) Operator[A] {
//
// Example:
//
// double := func(x int) int { return x * 2 }
// double := N.Mul(2)
// increment := func(x int) int { return x + 1 }
//
// // MonadChain executes LEFT-TO-RIGHT: double first, then increment
@@ -243,7 +243,7 @@ func MonadChain[A any](ma Endomorphism[A], f Endomorphism[A]) Endomorphism[A] {
//
// Example:
//
// double := func(x int) int { return x * 2 }
// double := N.Mul(2)
// log := func(x int) int { fmt.Println(x); return x }
// chained := endomorphism.MonadChainFirst(double, log)
// result := chained(5) // Prints 10, returns 10
@@ -269,7 +269,7 @@ func MonadChainFirst[A any](ma Endomorphism[A], f Endomorphism[A]) Endomorphism[
//
// log := func(x int) int { fmt.Println(x); return x }
// chainLog := endomorphism.ChainFirst(log)
// double := func(x int) int { return x * 2 }
// double := N.Mul(2)
// chained := chainLog(double)
// result := chained(5) // Prints 10, returns 10
func ChainFirst[A any](f Endomorphism[A]) Operator[A] {
@@ -296,7 +296,7 @@ func ChainFirst[A any](f Endomorphism[A]) Operator[A] {
//
// increment := func(x int) int { return x + 1 }
// chainWithIncrement := endomorphism.Chain(increment)
// double := func(x int) int { return x * 2 }
// double := N.Mul(2)
//
// // Chains double (first) with increment (second)
// chained := chainWithIncrement(double)

View File

@@ -19,6 +19,7 @@ import (
"testing"
M "github.com/IBM/fp-go/v2/monoid"
N "github.com/IBM/fp-go/v2/number"
S "github.com/IBM/fp-go/v2/semigroup"
"github.com/stretchr/testify/assert"
)
@@ -204,7 +205,7 @@ func TestCompose(t *testing.T) {
// TestMonadComposeVsCompose demonstrates the relationship between MonadCompose and Compose
func TestMonadComposeVsCompose(t *testing.T) {
double := func(x int) int { return x * 2 }
double := N.Mul(2)
increment := func(x int) int { return x + 1 }
// MonadCompose takes both functions at once
@@ -448,7 +449,7 @@ func TestOperatorType(t *testing.T) {
func BenchmarkCompose(b *testing.B) {
composed := MonadCompose(double, increment)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = composed(5)
}
}
@@ -456,7 +457,7 @@ func BenchmarkCompose(b *testing.B) {
// BenchmarkMonoidConcatAll benchmarks ConcatAll with monoid
// TestComposeVsChain demonstrates the key difference between Compose and Chain
func TestComposeVsChain(t *testing.T) {
double := func(x int) int { return x * 2 }
double := N.Mul(2)
increment := func(x int) int { return x + 1 }
// Compose executes RIGHT-TO-LEFT
@@ -499,7 +500,7 @@ func BenchmarkMonoidConcatAll(b *testing.B) {
monoid := Monoid[int]()
combined := M.ConcatAll(monoid)([]Endomorphism[int]{double, increment, square})
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = combined(5)
}
}
@@ -509,7 +510,7 @@ func BenchmarkChain(b *testing.B) {
chainWithIncrement := Chain(increment)
chained := chainWithIncrement(double)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = chained(5)
}
}
@@ -704,7 +705,7 @@ func TestApEqualsCompose(t *testing.T) {
// TestChainFirst tests the ChainFirst operation
func TestChainFirst(t *testing.T) {
double := func(x int) int { return x * 2 }
double := N.Mul(2)
// Track side effect
var sideEffect int

10
v2/endomorphism/from.go Normal file
View File

@@ -0,0 +1,10 @@
package endomorphism
import (
"github.com/IBM/fp-go/v2/function"
S "github.com/IBM/fp-go/v2/semigroup"
)
func FromSemigroup[A any](s S.Semigroup[A]) Kleisli[A] {
return function.Bind2of2(s.Concat)
}

View File

@@ -35,7 +35,7 @@ import (
//
// Example:
//
// myFunc := func(x int) int { return x * 2 }
// myFunc := N.Mul(2)
// endo := endomorphism.Of(myFunc)
func Of[F ~func(A) A, A any](f F) Endomorphism[A] {
return f
@@ -75,7 +75,7 @@ func Unwrap[F ~func(A) A, A any](f Endomorphism[A]) F {
// result := id(42) // Returns: 42
//
// // Identity is neutral for composition
// double := func(x int) int { return x * 2 }
// double := N.Mul(2)
// composed := endomorphism.Compose(id, double)
// // composed behaves exactly like double
func Identity[A any]() Endomorphism[A] {
@@ -103,7 +103,7 @@ func Identity[A any]() Endomorphism[A] {
// import S "github.com/IBM/fp-go/v2/semigroup"
//
// sg := endomorphism.Semigroup[int]()
// double := func(x int) int { return x * 2 }
// double := N.Mul(2)
// increment := func(x int) int { return x + 1 }
//
// // Combine using the semigroup (RIGHT-TO-LEFT execution)
@@ -139,7 +139,7 @@ func Semigroup[A any]() S.Semigroup[Endomorphism[A]] {
// import M "github.com/IBM/fp-go/v2/monoid"
//
// monoid := endomorphism.Monoid[int]()
// double := func(x int) int { return x * 2 }
// double := N.Mul(2)
// increment := func(x int) int { return x + 1 }
// square := func(x int) int { return x * x }
//

View File

@@ -29,7 +29,7 @@ type (
// Example:
//
// // Simple endomorphisms on integers
// double := func(x int) int { return x * 2 }
// double := N.Mul(2)
// increment := func(x int) int { return x + 1 }
//
// // Both are endomorphisms of type Endomorphism[int]

View File

@@ -23,6 +23,7 @@ import (
E "github.com/IBM/fp-go/v2/either"
F "github.com/IBM/fp-go/v2/function"
N "github.com/IBM/fp-go/v2/number"
"github.com/stretchr/testify/assert"
)
@@ -266,7 +267,7 @@ func TestEither(t *testing.T) {
erased := Erase(42)
result := F.Pipe1(
SafeUnerase[int](erased),
E.Map[error](func(x int) int { return x * 2 }),
E.Map[error](N.Mul(2)),
)
assert.True(t, E.IsRight(result))

View File

@@ -0,0 +1,49 @@
// Copyright (c) 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 option
import (
M "github.com/IBM/fp-go/v2/monoid"
S "github.com/IBM/fp-go/v2/semigroup"
)
// ApplySemigroup lifts a Semigroup over a type A to a Semigroup over Option[A].
// The resulting semigroup combines two Options using the applicative functor pattern.
//
// Example:
//
// intSemigroup := semigroup.MakeSemigroup(func(a, b int) int { return a + b })
// optSemigroup := ApplySemigroup(intSemigroup)
// result := optSemigroup.Concat(Some(2), Some(3)) // Some(5)
// result := optSemigroup.Concat(Some(2), None[int]()) // None
func ApplySemigroup[A any](s S.Semigroup[A]) S.Semigroup[Option[A]] {
return S.ApplySemigroup(MonadMap[A, func(A) A], MonadAp[A, A], s)
}
// ApplicativeMonoid returns a Monoid that concatenates Option instances via their applicative functor.
// This combines the monoid structure of the underlying type with the Option structure.
//
// Example:
//
// intMonoid := monoid.MakeMonoid(func(a, b int) int { return a + b }, 0)
// optMonoid := ApplicativeMonoid(intMonoid)
// result := optMonoid.Concat(Some(2), Some(3)) // Some(5)
// result := optMonoid.Empty() // Some(0)
//
//go:inline
func ApplicativeMonoid[A any](m M.Monoid[A]) M.Monoid[Option[A]] {
return M.ApplicativeMonoid(Of[A], MonadMap[A, func(A) A], MonadAp[A, A], m)
}

View File

@@ -0,0 +1,96 @@
// Copyright (c) 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 option
// TraverseArrayG transforms an array by applying a function that returns an Option to each element.
// Returns Some containing the array of results if all operations succeed, None if any fails.
// This is the generic version that works with custom slice types.
//
// Example:
//
// parse := func(s string) Option[int] {
// n, err := strconv.Atoi(s)
// if err != nil { return None[int]() }
// return Some(n)
// }
// result := TraverseArrayG[[]string, []int](parse)([]string{"1", "2", "3"}) // Some([1, 2, 3])
// result := TraverseArrayG[[]string, []int](parse)([]string{"1", "x", "3"}) // None
func TraverseArrayG[GA ~[]A, GB ~[]B, A, B any](f Kleisli[A, B]) Kleisli[GA, GB] {
return func(g GA) (GB, bool) {
var bs GB
for _, a := range g {
b, bok := f(a)
if !bok {
return bs, false
}
bs = append(bs, b)
}
return bs, true
}
}
// TraverseArray transforms an array by applying a function that returns an Option to each element.
// Returns Some containing the array of results if all operations succeed, None if any fails.
//
// Example:
//
// validate := func(x int) Option[int] {
// if x > 0 { return Some(x * 2) }
// return None[int]()
// }
// result := TraverseArray(validate)([]int{1, 2, 3}) // Some([2, 4, 6])
// result := TraverseArray(validate)([]int{1, -1, 3}) // None
func TraverseArray[A, B any](f Kleisli[A, B]) Kleisli[[]A, []B] {
return TraverseArrayG[[]A, []B](f)
}
// TraverseArrayWithIndexG transforms an array by applying an indexed function that returns an Option.
// The function receives both the index and the element.
// This is the generic version that works with custom slice types.
//
// Example:
//
// f := func(i int, s string) Option[string] {
// return Some(fmt.Sprintf("%d:%s", i, s))
// }
// result := TraverseArrayWithIndexG[[]string, []string](f)([]string{"a", "b"}) // Some(["0:a", "1:b"])
func TraverseArrayWithIndexG[GA ~[]A, GB ~[]B, A, B any](f func(int, A) (B, bool)) Kleisli[GA, GB] {
return func(g GA) (GB, bool) {
var bs GB
for i, a := range g {
b, bok := f(i, a)
if !bok {
return bs, false
}
bs = append(bs, b)
}
return bs, true
}
}
// TraverseArrayWithIndex transforms an array by applying an indexed function that returns an Option.
// The function receives both the index and the element.
//
// Example:
//
// f := func(i int, x int) Option[int] {
// if x > i { return Some(x) }
// return None[int]()
// }
// result := TraverseArrayWithIndex(f)([]int{1, 2, 3}) // Some([1, 2, 3])
func TraverseArrayWithIndex[A, B any](f func(int, A) (B, bool)) Kleisli[[]A, []B] {
return TraverseArrayWithIndexG[[]A, []B](f)
}

View File

@@ -0,0 +1,18 @@
package option
import (
"testing"
"github.com/stretchr/testify/assert"
)
func AssertEq[A any](l A, lok bool) func(A, bool) func(*testing.T) {
return func(r A, rok bool) func(*testing.T) {
return func(t *testing.T) {
assert.Equal(t, lok, rok)
if lok && rok {
assert.Equal(t, l, r)
}
}
}
}

356
v2/idiomatic/option/bind.go Normal file
View File

@@ -0,0 +1,356 @@
// Copyright (c) 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 option
import (
"github.com/IBM/fp-go/v2/function"
L "github.com/IBM/fp-go/v2/optics/lens"
)
// Do creates an empty context of type S to be used with the Bind operation.
// This is the starting point for building up a context using do-notation style.
//
// Parameters:
// - empty: The initial empty context value
//
// Example:
//
// type Result struct {
// x int
// y string
// }
// result := Do(Result{})
func Do[S any](
empty S,
) (S, bool) {
return Of(empty)
}
// Bind attaches the result of a computation to a context S1 to produce a context S2.
// This is used in do-notation style to sequentially build up a context.
//
// Parameters:
// - setter: A function that takes a value and returns a function to update the context
// - f: A function that computes an Option value from the current context
//
// Example:
//
// type State struct { x int; y int }
// result := F.Pipe2(
// Do(State{}),
// Bind(func(x int) func(State) State {
// return func(s State) State { s.x = x; return s }
// }, func(s State) (int, bool) { return 42, true }),
// )
func Bind[S1, S2, A any](
setter func(A) func(S1) S2,
f Kleisli[S1, A],
) Operator[S1, S2] {
return func(s1 S1, s1ok bool) (s2 S2, s2ok bool) {
if s1ok {
a, aok := f(s1)
if aok {
return Of(setter(a)(s1))
}
}
return
}
}
// 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, not an Option.
//
// Parameters:
// - key: A function that takes a value and returns a function to update the context
// - f: A pure function that computes a value from the current context
//
// Example:
//
// type State struct { x int; computed int }
// result := F.Pipe2(
// Do(State{x: 5}),
// Let(func(c int) func(State) State {
// return func(s State) State { s.computed = c; return s }
// }, func(s State) int { return s.x * 2 }),
// )
func Let[S1, S2, B any](
key func(B) func(S1) S2,
f func(S1) B,
) Operator[S1, S2] {
return func(s1 S1, s1ok bool) (s2 S2, s2ok bool) {
if s1ok {
return Of(key(f(s1))(s1))
}
return
}
}
// LetTo attaches a constant value to a context S1 to produce a context S2.
//
// Parameters:
// - key: A function that takes a value and returns a function to update the context
// - b: The constant value to attach to the context
//
// Example:
//
// type State struct { x int; name string }
// result := F.Pipe2(
// Do(State{x: 5}),
// LetTo(func(n string) func(State) State {
// return func(s State) State { s.name = n; return s }
// }, "example"),
// )
func LetTo[S1, S2, B any](
key func(B) func(S1) S2,
b B,
) Operator[S1, S2] {
kb := key(b)
return func(s1 S1, s1ok bool) (s2 S2, s2ok bool) {
if s1ok {
return Of(kb(s1))
}
return
}
}
// BindTo initializes a new state S1 from a value T.
// This is typically used as the first operation after creating an Option value.
//
// Parameters:
// - setter: A function that creates the initial context from a value
//
// Example:
//
// type State struct { value int }
// result := F.Pipe1(
// Some(42),
// BindTo(func(x int) State { return State{value: x} }),
// )
func BindTo[S1, T any](
setter func(T) S1,
) Operator[T, S1] {
return func(t T, tok bool) (s1 S1, s1ok bool) {
if tok {
return Of(setter(t))
}
return
}
}
// ApS attaches a value to a context S1 to produce a context S2 by considering the context and the value concurrently.
// This uses the applicative functor pattern, allowing parallel composition.
//
// Parameters:
// - setter: A function that takes a value and returns a function to update the context
//
// Returns a function that takes an Option (value, bool) and returns an Operator.
//
// Example:
//
// type State struct { x int; y int }
// result := F.Pipe2(
// Do(State{}),
// ApS(func(x int) func(State) State {
// return func(s State) State { s.x = x; return s }
// }, Some(42)),
// )
func ApS[S1, S2, T any](
setter func(T) func(S1) S2,
) func(T, bool) Operator[S1, S2] {
return func(t T, tok bool) Operator[S1, S2] {
if tok {
st := setter(t)
return func(s1 S1, s1ok bool) (s2 S2, s2ok bool) {
if s1ok {
return Of(st(s1))
}
return
}
}
return func(_ S1, _ bool) (s2 S2, s2ok bool) {
return
}
}
}
// ApSL attaches a value to a context using a lens-based setter.
// This is a convenience function that combines ApS with a lens, allowing you to use
// optics to update nested structures in a more composable way.
//
// The lens parameter provides both the getter and setter for a field within the structure S.
// This eliminates the need to manually write setter functions.
//
// Example:
//
// type Address struct {
// Street string
// City string
// }
//
// type Person struct {
// Name string
// Address Address
// }
//
// // Create a lens for the Address field
// addressLens := lens.MakeLens(
// func(p Person) Address { return p.Address },
// func(p Person, a Address) Person { p.Address = a; return p },
// )
//
// // Use ApSL to update the address
// result := F.Pipe2(
// option.Some(Person{Name: "Alice"}),
// option.ApSL(
// addressLens,
// option.Some(Address{Street: "Main St", City: "NYC"}),
// ),
// )
//
// Parameters:
// - lens: A lens that focuses on a field within the structure S
//
// Returns a function that takes an Option (value, bool) and returns an Operator.
func ApSL[S, T any](
lens L.Lens[S, T],
) func(T, bool) Operator[S, S] {
return ApS(lens.Set)
}
// BindL attaches the result of a computation to a context using a lens-based setter.
// This is a convenience function that combines Bind with a lens, allowing you to use
// optics to update nested structures based on their current values.
//
// The lens parameter provides both the getter and setter for a field within the structure S.
// The computation function f receives the current value of the focused field and returns
// an Option that produces the new value.
//
// Unlike ApSL, BindL uses monadic sequencing, meaning the computation f can depend on
// the current value of the focused field.
//
// Example:
//
// type Counter struct {
// Value int
// }
//
// valueLens := lens.MakeLens(
// func(c Counter) int { return c.Value },
// func(c Counter, v int) Counter { c.Value = v; return c },
// )
//
// // Increment the counter, but return None if it would exceed 100
// increment := func(v int) option.Option[int] {
// if v >= 100 {
// return option.None[int]()
// }
// return option.Some(v + 1)
// }
//
// result := F.Pipe1(
// option.Some(Counter{Value: 42}),
// option.BindL(valueLens, increment),
// ) // Some(Counter{Value: 43})
//
// Parameters:
// - lens: A lens that focuses on a field within the structure S
// - f: A function that computes an Option value from the current field value
func BindL[S, T any](
lens L.Lens[S, T],
f Kleisli[T, T],
) Operator[S, S] {
return Bind(lens.Set, func(s S) (T, bool) {
return f(lens.Get(s))
})
}
// LetL attaches the result of a pure computation to a context using a lens-based setter.
// This is a convenience function that combines Let with a lens, allowing you to use
// optics to update nested structures with pure transformations.
//
// The lens parameter provides both the getter and setter for a field within the structure S.
// The transformation function f receives the current value of the focused field and returns
// the new value directly (not wrapped in Option).
//
// This is useful for pure transformations that cannot fail, such as mathematical operations,
// string manipulations, or other deterministic updates.
//
// Example:
//
// type Counter struct {
// Value int
// }
//
// valueLens := lens.MakeLens(
// func(c Counter) int { return c.Value },
// func(c Counter, v int) Counter { c.Value = v; return c },
// )
//
// // Double the counter value
// double := func(v int) int { return v * 2 }
//
// result := F.Pipe1(
// option.Some(Counter{Value: 21}),
// option.LetL(valueLens, double),
// ) // Some(Counter{Value: 42})
//
// Parameters:
// - lens: A lens that focuses on a field within the structure S
// - f: A pure transformation function for the field value
func LetL[S, T any](
lens L.Lens[S, T],
f Endomorphism[T],
) Operator[S, S] {
return Let(lens.Set, function.Flow2(lens.Get, f))
}
// LetToL attaches a constant value to a context using a lens-based setter.
// This is a convenience function that combines LetTo with a lens, allowing you to use
// optics to set nested fields to specific values.
//
// The lens parameter provides the setter for a field within the structure S.
// Unlike LetL which transforms the current value, LetToL simply replaces it with
// the provided constant value b.
//
// This is useful for resetting fields, initializing values, or setting fields to
// predetermined constants.
//
// Example:
//
// type Config struct {
// Debug bool
// Timeout int
// }
//
// debugLens := lens.MakeLens(
// func(c Config) bool { return c.Debug },
// func(c Config, d bool) Config { c.Debug = d; return c },
// )
//
// result := F.Pipe1(
// option.Some(Config{Debug: true, Timeout: 30}),
// option.LetToL(debugLens, false),
// ) // Some(Config{Debug: false, Timeout: 30})
//
// Parameters:
// - lens: A lens that focuses on a field within the structure S
// - b: The constant value to set the field to
func LetToL[S, T any](
lens L.Lens[S, T],
b T,
) Operator[S, S] {
return LetTo(lens.Set, b)
}

View File

@@ -0,0 +1,52 @@
// Copyright (c) 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 option
import (
"testing"
"github.com/IBM/fp-go/v2/internal/utils"
)
func getLastName(s utils.Initial) (string, bool) {
return Of("Doe")
}
func getGivenName(s utils.WithLastName) (string, bool) {
return Of("John")
}
func TestBind(t *testing.T) {
res, resok := Flow3(
Bind(utils.SetLastName, getLastName),
Bind(utils.SetGivenName, getGivenName),
Map(utils.GetFullName),
)(Do(utils.Empty))
AssertEq(Of("John Doe"))(res, resok)(t)
}
func TestApS(t *testing.T) {
res, resok := Flow3(
ApS(utils.SetLastName)(Of("Doe")),
ApS(utils.SetGivenName)(Of("John")),
Map(utils.GetFullName),
)(Do(utils.Empty))
AssertEq(Of("John Doe"))(res, resok)(t)
}

113
v2/idiomatic/option/core.go Normal file
View File

@@ -0,0 +1,113 @@
// Copyright (c) 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 option
import "fmt"
type (
Operator[A, B any] = func(A, bool) (B, bool)
Kleisli[A, B any] = func(A) (B, bool)
)
// IsSome checks if an Option contains a value.
//
// Parameters:
// - t: The value of the Option
// - tok: Whether the Option contains a value (true for Some, false for None)
//
// Example:
//
// opt := Some(42)
// IsSome(opt) // true
// opt := None[int]()
// IsSome(opt) // false
//
//go:inline
func IsSome[T any](t T, tok bool) bool {
return tok
}
// IsNone checks if an Option is None (contains no value).
//
// Parameters:
// - t: The value of the Option
// - tok: Whether the Option contains a value (true for Some, false for None)
//
// Example:
//
// opt := None[int]()
// IsNone(opt) // true
// opt := Some(42)
// IsNone(opt) // false
//
//go:inline
func IsNone[T any](t T, tok bool) bool {
return !tok
}
// Some creates an Option that contains a value.
//
// Parameters:
// - value: The value to wrap in Some
//
// Example:
//
// opt := Some(42) // Option containing 42
// opt := Some("hello") // Option containing "hello"
//
//go:inline
func Some[T any](value T) (T, bool) {
return value, true
}
// Of creates an Option that contains a value.
// This is an alias for Some and is used in monadic contexts.
//
// Parameters:
// - value: The value to wrap in Some
//
// Example:
//
// opt := Of(42) // Option containing 42
//
//go:inline
func Of[T any](value T) (T, bool) {
return Some(value)
}
// None creates an Option that contains no value.
//
// Example:
//
// opt := None[int]() // Empty Option of type int
// opt := None[string]() // Empty Option of type string
//
//go:inline
func None[T any]() (t T, tok bool) {
return
}
// ToString converts an Option to a string representation for debugging.
//
// Parameters:
// - t: The value of the Option
// - tok: Whether the Option contains a value (true for Some, false for None)
func ToString[T any](t T, tok bool) string {
if tok {
return fmt.Sprintf("Some[%T](%v)", t, t)
}
return fmt.Sprintf("None[%T]", t)
}

241
v2/idiomatic/option/doc.go Normal file
View File

@@ -0,0 +1,241 @@
// Copyright (c) 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 option implements the Option monad using idiomatic Go tuple signatures.
//
// Unlike the standard option package which uses wrapper structs, this package represents
// Options as tuples (value, bool) where the boolean indicates presence (true) or absence (false).
// This approach is more idiomatic in Go and has better performance characteristics.
//
// # Type Signatures
//
// The core types used in this package are:
//
// Operator[A, B any] = func(A, bool) (B, bool) // Transforms an Option[A] to Option[B]
// Kleisli[A, B any] = func(A) (B, bool) // Monadic function from A to Option[B]
//
// # Basic Usage
//
// Create an Option with Some or None:
//
// some := Some(42) // (42, true)
// none := None[int]() // (0, false)
// opt := Of(42) // Alternative to Some: (42, true)
//
// Check if an Option contains a value:
//
// value, ok := Some(42)
// if ok {
// // value == 42
// }
//
// if IsSome(Some(42)) {
// // Option contains a value
// }
// if IsNone(None[int]()) {
// // Option is empty
// }
//
// Extract values:
//
// value, ok := Some(42) // Direct tuple unpacking: value == 42, ok == true
// value := GetOrElse(func() int { return 0 })(Some(42)) // Returns 42
// value := GetOrElse(func() int { return 0 })(None[int]()) // Returns 0
//
// # Transformations
//
// Map transforms the contained value:
//
// double := Map(func(x int) int { return x * 2 })
// result := double(Some(21)) // (42, true)
// result := double(None[int]()) // (0, false)
//
// Chain sequences operations that may fail:
//
// validate := Chain(func(x int) (int, bool) {
// if x > 0 { return x * 2, true }
// return 0, false
// })
// result := validate(Some(5)) // (10, true)
// result := validate(Some(-1)) // (0, false)
//
// Filter keeps values that satisfy a predicate:
//
// isPositive := Filter(func(x int) bool { return x > 0 })
// result := isPositive(Some(5)) // (5, true)
// result := isPositive(Some(-1)) // (0, false)
//
// # Working with Collections
//
// Transform arrays:
//
// doublePositive := func(x int) (int, bool) {
// if x > 0 { return x * 2, true }
// return 0, false
// }
// result := TraverseArray(doublePositive)([]int{1, 2, 3}) // ([2, 4, 6], true)
// result := TraverseArray(doublePositive)([]int{1, -2, 3}) // ([], false)
//
// Sequence arrays of Options:
//
// opts := []Option[int]{Some(1), Some(2), Some(3)}
// result := SequenceArray(opts) // ([1, 2, 3], true)
//
// opts := []Option[int]{Some(1), None[int](), Some(3)}
// result := SequenceArray(opts) // ([], false)
//
// Compact arrays (remove None values):
//
// opts := []Option[int]{Some(1), None[int](), Some(3)}
// result := CompactArray(opts) // [1, 3]
//
// # Algebraic Operations
//
// Option supports various algebraic structures:
//
// - Functor: Map operations for transforming values
// - Applicative: Ap operations for applying wrapped functions
// - Monad: Chain operations for sequencing computations
// - Alternative: Alt operations for providing fallbacks
//
// Applicative example:
//
// fab := Some(func(x int) int { return x * 2 })
// fa := Some(21)
// result := Ap[int](fa)(fab) // (42, true)
//
// Alternative example:
//
// withDefault := Alt(func() (int, bool) { return 100, true })
// result := withDefault(Some(42)) // (42, true)
// result := withDefault(None[int]()) // (100, true)
//
// # Error Handling
//
// Convert error-returning functions:
//
// result := TryCatch(func() (int, error) {
// return strconv.Atoi("42")
// }) // (42, true)
//
// result := TryCatch(func() (int, error) {
// return strconv.Atoi("invalid")
// }) // (0, false)
//
// Convert validation functions:
//
// parse := FromValidation(func(s string) (int, bool) {
// n, err := strconv.Atoi(s)
// return n, err == nil
// })
// result := parse("42") // (42, true)
// result := parse("invalid") // (0, false)
//
// Convert predicates:
//
// isPositive := FromPredicate(func(n int) bool { return n > 0 })
// result := isPositive(5) // (5, true)
// result := isPositive(-1) // (-1, false)
//
// Convert nullable pointers:
//
// var ptr *int = nil
// result := FromNillable(ptr) // (nil, false)
// val := 42
// result := FromNillable(&val) // (&val, true)
//
// # Do-Notation Style
//
// Build complex computations using do-notation:
//
// type Result struct {
// x int
// y int
// sum int
// }
//
// result := F.Pipe3(
// Do(Result{}),
// Bind(func(x int) func(Result) Result {
// return func(r Result) Result { r.x = x; return r }
// }, func(r Result) (int, bool) { return Some(10) }),
// Bind(func(y int) func(Result) Result {
// return func(r Result) Result { r.y = y; return r }
// }, func(r Result) (int, bool) { return Some(20) }),
// Let(func(sum int) func(Result) Result {
// return func(r Result) Result { r.sum = sum; return r }
// }, func(r Result) int { return r.x + r.y }),
// ) // (Result{x: 10, y: 20, sum: 30}, true)
//
// # Lens-Based Operations
//
// Use lenses for cleaner field updates:
//
// type Person struct {
// Name string
// Age int
// }
//
// ageLens := lens.MakeLens(
// func(p Person) int { return p.Age },
// func(p Person, age int) Person { p.Age = age; return p },
// )
//
// // Update using a lens
// incrementAge := BindL(ageLens, func(age int) (int, bool) {
// if age < 120 { return age + 1, true }
// return 0, false
// })
// result := incrementAge(Some(Person{Name: "Alice", Age: 30}))
// // (Person{Name: "Alice", Age: 31}, true)
//
// // Set using a lens
// setAge := LetToL(ageLens, 25)
// result := setAge(Some(Person{Name: "Bob", Age: 30}))
// // (Person{Name: "Bob", Age: 25}, true)
//
// # Folding and Reducing
//
// Fold provides a way to handle both Some and None cases:
//
// handler := Fold(
// func() string { return "no value" },
// func(x int) string { return fmt.Sprintf("value: %d", x) },
// )
// result := handler(Some(42)) // "value: 42"
// result := handler(None[int]()) // "no value"
//
// Reduce folds an Option into a single value:
//
// sum := Reduce(func(acc, val int) int { return acc + val }, 0)
// result := sum(Some(5)) // 5
// result := sum(None[int]()) // 0
//
// # Debugging
//
// Convert Options to strings for debugging:
//
// str := ToString(Some(42)) // "Some[int](42)"
// str := ToString(None[int]()) // "None[int]"
//
// # Subpackages
//
// - option/number: Number conversion utilities (Atoi, Itoa)
// - option/testing: Testing utilities for verifying monad laws
package option
//go:generate go run .. option --count 10 --filename gen.go
// Made with Bob

84
v2/idiomatic/option/eq.go Normal file
View File

@@ -0,0 +1,84 @@
// Copyright (c) 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 option
import (
EQ "github.com/IBM/fp-go/v2/eq"
)
// Eq constructs an equality predicate for Option[A] given an equality predicate for A.
// Two Options are equal if:
// - Both are None, or
// - Both are Some and their contained values are equal according to the provided Eq
//
// Parameters:
// - eq: An equality predicate for the contained type A
//
// Returns a curried function that takes two Options (as tuples) and returns true if they are equal.
//
// Example:
//
// intEq := eq.FromStrictEquals[int]()
// optEq := Eq(intEq)
//
// opt1 := Some(42) // (42, true)
// opt2 := Some(42) // (42, true)
// optEq(opt1)(opt2) // true
//
// opt3 := Some(43) // (43, true)
// optEq(opt1)(opt3) // false
//
// none1 := None[int]() // (0, false)
// none2 := None[int]() // (0, false)
// optEq(none1)(none2) // true
//
// optEq(opt1)(none1) // false
func Eq[A any](eq EQ.Eq[A]) func(A, bool) func(A, bool) bool {
return func(a1 A, a1ok bool) func(A, bool) bool {
return func(a2 A, a2ok bool) bool {
if a1ok {
if a2ok {
return eq.Equals(a1, a2)
}
return false
}
return !a2ok
}
}
}
// FromStrictEquals constructs an Eq for Option[A] using Go's built-in equality (==) for type A.
// This is a convenience function for comparable types.
//
// Returns a curried function that takes two Options (as tuples) and returns true if they are equal.
//
// Example:
//
// optEq := FromStrictEquals[int]()
//
// opt1 := Some(42) // (42, true)
// opt2 := Some(42) // (42, true)
// optEq(opt1)(opt2) // true
//
// none1 := None[int]() // (0, false)
// none2 := None[int]() // (0, false)
// optEq(none1)(none2) // true
//
// opt3 := Some(43) // (43, true)
// optEq(opt1)(opt3) // false
func FromStrictEquals[A comparable]() func(A, bool) func(A, bool) bool {
return Eq(EQ.FromStrictEquals[A]())
}

View File

@@ -0,0 +1,41 @@
// Copyright (c) 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 option
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestEq(t *testing.T) {
r1, r1ok := Of(1)
r2, r2ok := Of(1)
r3, r3ok := Of(2)
n1, n1ok := None[int]()
eq := FromStrictEquals[int]()
assert.True(t, eq(r1, r1ok)(r1, r1ok))
assert.True(t, eq(r1, r1ok)(r2, r2ok))
assert.False(t, eq(r1, r1ok)(r3, r3ok))
assert.False(t, eq(r1, r1ok)(n1, n1ok))
assert.True(t, eq(n1, n1ok)(n1, n1ok))
assert.False(t, eq(n1, n1ok)(r2, r2ok))
}

View File

@@ -0,0 +1,55 @@
// Copyright (c) 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 option
import "fmt"
func ExampleSome_creation() {
// Build an Option
none1, none1ok := None[int]()
some1, some1ok := Some("value")
// Build from a value
fromNillable := FromNillable[string]
nonFromNil, nonFromNilok := fromNillable(nil) // None[*string]
value := "value"
someFromPointer, someFromPointerok := fromNillable(&value) // Some[*string](xxx)
// some predicate
isEven := func(num int) bool {
return num%2 == 0
}
fromEven := FromPredicate(isEven)
noneFromPred, noneFromPredok := fromEven(3) // None[int]
someFromPred, someFromPredok := fromEven(4) // Some[int](4)
fmt.Println(ToString(none1, none1ok))
fmt.Println(ToString(some1, some1ok))
fmt.Println(ToString(nonFromNil, nonFromNilok))
fmt.Println(IsSome(someFromPointer, someFromPointerok))
fmt.Println(ToString(noneFromPred, noneFromPredok))
fmt.Println(ToString(someFromPred, someFromPredok))
// Output:
// None[int]
// Some[string](value)
// None[*string]
// true
// None[int]
// Some[int](4)
}

View File

@@ -0,0 +1,57 @@
// Copyright (c) 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 option
import (
"fmt"
F "github.com/IBM/fp-go/v2/function"
N "github.com/IBM/fp-go/v2/number"
)
func ExampleSome_extraction() {
noneValue, okFromNone := None[int]()
someValue, okFromSome := Of(42)
// Convert Option[T] with a default value
noneWithDefault := GetOrElse(F.Constant(0))(noneValue, okFromNone) // 0
someWithDefault := GetOrElse(F.Constant(0))(someValue, okFromSome) // 42
// Apply a different function on None/Some(...)
doubleOrZero := Fold(
F.Constant(0), // none case
N.Mul(2), // some case
) // func(ma Option[int]) int
doubleFromNone := doubleOrZero(noneValue, okFromNone) // 0
doubleFromSome := doubleOrZero(someValue, okFromSome) // 84
fmt.Printf("%d, %t\n", noneValue, okFromNone)
fmt.Printf("%d, %t\n", someValue, okFromSome)
fmt.Println(noneWithDefault)
fmt.Println(someWithDefault)
fmt.Println(doubleFromNone)
fmt.Println(doubleFromSome)
// Output:
// 0, false
// 42, true
// 0
// 42
// 0
// 84
}

View File

@@ -0,0 +1,89 @@
package option
// Pipe1 takes an initial value t0 and successively applies 1 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Pipe1[F1 ~func(T0) (T1, bool), T0, T1 any](t0 T0, f1 F1) (T1, bool) {
return f1(t0)
}
// Flow1 creates a function that takes an initial value t0 and successively applies 1 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Flow1[F1 ~func(T0, bool) (T1, bool), T0, T1 any](f1 F1) func(T0, bool) (T1, bool) {
return f1
}
// Pipe2 takes an initial value t0 and successively applies 2 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Pipe2[F1 ~func(T0) (T1, bool), F2 ~func(T1, bool) (T2, bool), T0, T1, T2 any](t0 T0, f1 F1, f2 F2) (T2, bool) {
return f2(f1(t0))
}
// Flow2 creates a function that takes an initial value t0 and successively applies 2 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Flow2[F1 ~func(T0, bool) (T1, bool), F2 ~func(T1, bool) (T2, bool), T0, T1, T2 any](f1 F1, f2 F2) func(T0, bool) (T2, bool) {
return func(t0 T0, t0ok bool) (T2, bool) {
return f2(f1(t0, t0ok))
}
}
// Pipe3 takes an initial value t0 and successively applies 3 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Pipe3[F1 ~func(T0) (T1, bool), F2 ~func(T1, bool) (T2, bool), F3 ~func(T2, bool) (T3, bool), T0, T1, T2, T3 any](t0 T0, f1 F1, f2 F2, f3 F3) (T3, bool) {
return f3(f2(f1(t0)))
}
// Flow3 creates a function that takes an initial value t0 and successively applies 3 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Flow3[F1 ~func(T0, bool) (T1, bool), F2 ~func(T1, bool) (T2, bool), F3 ~func(T2, bool) (T3, bool), T0, T1, T2, T3 any](f1 F1, f2 F2, f3 F3) func(T0, bool) (T3, bool) {
return func(t0 T0, t0ok bool) (T3, bool) {
return f3(f2(f1(t0, t0ok)))
}
}
// Pipe4 takes an initial value t0 and successively applies 4 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Pipe4[F1 ~func(T0) (T1, bool), F2 ~func(T1, bool) (T2, bool), F3 ~func(T2, bool) (T3, bool), F4 ~func(T3, bool) (T4, bool), T0, T1, T2, T3, T4 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4) (T4, bool) {
return f4(f3(f2(f1(t0))))
}
// Flow4 creates a function that takes an initial value t0 and successively applies 4 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Flow4[F1 ~func(T0, bool) (T1, bool), F2 ~func(T1, bool) (T2, bool), F3 ~func(T2, bool) (T3, bool), F4 ~func(T3, bool) (T4, bool), T0, T1, T2, T3, T4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(T0, bool) (T4, bool) {
return func(t0 T0, t0ok bool) (T4, bool) {
return f4(f3(f2(f1(t0, t0ok))))
}
}
// Pipe5 takes an initial value t0 and successively applies 5 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Pipe5[F1 ~func(T0) (T1, bool), F2 ~func(T1, bool) (T2, bool), F3 ~func(T2, bool) (T3, bool), F4 ~func(T3, bool) (T4, bool), F5 ~func(T4, bool) (T5, bool), T0, T1, T2, T3, T4, T5 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) (T5, bool) {
return f5(f4(f3(f2(f1(t0)))))
}
// Flow5 creates a function that takes an initial value t0 and successively applies 5 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Flow5[F1 ~func(T0, bool) (T1, bool), F2 ~func(T1, bool) (T2, bool), F3 ~func(T2, bool) (T3, bool), F4 ~func(T3, bool) (T4, bool), F5 ~func(T4, bool) (T5, bool), T0, T1, T2, T3, T4, T5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(T0, bool) (T5, bool) {
return func(t0 T0, t0ok bool) (T5, bool) {
return f5(f4(f3(f2(f1(t0, t0ok)))))
}
}

View File

@@ -0,0 +1,41 @@
// Copyright (c) 2024 - 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 option
type (
optionFunctor[A, B any] struct{}
Functor[A, B any] interface {
Map(func(A) B) func(A, bool) (B, bool)
}
)
func (o optionFunctor[A, B]) Map(f func(A) B) Operator[A, B] {
return Map(f)
}
// Functor implements the functoric operations for Option.
// A functor is a type that can be mapped over, transforming the contained value
// while preserving the structure.
//
// Example:
//
// f := Functor[int, string]()
// mapper := f.Map(strconv.Itoa)
// result := mapper(Some(42)) // Some("42")
func MakeFunctor[A, B any]() Functor[A, B] {
return optionFunctor[A, B]{}
}

195
v2/idiomatic/option/gen.go Normal file
View File

@@ -0,0 +1,195 @@
// Code generated by go generate; DO NOT EDIT.
// This file was generated by robots at
// 2025-03-09 23:53:08.2750287 +0100 CET m=+0.001545801
package option
// TraverseTuple1 converts a [Tuple1] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple1]].
func TraverseTuple1[F1 ~Kleisli[A1, T1], A1, T1 any](f1 F1) func(A1) (T1, bool) {
return func(a1 A1) (t1 T1, ok bool) {
if t1, ok := f1(a1); ok {
return t1, true
}
return
}
}
// TraverseTuple2 converts a [Tuple2] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple2]].
func TraverseTuple2[F1 ~Kleisli[A1, T1], F2 ~Kleisli[A2, T2], A1, T1, A2, T2 any](f1 F1, f2 F2) func(A1, A2) (T1, T2, bool) {
return func(a1 A1, a2 A2) (t1 T1, t2 T2, ok bool) {
if t1, ok := f1(a1); ok {
if t2, ok := f2(a2); ok {
return t1, t2, true
}
}
return
}
}
// TraverseTuple3 converts a [Tuple3] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple3]].
func TraverseTuple3[F1 ~Kleisli[A1, T1], F2 ~Kleisli[A2, T2], F3 ~Kleisli[A3, T3], A1, T1, A2, T2, A3, T3 any](f1 F1, f2 F2, f3 F3) func(A1, A2, A3) (T1, T2, T3, bool) {
return func(a1 A1, a2 A2, a3 A3) (t1 T1, t2 T2, t3 T3, ok bool) {
if t1, ok := f1(a1); ok {
if t2, ok := f2(a2); ok {
if t3, ok := f3(a3); ok {
return t1, t2, t3, true
}
}
}
return
}
}
// TraverseTuple4 converts a [Tuple4] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple4]].
func TraverseTuple4[F1 ~Kleisli[A1, T1], F2 ~Kleisli[A2, T2], F3 ~Kleisli[A3, T3], F4 ~Kleisli[A4, T4], A1, T1, A2, T2, A3, T3, A4, T4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(A1, A2, A3, A4) (T1, T2, T3, T4, bool) {
return func(a1 A1, a2 A2, a3 A3, a4 A4) (t1 T1, t2 T2, t3 T3, t4 T4, ok bool) {
if t1, ok := f1(a1); ok {
if t2, ok := f2(a2); ok {
if t3, ok := f3(a3); ok {
if t4, ok := f4(a4); ok {
return t1, t2, t3, t4, true
}
}
}
}
return
}
}
// TraverseTuple5 converts a [Tuple5] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple5]].
func TraverseTuple5[F1 ~Kleisli[A1, T1], F2 ~Kleisli[A2, T2], F3 ~Kleisli[A3, T3], F4 ~Kleisli[A4, T4], F5 ~Kleisli[A5, T5], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(A1, A2, A3, A4, A5) (T1, T2, T3, T4, T5, bool) {
return func(a1 A1, a2 A2, a3 A3, a4 A4, a5 A5) (t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, ok bool) {
if t1, ok := f1(a1); ok {
if t2, ok := f2(a2); ok {
if t3, ok := f3(a3); ok {
if t4, ok := f4(a4); ok {
if t5, ok := f5(a5); ok {
return t1, t2, t3, t4, t5, true
}
}
}
}
}
return
}
}
// TraverseTuple6 converts a [Tuple6] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple6]].
func TraverseTuple6[F1 ~Kleisli[A1, T1], F2 ~Kleisli[A2, T2], F3 ~Kleisli[A3, T3], F4 ~Kleisli[A4, T4], F5 ~Kleisli[A5, T5], F6 ~Kleisli[A6, T6], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(A1, A2, A3, A4, A5, A6) (T1, T2, T3, T4, T5, T6, bool) {
return func(a1 A1, a2 A2, a3 A3, a4 A4, a5 A5, a6 A6) (t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, ok bool) {
if t1, ok := f1(a1); ok {
if t2, ok := f2(a2); ok {
if t3, ok := f3(a3); ok {
if t4, ok := f4(a4); ok {
if t5, ok := f5(a5); ok {
if t6, ok := f6(a6); ok {
return t1, t2, t3, t4, t5, t6, true
}
}
}
}
}
}
return
}
}
// TraverseTuple7 converts a [Tuple7] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple7]].
func TraverseTuple7[F1 ~Kleisli[A1, T1], F2 ~Kleisli[A2, T2], F3 ~Kleisli[A3, T3], F4 ~Kleisli[A4, T4], F5 ~Kleisli[A5, T5], F6 ~Kleisli[A6, T6], F7 ~Kleisli[A7, T7], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(A1, A2, A3, A4, A5, A6, A7) (T1, T2, T3, T4, T5, T6, T7, bool) {
return func(a1 A1, a2 A2, a3 A3, a4 A4, a5 A5, a6 A6, a7 A7) (t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, ok bool) {
if t1, ok := f1(a1); ok {
if t2, ok := f2(a2); ok {
if t3, ok := f3(a3); ok {
if t4, ok := f4(a4); ok {
if t5, ok := f5(a5); ok {
if t6, ok := f6(a6); ok {
if t7, ok := f7(a7); ok {
return t1, t2, t3, t4, t5, t6, t7, true
}
}
}
}
}
}
}
return
}
}
// TraverseTuple8 converts a [Tuple8] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple8]].
func TraverseTuple8[F1 ~Kleisli[A1, T1], F2 ~Kleisli[A2, T2], F3 ~Kleisli[A3, T3], F4 ~Kleisli[A4, T4], F5 ~Kleisli[A5, T5], F6 ~Kleisli[A6, T6], F7 ~Kleisli[A7, T7], F8 ~Kleisli[A8, T8], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(A1, A2, A3, A4, A5, A6, A7, A8) (T1, T2, T3, T4, T5, T6, T7, T8, bool) {
return func(a1 A1, a2 A2, a3 A3, a4 A4, a5 A5, a6 A6, a7 A7, a8 A8) (t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, ok bool) {
if t1, ok := f1(a1); ok {
if t2, ok := f2(a2); ok {
if t3, ok := f3(a3); ok {
if t4, ok := f4(a4); ok {
if t5, ok := f5(a5); ok {
if t6, ok := f6(a6); ok {
if t7, ok := f7(a7); ok {
if t8, ok := f8(a8); ok {
return t1, t2, t3, t4, t5, t6, t7, t8, true
}
}
}
}
}
}
}
}
return
}
}
// TraverseTuple9 converts a [Tuple9] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple9]].
func TraverseTuple9[F1 ~Kleisli[A1, T1], F2 ~Kleisli[A2, T2], F3 ~Kleisli[A3, T3], F4 ~Kleisli[A4, T4], F5 ~Kleisli[A5, T5], F6 ~Kleisli[A6, T6], F7 ~Kleisli[A7, T7], F8 ~Kleisli[A8, T8], F9 ~Kleisli[A9, T9], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(A1, A2, A3, A4, A5, A6, A7, A8, A9) (T1, T2, T3, T4, T5, T6, T7, T8, T9, bool) {
return func(a1 A1, a2 A2, a3 A3, a4 A4, a5 A5, a6 A6, a7 A7, a8 A8, a9 A9) (t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, ok bool) {
if t1, ok := f1(a1); ok {
if t2, ok := f2(a2); ok {
if t3, ok := f3(a3); ok {
if t4, ok := f4(a4); ok {
if t5, ok := f5(a5); ok {
if t6, ok := f6(a6); ok {
if t7, ok := f7(a7); ok {
if t8, ok := f8(a8); ok {
if t9, ok := f9(a9); ok {
return t1, t2, t3, t4, t5, t6, t7, t8, t9, true
}
}
}
}
}
}
}
}
}
return
}
}
// TraverseTuple10 converts a [Tuple10] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple10]].
func TraverseTuple10[F1 ~Kleisli[A1, T1], F2 ~Kleisli[A2, T2], F3 ~Kleisli[A3, T3], F4 ~Kleisli[A4, T4], F5 ~Kleisli[A5, T5], F6 ~Kleisli[A6, T6], F7 ~Kleisli[A7, T7], F8 ~Kleisli[A8, T8], F9 ~Kleisli[A9, T9], F10 ~Kleisli[A10, T10], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9, A10, T10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10) (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, bool) {
return func(a1 A1, a2 A2, a3 A3, a4 A4, a5 A5, a6 A6, a7 A7, a8 A8, a9 A9, a10 A10) (t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, ok bool) {
if t1, ok := f1(a1); ok {
if t2, ok := f2(a2); ok {
if t3, ok := f3(a3); ok {
if t4, ok := f4(a4); ok {
if t5, ok := f5(a5); ok {
if t6, ok := f6(a6); ok {
if t7, ok := f7(a7); ok {
if t8, ok := f8(a8); ok {
if t9, ok := f9(a9); ok {
if t10, ok := f10(a10); ok {
return t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, true
}
}
}
}
}
}
}
}
}
}
return
}
}

View File

@@ -0,0 +1,69 @@
// Copyright (c) 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 option
import (
I "github.com/IBM/fp-go/v2/iterator/iter"
)
// TraverseIter transforms a sequence by applying a function that returns an Option to each element.
// Returns Some containing a sequence of results if all operations succeed, None if any fails.
// This function is useful for processing sequences where each element may fail validation or transformation.
//
// The traversal short-circuits on the first None encountered, making it efficient for validation pipelines.
// The resulting sequence is lazy and will only be evaluated when iterated.
//
// Example:
//
// // Parse a sequence of strings to integers
// parse := func(s string) Option[int] {
// n, err := strconv.Atoi(s)
// if err != nil { return None[int]() }
// return Some(n)
// }
//
// // Create a sequence of strings
// strings := func(yield func(string) bool) {
// for _, s := range []string{"1", "2", "3"} {
// if !yield(s) { return }
// }
// }
//
// result := TraverseIter(parse)(strings)
// // result is Some(sequence of [1, 2, 3])
//
// // With invalid input
// invalidStrings := func(yield func(string) bool) {
// for _, s := range []string{"1", "invalid", "3"} {
// if !yield(s) { return }
// }
// }
//
// result := TraverseIter(parse)(invalidStrings)
// // result is None because "invalid" cannot be parsed
func TraverseIter[A, B any](f Kleisli[A, B]) Kleisli[Seq[A], Seq[B]] {
return func(s Seq[A]) (Seq[B], bool) {
var bs []B
for a := range s {
b, bok := f(a)
if !bok {
return nil, false
}
bs = append(bs, b)
}
return I.From(bs...), true
}
}

View File

@@ -0,0 +1,325 @@
// Copyright (c) 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 option
import (
"fmt"
"slices"
"strconv"
"testing"
A "github.com/IBM/fp-go/v2/array"
F "github.com/IBM/fp-go/v2/function"
I "github.com/IBM/fp-go/v2/iterator/iter"
"github.com/stretchr/testify/assert"
)
// Helper function to create a sequence from a slice
func seqFromSlice[T any](items []T) Seq[T] {
return I.From(items...)
}
// Helper function to collect a sequence into a slice
func collectSeq[T any](seq Seq[T]) []T {
return slices.Collect(seq)
}
func TestTraverseIter_AllSome(t *testing.T) {
// Test case where all transformations succeed
parse := func(s string) (int, bool) {
n, err := strconv.Atoi(s)
if err != nil {
return None[int]()
}
return Some(n)
}
input := I.From("1", "2", "3", "4", "5")
result, resultok := TraverseIter(parse)(input)
assert.True(t, IsSome(result, resultok), "Expected Some result when all transformations succeed")
collected := collectSeq(result)
expected := A.From(1, 2, 3, 4, 5)
assert.Equal(t, expected, collected)
}
func TestTraverseIter_ContainsNone(t *testing.T) {
// Test case where one transformation fails
parse := func(s string) (int, bool) {
n, err := strconv.Atoi(s)
if err != nil {
return None[int]()
}
return Some(n)
}
input := seqFromSlice([]string{"1", "invalid", "3"})
result, resultok := TraverseIter(parse)(input)
assert.True(t, IsNone(result, resultok), "Expected None when any transformation fails")
}
func TestTraverseIter_EmptySequence(t *testing.T) {
// Test with empty sequence
double := func(x int) (int, bool) {
return Some(x * 2)
}
input := seqFromSlice([]int{})
result, resultok := TraverseIter(double)(input)
assert.True(t, IsSome(result, resultok), "Expected Some for empty sequence")
collected := collectSeq(result)
assert.Empty(t, collected)
}
func TestTraverseIter_SingleElement(t *testing.T) {
// Test with single element - success case
validate := func(x int) (int, bool) {
if x > 0 {
return Some(x * 2)
}
return None[int]()
}
input := seqFromSlice([]int{5})
result, resultok := TraverseIter(validate)(input)
assert.True(t, IsSome(result, resultok))
collected := collectSeq(result)
assert.Equal(t, []int{10}, collected)
}
func TestTraverseIter_SingleElementFails(t *testing.T) {
// Test with single element - failure case
validate := func(x int) (int, bool) {
if x > 0 {
return Some(x * 2)
}
return None[int]()
}
input := seqFromSlice([]int{-5})
result, resultok := TraverseIter(validate)(input)
assert.True(t, IsNone(result, resultok))
}
func TestTraverseIter_Validation(t *testing.T) {
// Test validation use case
validatePositive := func(x int) (int, bool) {
if x > 0 {
return Some(x)
}
return None[int]()
}
// All positive
input1 := seqFromSlice([]int{1, 2, 3, 4})
result1, result1ok := TraverseIter(validatePositive)(input1)
assert.True(t, IsSome(result1, result1ok))
// Contains negative
input2 := seqFromSlice([]int{1, -2, 3})
result2, result2ok := TraverseIter(validatePositive)(input2)
assert.True(t, IsNone(result2, result2ok))
// Contains zero
input3 := seqFromSlice([]int{1, 0, 3})
result3, result3ok := TraverseIter(validatePositive)(input3)
assert.True(t, IsNone(result3, result3ok))
}
func TestTraverseIter_Transformation(t *testing.T) {
// Test transformation use case
safeDivide := func(x int) (float64, bool) {
if x != 0 {
return Some(100.0 / float64(x))
}
return None[float64]()
}
// All non-zero
input1 := seqFromSlice([]int{1, 2, 4, 5})
result1, result1ok := TraverseIter(safeDivide)(input1)
assert.True(t, IsSome(result1, result1ok))
collected := collectSeq(result1)
expected := []float64{100.0, 50.0, 25.0, 20.0}
assert.Equal(t, expected, collected)
// Contains zero
input2 := seqFromSlice([]int{1, 0, 4})
result2, result2ok := TraverseIter(safeDivide)(input2)
assert.True(t, IsNone(result2, result2ok))
}
func TestTraverseIter_ShortCircuit(t *testing.T) {
// Test that traversal short-circuits on first None
callCount := 0
countingFunc := func(x int) (int, bool) {
callCount++
if x < 0 {
return None[int]()
}
return Some(x * 2)
}
// First element fails
input := seqFromSlice([]int{-1, 2, 3, 4, 5})
result, resultok := TraverseIter(countingFunc)(input)
assert.True(t, IsNone(result, resultok))
// Should have called the function for elements until the first failure
// Note: The exact count depends on implementation details of the traverse function
assert.Greater(t, callCount, 0, "Function should be called at least once")
}
func TestTraverseIter_LazyEvaluation(t *testing.T) {
// Test that the result sequence is lazy
transform := func(x int) (int, bool) {
return Some(x * 2)
}
input := seqFromSlice([]int{1, 2, 3, 4, 5})
result, resultok := TraverseIter(transform)(input)
assert.True(t, IsSome(result, resultok))
// Partially consume the sequence
callCount := 0
Fold(func() int { return 0 }, func(seq Seq[int]) int {
for val := range seq {
callCount++
_ = val
if callCount == 2 {
break
}
}
return callCount
})(result, resultok)
assert.Equal(t, 2, callCount, "Should only evaluate consumed elements")
}
func TestTraverseIter_ComplexTransformation(t *testing.T) {
// Test with more complex transformation
type Person struct {
Name string
Age int
}
validatePerson := func(name string) (Person, bool) {
if name == "" {
return None[Person]()
}
return Some(Person{Name: name, Age: len(name)})
}
input := seqFromSlice([]string{"Alice", "Bob", "Charlie"})
result, resultok := TraverseIter(validatePerson)(input)
assert.True(t, IsSome(result, resultok))
collected := collectSeq((result))
expected := []Person{
{Name: "Alice", Age: 5},
{Name: "Bob", Age: 3},
{Name: "Charlie", Age: 7},
}
assert.Equal(t, expected, collected)
}
func TestTraverseIter_WithPipeline(t *testing.T) {
// Test TraverseIter in a functional pipeline
parse := func(s string) (int, bool) {
n, err := strconv.Atoi(s)
if err != nil {
return None[int]()
}
return Some(n)
}
input := seqFromSlice([]string{"1", "2", "3", "4", "5"})
collected := Fold(func() []int { return nil }, F.Identity[[]int])(Map(collectSeq[int])(TraverseIter(parse)(input)))
expected := []int{1, 2, 3, 4, 5}
assert.Equal(t, expected, collected)
}
func TestTraverseIter_ChainedTransformations(t *testing.T) {
// Test chaining multiple transformations
parseAndValidate := func(s string) (int, bool) {
n, err := strconv.Atoi(s)
if err != nil {
return None[int]()
}
if n > 0 {
return Some(n)
}
return None[int]()
}
// All valid
input1 := seqFromSlice([]string{"1", "2", "3"})
result1, result1ok := TraverseIter(parseAndValidate)(input1)
assert.True(t, IsSome(result1, result1ok))
// Contains invalid number
input2 := seqFromSlice([]string{"1", "invalid", "3"})
result2, result2ok := TraverseIter(parseAndValidate)(input2)
assert.True(t, IsNone(result2, result2ok))
// Contains non-positive number
input3 := seqFromSlice([]string{"1", "0", "3"})
result3, result3ok := TraverseIter(parseAndValidate)(input3)
assert.True(t, IsNone(result3, result3ok))
}
// Example test demonstrating usage
func ExampleTraverseIter() {
// Parse a sequence of strings to integers
parse := func(s string) (int, bool) {
n, err := strconv.Atoi(s)
if err != nil {
return None[int]()
}
return Some(n)
}
// Create a sequence of valid strings
validStrings := seqFromSlice([]string{"1", "2", "3"})
result, resultok := TraverseIter(parse)(validStrings)
if IsSome(result, resultok) {
numbers := collectSeq(result)
fmt.Println(numbers)
}
// Create a sequence with invalid string
invalidStrings := seqFromSlice([]string{"1", "invalid", "3"})
result2, result2ok := TraverseIter(parse)(invalidStrings)
if IsNone(result2, result2ok) {
fmt.Println("Parsing failed")
}
// Output:
// [1 2 3]
// Parsing failed
}

View File

@@ -0,0 +1,61 @@
// Copyright (c) 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 option
import (
"log"
L "github.com/IBM/fp-go/v2/logging"
)
func _log[A any](left func(string, ...any), right func(string, ...any), prefix string) Operator[A, A] {
return func(a A, aok bool) (A, bool) {
if aok {
right("%s: %v", prefix, a)
} else {
left("%s", prefix)
}
return a, aok
}
}
// Logger creates a logging function for Options that logs the state (None or Some with value)
// and returns the original Option unchanged. This is useful for debugging pipelines.
//
// Parameters:
// - loggers: optional log.Logger instances to use for logging (defaults to standard logger)
//
// Returns a function that takes a prefix string and returns a function that logs and passes through an Option.
//
// Example:
//
// logger := Logger[int]()
// result := F.Pipe2(
// Some(42),
// logger("step1"), // logs "step1: 42"
// Map(N.Mul(2)),
// ) // Some(84)
//
// result := F.Pipe1(
// None[int](),
// logger("step1"), // logs "step1"
// ) // None
func Logger[A any](loggers ...*log.Logger) func(string) Operator[A, A] {
left, right := L.LoggingCallbacks(loggers...)
return func(prefix string) Operator[A, A] {
return _log[A](left, right, prefix)
}
}

View File

@@ -0,0 +1,61 @@
// Copyright (c) 2024 - 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 option
import (
"github.com/IBM/fp-go/v2/internal/monad"
)
type (
optionMonad[A, B any] struct{}
)
func (o *optionMonad[A, B]) Of(a A) Option[A] {
return Of(a)
}
func (o *optionMonad[A, B]) Map(f func(A) B) Operator[A, B] {
return Map(f)
}
func (o *optionMonad[A, B]) Chain(f Kleisli[A, B]) Operator[A, B] {
return Chain(f)
}
func (o *optionMonad[A, B]) Ap(fa Option[A]) func(Option[func(A) B]) Option[B] {
return Ap[B](fa)
}
// Monad implements the monadic operations for Option.
// A monad provides a way to chain computations that may fail, handling the
// None case automatically.
//
// The monad interface includes:
// - Of: wraps a value in an Option
// - Map: transforms the contained value
// - Chain: sequences Option-returning operations
// - Ap: applies an Option-wrapped function to an Option-wrapped value
//
// Example:
//
// m := Monad[int, string]()
// result := m.Chain(func(x int) Option[string] {
// if x > 0 { return Some(fmt.Sprintf("%d", x)) }
// return None[string]()
// })(Some(42)) // Some("42")
func Monad[A, B any]() monad.Monad[A, B, Option[A], Option[B], Option[func(A) B]] {
return &optionMonad[A, B]{}
}

View File

@@ -0,0 +1,111 @@
// Copyright (c) 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 option
import (
F "github.com/IBM/fp-go/v2/function"
M "github.com/IBM/fp-go/v2/monoid"
S "github.com/IBM/fp-go/v2/semigroup"
)
// Semigroup returns a function that lifts a Semigroup over type A to a Semigroup over Option[A].
// The resulting semigroup combines two Options according to these rules:
// - If both are Some, concatenates their values using the provided Semigroup
// - If one is None, returns the other
// - If both are None, returns None
//
// Example:
//
// intSemigroup := semigroup.MakeSemigroup(func(a, b int) int { return a + b })
// optSemigroup := Semigroup[int]()(intSemigroup)
// optSemigroup.Concat(Some(2), Some(3)) // Some(5)
// optSemigroup.Concat(Some(2), None[int]()) // Some(2)
// optSemigroup.Concat(None[int](), Some(3)) // Some(3)
func Semigroup[A any]() func(S.Semigroup[A]) S.Semigroup[Option[A]] {
return func(s S.Semigroup[A]) S.Semigroup[Option[A]] {
concat := s.Concat
return S.MakeSemigroup(
func(x, y Option[A]) Option[A] {
return MonadFold(x, F.Constant(y), func(left A) Option[A] {
return MonadFold(y, F.Constant(x), func(right A) Option[A] {
return Some(concat(left, right))
})
})
},
)
}
}
// Monoid returns a function that lifts a Semigroup over type A to a Monoid over Option[A].
// The monoid returns the left-most non-None value. If both operands are Some, their inner
// values are concatenated using the provided Semigroup. The empty value is None.
//
// Truth table:
//
// | x | y | concat(x, y) |
// | ------- | ------- | ------------------ |
// | none | none | none |
// | some(a) | none | some(a) |
// | none | some(b) | some(b) |
// | some(a) | some(b) | some(concat(a, b)) |
//
// Example:
//
// intSemigroup := semigroup.MakeSemigroup(func(a, b int) int { return a + b })
// optMonoid := Monoid[int]()(intSemigroup)
// optMonoid.Concat(Some(2), Some(3)) // Some(5)
// optMonoid.Empty() // None
func Monoid[A any]() func(S.Semigroup[A]) M.Monoid[Option[A]] {
sg := Semigroup[A]()
return func(s S.Semigroup[A]) M.Monoid[Option[A]] {
return M.MakeMonoid(sg(s).Concat, None[A]())
}
}
// AlternativeMonoid creates a Monoid for Option[A] using the alternative semantics.
// This combines the applicative functor structure with the alternative (Alt) operation.
//
// Example:
//
// intMonoid := monoid.MakeMonoid(func(a, b int) int { return a + b }, 0)
// optMonoid := AlternativeMonoid(intMonoid)
// result := optMonoid.Concat(Some(2), Some(3)) // Some(5)
func AlternativeMonoid[A any](m M.Monoid[A]) M.Monoid[Option[A]] {
return M.AlternativeMonoid(
Of[A],
MonadMap[A, func(A) A],
MonadAp[A, A],
MonadAlt[A],
m,
)
}
// AltMonoid creates a Monoid for Option[A] using the Alt operation.
// This monoid returns the first Some value, or None if both are None.
// The empty value is None.
//
// Example:
//
// optMonoid := AltMonoid[int]()
// optMonoid.Concat(Some(2), Some(3)) // Some(2) - returns first Some
// optMonoid.Concat(None[int](), Some(3)) // Some(3)
// optMonoid.Empty() // None
func AltMonoid[A any]() M.Monoid[Option[A]] {
return M.AltMonoid(
None[A],
MonadAlt[A],
)
}

View File

@@ -0,0 +1,45 @@
// Copyright (c) 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 number provides Option-based utilities for number conversions.
package number
import (
"strconv"
)
// Atoi converts a string to an integer, returning Some(int) on success or None on failure.
//
// Example:
//
// result := Atoi("42") // Some(42)
// result := Atoi("abc") // None
// result := Atoi("") // None
func Atoi(value string) (int, bool) {
data, err := strconv.Atoi(value)
return data, err == nil
}
// Itoa converts an integer to a string, always returning Some(string).
//
// Example:
//
// result := Itoa(42) // Some("42")
// result := Itoa(-10) // Some("-10")
// result := Itoa(0) // Some("0")
func Itoa(value int) (string, bool) {
return strconv.Itoa(value), true
}

View File

@@ -0,0 +1,329 @@
// Copyright (c) 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 option implements the Option monad using idiomatic Go data types.
//
// Unlike the standard option package which uses wrapper structs, this package represents
// Options as tuples (value, bool) where the boolean indicates presence (true) or absence (false).
// This approach is more idiomatic in Go and has better performance characteristics.
//
// Example:
//
// // Creating Options
// some := Some(42) // (42, true)
// none := None[int]() // (0, false)
//
// // Using Options
// result, ok := some // ok == true, result == 42
// result, ok := none // ok == false, result == 0
//
// // Transforming Options
// doubled := Map(func(x int) int { return x * 2 })(some) // (84, true)
package option
import (
"github.com/IBM/fp-go/v2/eq"
F "github.com/IBM/fp-go/v2/function"
P "github.com/IBM/fp-go/v2/predicate"
)
// FromPredicate returns a function that creates an Option based on a predicate.
// The returned function will wrap a value in Some if the predicate is satisfied, otherwise None.
//
// Parameters:
// - pred: A predicate function that determines if a value should be wrapped in Some
//
// Example:
//
// isPositive := FromPredicate(func(n int) bool { return n > 0 })
// result := isPositive(5) // Some(5)
// result := isPositive(-1) // None
func FromPredicate[A any](pred func(A) bool) Kleisli[A, A] {
return func(a A) (A, bool) {
return a, pred(a)
}
}
//go:inline
func FromZero[A comparable]() Kleisli[A, A] {
return FromPredicate(P.IsZero[A]())
}
//go:inline
func FromNonZero[A comparable]() Kleisli[A, A] {
return FromPredicate(P.IsNonZero[A]())
}
//go:inline
func FromEq[A any](pred eq.Eq[A]) func(A) Kleisli[A, A] {
return F.Flow2(P.IsEqual(pred), FromPredicate[A])
}
// FromNillable converts a pointer to an Option.
// Returns Some if the pointer is non-nil, None otherwise.
//
// Parameters:
// - a: A pointer that may be nil
//
// Example:
//
// var ptr *int = nil
// result := FromNillable(ptr) // None
// val := 42
// result := FromNillable(&val) // Some(&val)
func FromNillable[A any](a *A) (*A, bool) {
return a, F.IsNonNil(a)
}
// Ap is the curried applicative functor for Option.
// Returns a function that applies an Option-wrapped function to the given Option value.
//
// Parameters:
// - fa: The value of the Option
// - faok: Whether the Option contains a value (true for Some, false for None)
//
// Example:
//
// fa := Some(5)
// applyTo5 := Ap[int](fa)
// fab := Some(N.Mul(2))
// result := applyTo5(fab) // Some(10)
func Ap[B, A any](fa A, faok bool) Operator[func(A) B, B] {
if faok {
return func(fab func(A) B, fabok bool) (b B, bok bool) {
if fabok {
return fab(fa), true
}
return
}
}
return func(_ func(A) B, _ bool) (b B, bok bool) {
return
}
}
// Map returns a function that applies a transformation to the value inside an Option.
// If the Option is None, returns None.
//
// Parameters:
// - f: A transformation function to apply to the Option value
//
// Example:
//
// double := Map(N.Mul(2))
// result := double(Some(5)) // Some(10)
// result := double(None[int]()) // None
func Map[A, B any](f func(a A) B) Operator[A, B] {
return func(fa A, faok bool) (b B, bok bool) {
if faok {
return f(fa), true
}
return
}
}
// MapTo returns a function that replaces the value inside an Option with a constant.
//
// Parameters:
// - b: The constant value to replace with
//
// Example:
//
// replaceWith42 := MapTo[string, int](42)
// result := replaceWith42(Some("hello")) // Some(42)
func MapTo[A, B any](b B) Operator[A, B] {
return func(_ A, faok bool) (B, bool) {
return b, faok
}
}
// Fold provides a way to handle both Some and None cases of an Option.
// Returns a function that applies onNone if the Option is None, or onSome if it's Some.
//
// Parameters:
// - onNone: Function to call when the Option is None
// - onSome: Function to call when the Option is Some, receives the wrapped value
//
// Example:
//
// handler := Fold(
// func() string { return "no value" },
// func(x int) string { return fmt.Sprintf("value: %d", x) },
// )
// result := handler(Some(42)) // "value: 42"
// result := handler(None[int]()) // "no value"
func Fold[A, B any](onNone func() B, onSome func(A) B) func(A, bool) B {
return func(a A, aok bool) B {
if aok {
return onSome(a)
}
return onNone()
}
}
// GetOrElse returns a function that extracts the value from an Option or returns a default.
//
// Parameters:
// - onNone: Function that provides the default value when the Option is None
//
// Example:
//
// getOrZero := GetOrElse(func() int { return 0 })
// result := getOrZero(Some(42)) // 42
// result := getOrZero(None[int]()) // 0
func GetOrElse[A any](onNone func() A) func(A, bool) A {
return func(a A, aok bool) A {
if aok {
return a
}
return onNone()
}
}
// Chain returns a function that applies an Option-returning function to an Option value.
// This is the curried form of the monadic bind operation.
//
// Parameters:
// - f: A function that takes a value and returns an Option
//
// Example:
//
// validate := Chain(func(x int) (int, bool) {
// if x > 0 { return x * 2, true }
// return 0, false
// })
// result := validate(Some(5)) // Some(10)
func Chain[A, B any](f Kleisli[A, B]) Operator[A, B] {
return func(a A, aok bool) (b B, bok bool) {
if aok {
return f(a)
}
return
}
}
// ChainTo returns a function that ignores its input Option and returns a fixed Option.
//
// Parameters:
// - b: The value of the replacement Option
// - bok: Whether the replacement Option contains a value
//
// Example:
//
// replaceWith := ChainTo(Some("hello"))
// result := replaceWith(Some(42)) // Some("hello")
func ChainTo[A, B any](b B, bok bool) Operator[A, B] {
return func(_ A, aok bool) (B, bool) {
return b, bok && aok
}
}
// ChainFirst returns a function that applies an Option-returning function but keeps the original value.
//
// Parameters:
// - f: A function that takes a value and returns an Option (result is used only for success/failure)
//
// Example:
//
// logAndKeep := ChainFirst(func(x int) (string, bool) {
// fmt.Println(x)
// return "logged", true
// })
// result := logAndKeep(Some(5)) // Some(5)
func ChainFirst[A, B any](f Kleisli[A, B]) Operator[A, A] {
return func(a A, aok bool) (A, bool) {
if aok {
_, bok := f(a)
return a, bok
}
return a, false
}
}
// Alt returns a function that provides an alternative Option if the input is None.
//
// Parameters:
// - that: A function that provides an alternative Option
//
// Example:
//
// withDefault := Alt(func() (int, bool) { return 0, true })
// result := withDefault(Some(5)) // Some(5)
// result := withDefault(None[int]()) // Some(0)
func Alt[A any](that func() (A, bool)) Operator[A, A] {
return func(a A, aok bool) (A, bool) {
if aok {
return a, aok
}
return that()
}
}
// Reduce folds an Option into a single value using a reducer function.
// If the Option is None, returns the initial value.
//
// Parameters:
// - f: A reducer function that combines the accumulator with the Option value
// - initial: The initial/default value to use
//
// Example:
//
// sum := Reduce(func(acc, val int) int { return acc + val }, 0)
// result := sum(Some(5)) // 5
// result := sum(None[int]()) // 0
func Reduce[A, B any](f func(B, A) B, initial B) func(A, bool) B {
return func(a A, aok bool) B {
if aok {
return f(initial, a)
}
return initial
}
}
// Filter keeps the Option if it's Some and the predicate is satisfied, otherwise returns None.
//
// Parameters:
// - pred: A predicate function to test the Option value
//
// Example:
//
// isPositive := Filter(func(x int) bool { return x > 0 })
// result := isPositive(Some(5)) // Some(5)
// result := isPositive(Some(-1)) // None
// result := isPositive(None[int]()) // None
func Filter[A any](pred func(A) bool) Operator[A, A] {
return func(a A, aok bool) (A, bool) {
return a, aok && pred(a)
}
}
// Flap returns a function that applies a value to an Option-wrapped function.
//
// Parameters:
// - a: The value to apply to the function
//
// Example:
//
// applyFive := Flap[int](5)
// fab := Some(N.Mul(2))
// result := applyFive(fab) // Some(10)
func Flap[B, A any](a A) Operator[func(A) B, B] {
return func(f func(A) B, fabok bool) (b B, bok bool) {
if fabok {
return f(a), true
}
return
}
}

View File

@@ -0,0 +1,167 @@
// Copyright (c) 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 option
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
// Test Logger function
func TestLogger(t *testing.T) {
logger := Logger[int]()
logFunc := logger("test")
// Test with Some
result, resultok := logFunc(Some(42))
AssertEq(Some(42))(result, resultok)(t)
// Test with None
result, resultok = logFunc(None[int]())
AssertEq(None[int]())(result, resultok)(t)
}
// Test TraverseArrayG with custom slice types
func TestTraverseArrayG(t *testing.T) {
type MySlice []int
type MyResultSlice []string
f := func(x int) (string, bool) {
if x > 0 {
return Some(fmt.Sprintf("%d", x))
}
return None[string]()
}
result, resultok := TraverseArrayG[MySlice, MyResultSlice](f)(MySlice{1, 2, 3})
AssertEq(Some(MyResultSlice{"1", "2", "3"}))(result, resultok)(t)
// Test with failure
result, resultok = TraverseArrayG[MySlice, MyResultSlice](f)(MySlice{1, -1, 3})
AssertEq(None[MyResultSlice]())(result, resultok)(t)
}
// Test TraverseRecordG with custom map types
func TestTraverseRecordG(t *testing.T) {
type MyMap map[string]int
type MyResultMap map[string]string
f := func(x int) (string, bool) {
if x > 0 {
return Some(fmt.Sprintf("%d", x))
}
return None[string]()
}
input := MyMap{"a": 1, "b": 2}
result, resultok := TraverseRecordG[MyMap, MyResultMap](f)(input)
assert.True(t, IsSome(result, resultok))
assert.Equal(t, "1", result["a"])
assert.Equal(t, "2", result["b"])
}
// Test TraverseTuple3 through TraverseTuple10
func TestTraverseTuple3(t *testing.T) {
f1 := func(x int) (int, bool) { return Some(x * 2) }
f2 := func(s string) (string, bool) { return Some(s + "!") }
f3 := func(b bool) (bool, bool) { return Some(!b) }
traverse := TraverseTuple3(f1, f2, f3)
r1, r2, r3, resultok := traverse(5, "hello", true)
assert.True(t, resultok)
assert.Equal(t, r1, 10)
assert.Equal(t, r2, "hello!")
assert.Equal(t, r3, false)
}
func TestTraverseTuple4(t *testing.T) {
f1 := func(x int) (int, bool) { return Some(x * 2) }
f2 := func(x int) (int, bool) { return Some(x + 1) }
f3 := func(x int) (int, bool) { return Some(x - 1) }
f4 := func(x int) (int, bool) { return Some(x * 3) }
traverse := TraverseTuple4(f1, f2, f3, f4)
r1, r2, r3, r4, resultok := traverse(1, 2, 3, 4)
assert.True(t, resultok)
assert.Equal(t, r1, 2)
assert.Equal(t, r2, 3)
assert.Equal(t, r3, 2)
assert.Equal(t, r4, 12)
}
// Test edge cases for MonadFold
func TestMonadFoldEdgeCases(t *testing.T) {
// Test with complex types
type ComplexType struct {
value int
name string
}
result := Fold(
func() string { return "none" },
func(ct ComplexType) string { return ct.name },
)(Some(ComplexType{value: 42, name: "test"}))
assert.Equal(t, "test", result)
result = Fold(func() string { return "none" },
func(ct ComplexType) string { return ct.name },
)(None[ComplexType]())
assert.Equal(t, "none", result)
}
// Test TraverseArrayWithIndexG
func TestTraverseArrayWithIndexG(t *testing.T) {
type MySlice []int
type MyResultSlice []string
f := func(i int, x int) (string, bool) {
return Some(fmt.Sprintf("%d:%d", i, x))
}
result, resultok := TraverseArrayWithIndexG[MySlice, MyResultSlice](f)(MySlice{10, 20, 30})
AssertEq(Some(MyResultSlice{"0:10", "1:20", "2:30"}))(result, resultok)(t)
}
// Test TraverseRecordWithIndexG
func TestTraverseRecordWithIndexG(t *testing.T) {
type MyMap map[string]int
type MyResultMap map[string]string
f := func(k string, v int) (string, bool) {
return Some(fmt.Sprintf("%s=%d", k, v))
}
input := MyMap{"a": 1, "b": 2}
result, resultok := TraverseRecordWithIndexG[MyMap, MyResultMap](f)(input)
assert.True(t, IsSome(result, resultok))
}
// Test TraverseTuple1
func TestTraverseTuple1(t *testing.T) {
f := func(x int) (int, bool) { return Some(x * 2) }
traverse := TraverseTuple1(f)
result, resultok := traverse(5)
assert.True(t, resultok)
assert.Equal(t, 10, result)
}

View File

@@ -0,0 +1,415 @@
// Copyright (c) 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 option
import (
"fmt"
"testing"
N "github.com/IBM/fp-go/v2/number"
"github.com/stretchr/testify/assert"
)
// Test FromNillable
func TestFromNillable(t *testing.T) {
var nilPtr *int = nil
AssertEq(None[*int]())(FromNillable(nilPtr))(t)
val := 42
ptr := &val
result, resultok := FromNillable(ptr)
assert.True(t, IsSome(result, resultok))
assert.Equal(t, &val, result)
}
// Test MapTo
func TestMapTo(t *testing.T) {
t.Run("positive case - replace value", func(t *testing.T) {
replaceWith42 := MapTo[string](42)
// Should replace value when Some
AssertEq(Some(42))(replaceWith42(Some("hello")))(t)
AssertEq(Some(42))(replaceWith42(Some("world")))(t)
})
t.Run("negative case - input is None", func(t *testing.T) {
replaceWith42 := MapTo[string](42)
// Should return None when input is None
AssertEq(None[int]())(replaceWith42(None[string]()))(t)
})
}
// Test GetOrElse
func TestGetOrElse(t *testing.T) {
t.Run("positive case - extract value from Some", func(t *testing.T) {
getOrZero := GetOrElse(func() int { return 0 })
// Should extract value when Some
assert.Equal(t, 42, getOrZero(Some(42)))
assert.Equal(t, 100, getOrZero(Some(100)))
})
t.Run("negative case - use default for None", func(t *testing.T) {
getOrZero := GetOrElse(func() int { return 0 })
// Should return default when None
assert.Equal(t, 0, getOrZero(None[int]()))
})
t.Run("positive case - custom default", func(t *testing.T) {
getOrNegative := GetOrElse(func() int { return -1 })
// Should use custom default
assert.Equal(t, -1, getOrNegative(None[int]()))
assert.Equal(t, 42, getOrNegative(Some(42)))
})
}
// Test ChainTo
func TestChainTo(t *testing.T) {
t.Run("positive case - replace with Some", func(t *testing.T) {
replaceWith := ChainTo[int](Some("hello"))
// Should replace any input with the fixed value
AssertEq(Some("hello"))(replaceWith(Some(42)))(t)
AssertEq(None[string]())(replaceWith(None[int]()))(t)
})
t.Run("negative case - replace with None", func(t *testing.T) {
replaceWith := ChainTo[int](None[string]())
// Should replace any input with None
AssertEq(None[string]())(replaceWith(Some(42)))(t)
AssertEq(None[string]())(replaceWith(None[int]()))(t)
})
}
// Test ChainFirst
func TestChainFirst(t *testing.T) {
t.Run("positive case - side effect succeeds", func(t *testing.T) {
sideEffect := func(x int) (string, bool) {
return Some(fmt.Sprintf("%d", x))
}
chainFirst := ChainFirst(sideEffect)
// Should keep original value when side effect succeeds
AssertEq(Some(5))(chainFirst(Some(5)))(t)
})
t.Run("negative case - side effect fails", func(t *testing.T) {
sideEffect := func(x int) (string, bool) {
if x < 0 {
return None[string]()
}
return Some(fmt.Sprintf("%d", x))
}
chainFirst := ChainFirst(sideEffect)
// Should return None when side effect fails
AssertEq(None[int]())(chainFirst(Some(-5)))(t)
})
t.Run("negative case - input is None", func(t *testing.T) {
sideEffect := func(x int) (string, bool) {
return Some(fmt.Sprintf("%d", x))
}
chainFirst := ChainFirst(sideEffect)
// Should return None when input is None
AssertEq(None[int]())(chainFirst(None[int]()))(t)
})
}
// Test Filter
func TestFilter(t *testing.T) {
t.Run("positive case - predicate satisfied", func(t *testing.T) {
isPositive := Filter(func(x int) bool { return x > 0 })
// Should keep value when predicate is satisfied
AssertEq(Some(5))(isPositive(Some(5)))(t)
})
t.Run("negative case - predicate not satisfied", func(t *testing.T) {
isPositive := Filter(func(x int) bool { return x > 0 })
// Should return None when predicate fails
AssertEq(None[int]())(isPositive(Some(-1)))(t)
AssertEq(None[int]())(isPositive(Some(0)))(t)
})
t.Run("negative case - input is None", func(t *testing.T) {
isPositive := Filter(func(x int) bool { return x > 0 })
// Should return None when input is None
AssertEq(None[int]())(isPositive(None[int]()))(t)
})
}
// Test Flap
func TestFlap(t *testing.T) {
t.Run("positive case - function is Some", func(t *testing.T) {
applyFive := Flap[int](5)
double := N.Mul(2)
// Should apply value to function
AssertEq(Some(10))(applyFive(Some(double)))(t)
})
t.Run("positive case - multiple operations", func(t *testing.T) {
applyTen := Flap[int](10)
triple := N.Mul(3)
// Should work with different values
AssertEq(Some(30))(applyTen(Some(triple)))(t)
})
t.Run("negative case - function is None", func(t *testing.T) {
applyFive := Flap[int](5)
// Should return None when function is None
AssertEq(None[int]())(applyFive(None[func(int) int]()))(t)
})
}
// Test String and Format
func TestStringFormat(t *testing.T) {
str := ToString(Some(42))
assert.Contains(t, str, "Some")
assert.Contains(t, str, "42")
str = ToString(None[int]())
assert.Contains(t, str, "None")
}
// // Test Semigroup
// func TestSemigroup(t *testing.T) {
// intSemigroup := S.MakeSemigroup(func(a, b int) int { return a + b })
// optSemigroup := Semigroup[int]()(intSemigroup)
// AssertEq(Some(5), optSemigroup.Concat(Some(2), Some(3)))
// AssertEq(Some(2), optSemigroup.Concat(Some(2), None[int]()))
// AssertEq(Some(3), optSemigroup.Concat(None[int](), Some(3)))
// AssertEq(None[int](), optSemigroup.Concat(None[int](), None[int]()))
// }
// // Test Monoid
// func TestMonoid(t *testing.T) {
// intSemigroup := S.MakeSemigroup(func(a, b int) int { return a + b })
// optMonoid := Monoid[int]()(intSemigroup)
// AssertEq(Some(5), optMonoid.Concat(Some(2), Some(3)))
// AssertEq(None[int](), optMonoid.Empty())
// }
// // Test ApplySemigroup
// func TestApplySemigroup(t *testing.T) {
// intSemigroup := S.MakeSemigroup(func(a, b int) int { return a + b })
// optSemigroup := ApplySemigroup(intSemigroup)
// AssertEq(Some(5), optSemigroup.Concat(Some(2), Some(3)))
// AssertEq(None[int](), optSemigroup.Concat(Some(2), None[int]()))
// }
// // Test ApplicativeMonoid
// func TestApplicativeMonoid(t *testing.T) {
// intMonoid := M.MakeMonoid(func(a, b int) int { return a + b }, 0)
// optMonoid := ApplicativeMonoid(intMonoid)
// AssertEq(Some(5), optMonoid.Concat(Some(2), Some(3)))
// AssertEq(Some(0), optMonoid.Empty())
// }
// // Test AlternativeMonoid
// func TestAlternativeMonoid(t *testing.T) {
// intMonoid := M.MakeMonoid(func(a, b int) int { return a + b }, 0)
// optMonoid := AlternativeMonoid(intMonoid)
// // AlternativeMonoid uses applicative semantics, so it combines values
// AssertEq(Some(5), optMonoid.Concat(Some(2), Some(3)))
// AssertEq(Some(3), optMonoid.Concat(None[int](), Some(3)))
// AssertEq(Some(0), optMonoid.Empty())
// }
// // Test AltMonoid
// func TestAltMonoid(t *testing.T) {
// optMonoid := AltMonoid[int]()
// AssertEq(Some(2), optMonoid.Concat(Some(2), Some(3)))
// AssertEq(Some(3), optMonoid.Concat(None[int](), Some(3)))
// AssertEq(None[int](), optMonoid.Empty())
// }
// Test Do, Let, LetTo, BindTo
func TestDoLetLetToBindTo(t *testing.T) {
type State struct {
x int
y int
computed int
name string
}
result, resultok := Pipe5(
State{},
Do,
Let(func(c int) func(State) State {
return func(s State) State { s.x = c; return s }
}, func(s State) int { return 5 }),
LetTo(func(n string) func(State) State {
return func(s State) State { s.name = n; return s }
}, "test"),
Bind(func(y int) func(State) State {
return func(s State) State { s.y = y; return s }
}, func(s State) (int, bool) { return Some(10) }),
Map(func(s State) State {
s.computed = s.x + s.y
return s
}),
)
AssertEq(Some(State{x: 5, y: 10, computed: 15, name: "test"}))(result, resultok)(t)
}
// Test BindTo
func TestBindToFunction(t *testing.T) {
type State struct {
value int
}
result, resultok := Pipe2(
42,
Some,
BindTo(func(x int) State { return State{value: x} }),
)
AssertEq(Some(State{value: 42}))(result, resultok)(t)
}
// // Test Functor
// func TestFunctor(t *testing.T) {
// f := Functor[int, string]()
// mapper := f.Map(strconv.Itoa)
// AssertEq(Some("42"), mapper(Some(42)))
// AssertEq(None[string](), mapper(None[int]()))
// }
// // Test Monad
// func TestMonad(t *testing.T) {
// m := Monad[int, string]()
// // Test Of
// AssertEq(Some(42), m.Of(42))
// // Test Map
// mapper := m.Map(strconv.Itoa)
// AssertEq(Some("42"), mapper(Some(42)))
// // Test Chain
// chainer := m.Chain(func(x int) (string, bool) {
// if x > 0 {
// return Some(fmt.Sprintf("%d", x))
// }
// return None[string]()
// })
// AssertEq(Some("42"), chainer(Some(42)))
// // Test Ap
// double := func(x int) string { return fmt.Sprintf("%d", x*2) }
// ap := m.Ap(Some(5))
// AssertEq(Some("10"), ap(Some(double)))
// }
// // Test Pointed
// func TestPointed(t *testing.T) {
// p := Pointed[int]()
// AssertEq(Some(42), p.Of(42))
// }
// Test ToAny
func TestToAny(t *testing.T) {
result, resultok := ToAny(42)
assert.True(t, IsSome(result, resultok))
assert.Equal(t, 42, result)
}
// Test TraverseArray
func TestTraverseArray(t *testing.T) {
validate := func(x int) (int, bool) {
if x > 0 {
return Some(x * 2)
}
return None[int]()
}
result, resultok := TraverseArray(validate)([]int{1, 2, 3})
AssertEq(Some([]int{2, 4, 6}))(result, resultok)(t)
result, resultok = TraverseArray(validate)([]int{1, -1, 3})
AssertEq(None[[]int]())(result, resultok)(t)
}
// Test TraverseArrayWithIndex
func TestTraverseArrayWithIndex(t *testing.T) {
f := func(i int, x int) (int, bool) {
if x > i {
return Some(x + i)
}
return None[int]()
}
result, resultok := TraverseArrayWithIndex(f)([]int{1, 2, 3})
AssertEq(Some([]int{1, 3, 5}))(result, resultok)(t)
}
// Test TraverseRecord
func TestTraverseRecord(t *testing.T) {
validate := func(x int) (string, bool) {
if x > 0 {
return Some(fmt.Sprintf("%d", x))
}
return None[string]()
}
input := map[string]int{"a": 1, "b": 2}
result, resultok := TraverseRecord[string](validate)(input)
AssertEq(Some(map[string]string{"a": "1", "b": "2"}))(result, resultok)(t)
}
// Test TraverseRecordWithIndex
func TestTraverseRecordWithIndex(t *testing.T) {
f := func(k string, v int) (string, bool) {
return Some(fmt.Sprintf("%s:%d", k, v))
}
input := map[string]int{"a": 1, "b": 2}
result, resultok := TraverseRecordWithIndex(f)(input)
assert.True(t, IsSome(result, resultok))
}
// Test TraverseTuple functions
func TestTraverseTuple2(t *testing.T) {
f1 := func(x int) (int, bool) { return Some(x * 2) }
f2 := func(s string) (string, bool) { return Some(s + "!") }
traverse := TraverseTuple2(f1, f2)
r1, r2, resultok := traverse(5, "hello")
assert.True(t, resultok)
assert.Equal(t, r1, 10)
assert.Equal(t, r2, "hello!")
}
// Test FromStrictCompare
func TestFromStrictCompare(t *testing.T) {
optOrd := FromStrictCompare[int]()
assert.Equal(t, 0, optOrd(Some(5))(Some(5)))
assert.Equal(t, -1, optOrd(Some(3))(Some(5)))
assert.Equal(t, +1, optOrd(Some(5))(Some(3)))
assert.Equal(t, -1, optOrd(None[int]())(Some(5)))
assert.Equal(t, +1, optOrd(Some(5))(None[int]()))
}

View File

@@ -0,0 +1,133 @@
// Copyright (c) 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 option
import (
"testing"
"github.com/IBM/fp-go/v2/internal/utils"
"github.com/stretchr/testify/assert"
)
func TestIsNone(t *testing.T) {
assert.True(t, IsNone(None[int]()))
assert.False(t, IsNone(Of(1)))
}
func TestIsSome(t *testing.T) {
assert.True(t, IsSome(Of(1)))
assert.False(t, IsSome(None[int]()))
}
func TestMapOption(t *testing.T) {
AssertEq(Map(utils.Double)(Some(2)))(Some(4))(t)
AssertEq(Map(utils.Double)(None[int]()))(None[int]())(t)
}
func TestAp(t *testing.T) {
AssertEq(Some(4))(Ap[int](Some(2))(Some(utils.Double)))(t)
AssertEq(None[int]())(Ap[int](None[int]())(Some(utils.Double)))(t)
AssertEq(None[int]())(Ap[int](Some(2))(None[func(int) int]()))(t)
AssertEq(None[int]())(Ap[int](None[int]())(None[func(int) int]()))(t)
}
func TestChain(t *testing.T) {
f := func(n int) (int, bool) { return Some(n * 2) }
g := func(_ int) (int, bool) { return None[int]() }
AssertEq(Some(2))(Chain(f)(Some(1)))(t)
AssertEq(None[int]())(Chain(f)(None[int]()))(t)
AssertEq(None[int]())(Chain(g)(Some(1)))(t)
AssertEq(None[int]())(Chain(g)(None[int]()))(t)
}
func TestChainToUnit(t *testing.T) {
t.Run("positive case - replace Some input with Some value", func(t *testing.T) {
replaceWith := ChainTo[int](Some("hello"))
// Should replace Some(42) with Some("hello")
AssertEq(Some("hello"))(replaceWith(Some(42)))(t)
})
t.Run("positive case - replace None input with Some value", func(t *testing.T) {
replaceWith := ChainTo[int](Some("hello"))
// Should replace None with Some("hello")
AssertEq(None[string]())(replaceWith(None[int]()))(t)
})
t.Run("positive case - replace with different types", func(t *testing.T) {
replaceWithNumber := ChainTo[string](Some(100))
// Should work with type conversion
AssertEq(Some(100))(replaceWithNumber(Some("test")))(t)
AssertEq(None[int]())(replaceWithNumber(None[string]()))(t)
})
t.Run("negative case - replace Some input with None", func(t *testing.T) {
replaceWithNone := ChainTo[int](None[string]())
// Should replace Some(42) with None
AssertEq(None[string]())(replaceWithNone(Some(42)))(t)
})
t.Run("negative case - replace None input with None", func(t *testing.T) {
replaceWithNone := ChainTo[int](None[string]())
// Should replace None with None
AssertEq(None[string]())(replaceWithNone(None[int]()))(t)
})
t.Run("negative case - chaining multiple ChainTo operations", func(t *testing.T) {
// Chain multiple ChainTo operations - each ChainTo ignores input and returns fixed value
step1 := ChainTo[int](Some("first"))
step2 := ChainTo[string](Some(2.5))
step3 := ChainTo[float64](None[bool]())
result1, result1ok := step1(Some(1))
result2, result2ok := step2(result1, result1ok)
result3, result3ok := step3(result2, result2ok)
// Final result should be None
AssertEq(None[bool]())(result3, result3ok)(t)
})
}
// func TestFlatten(t *testing.T) {
// assert.Equal(t, Of(1), F.Pipe1(Of(Of(1)), Flatten[int]))
// }
// func TestFold(t *testing.T) {
// f := F.Constant("none")
// g := func(s string) string { return fmt.Sprintf("some%d", len(s)) }
// fold := Fold(f, g)
// assert.Equal(t, "none", fold(None[string]()))
// assert.Equal(t, "some3", fold(Some("abc")))
// }
// func TestFromPredicate(t *testing.T) {
// p := func(n int) bool { return n > 2 }
// f := FromPredicate(p)
// assert.Equal(t, None[int](), f(1))
// assert.Equal(t, Some(3), f(3))
// }
// func TestAlt(t *testing.T) {
// assert.Equal(t, Some(1), F.Pipe1(Some(1), Alt(F.Constant(Some(2)))))
// assert.Equal(t, Some(2), F.Pipe1(Some(2), Alt(F.Constant(None[int]()))))
// assert.Equal(t, Some(1), F.Pipe1(None[int](), Alt(F.Constant(Some(1)))))
// assert.Equal(t, None[int](), F.Pipe1(None[int](), Alt(F.Constant(None[int]()))))
// }

View File

@@ -0,0 +1,66 @@
// Copyright (c) 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 option
import (
C "github.com/IBM/fp-go/v2/constraints"
"github.com/IBM/fp-go/v2/ord"
)
// Ord constructs an ordering for Option[A] given an ordering for A.
// The ordering follows these rules:
// - None is considered less than any Some value
// - Two None values are equal
// - Two Some values are compared using the provided Ord for A
//
// Example:
//
// intOrd := ord.FromStrictCompare[int]()
// optOrd := Ord(intOrd)
// optOrd.Compare(None[int](), Some(5)) // -1 (None < Some)
// optOrd.Compare(Some(3), Some(5)) // -1 (3 < 5)
// optOrd.Compare(Some(5), Some(3)) // 1 (5 > 3)
// optOrd.Compare(None[int](), None[int]()) // 0 (equal)
func Ord[A any](o ord.Ord[A]) func(A, bool) func(A, bool) int {
return func(l A, lok bool) func(A, bool) int {
if lok {
return func(r A, rok bool) int {
if rok {
return o.Compare(l, r)
}
return +1
}
}
return func(_ A, rok bool) int {
if rok {
return -1
}
return 0
}
}
}
// FromStrictCompare constructs an Ord for Option[A] using Go's built-in comparison operators for type A.
// This is a convenience function for ordered types (types that support <, >, ==).
//
// Example:
//
// optOrd := FromStrictCompare[int]()
// optOrd.Compare(Some(5), Some(10)) // -1
// optOrd.Compare(None[int](), Some(5)) // -1
func FromStrictCompare[A C.Ordered]() func(A, bool) func(A, bool) int {
return Ord(ord.FromStrictCompare[A]())
}

View File

@@ -0,0 +1,46 @@
// Copyright (c) 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 option
import (
"testing"
S "github.com/IBM/fp-go/v2/string"
"github.com/stretchr/testify/assert"
)
// it('getOrd', () => {
// const OS = _.getOrd(S.Ord)
// U.deepStrictEqual(OS.compare(_.none, _.none), 0)
// U.deepStrictEqual(OS.compare(_.some('a'), _.none), 1)
// U.deepStrictEqual(OS.compare(_.none, _.some('a')), -1)
// U.deepStrictEqual(OS.compare(_.some('a'), _.some('a')), 0)
// U.deepStrictEqual(OS.compare(_.some('a'), _.some('b')), -1)
// U.deepStrictEqual(OS.compare(_.some('b'), _.some('a')), 1)
// })
func TestOrd(t *testing.T) {
os := Ord(S.Ord)
assert.Equal(t, 0, os((None[string]()))(None[string]()))
assert.Equal(t, +1, os(Some("a"))(None[string]()))
assert.Equal(t, -1, os(None[string]())(Some("a")))
assert.Equal(t, 0, os(Some("a"))(Some("a")))
assert.Equal(t, -1, os(Some("a"))(Some("b")))
assert.Equal(t, +1, os(Some("b"))(Some("a")))
}

View File

@@ -0,0 +1,39 @@
// Copyright (c) 2024 - 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 option
type (
optionPointed[A any] struct{}
Pointed[A any] interface {
Of(A) (A, bool)
}
)
func (o optionPointed[A]) Of(a A) (A, bool) {
return Of(a)
}
// Pointed implements the Pointed operations for Option.
// A pointed functor is a functor with an Of operation that wraps a value.
//
// Example:
//
// p := Pointed[int]()
// result := p.Of(42) // Some(42)
func MakePointed[A any]() Pointed[A] {
return optionPointed[A]{}
}

View File

@@ -0,0 +1,99 @@
// Copyright (c) 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 option
// TraverseRecordG transforms a record (map) by applying a function that returns an Option to each value.
// Returns Some containing the map of results if all operations succeed, None if any fails.
// This is the generic version that works with custom map types.
//
// Example:
//
// validate := func(x int) Option[int] {
// if x > 0 { return Some(x * 2) }
// return None[int]()
// }
// input := map[string]int{"a": 1, "b": 2}
// result := TraverseRecordG[map[string]int, map[string]int](validate)(input) // Some(map[a:2 b:4])
func TraverseRecordG[GA ~map[K]A, GB ~map[K]B, K comparable, A, B any](f Kleisli[A, B]) Kleisli[GA, GB] {
return func(ga GA) (GB, bool) {
gb := make(GB)
for k, a := range ga {
if b, ok := f(a); ok {
gb[k] = b
} else {
return gb, false
}
}
return gb, true
}
}
// TraverseRecord transforms a record (map) by applying a function that returns an Option to each value.
// Returns Some containing the map of results if all operations succeed, None if any fails.
//
// Example:
//
// validate := func(x int) Option[string] {
// if x > 0 { return Some(fmt.Sprintf("%d", x)) }
// return None[string]()
// }
// input := map[string]int{"a": 1, "b": 2}
// result := TraverseRecord(validate)(input) // Some(map[a:"1" b:"2"])
func TraverseRecord[K comparable, A, B any](f Kleisli[A, B]) Kleisli[map[K]A, map[K]B] {
return TraverseRecordG[map[K]A, map[K]B](f)
}
// TraverseRecordWithIndexG transforms a record by applying a function that receives both key and value.
// Returns Some containing the map of results if all operations succeed, None if any fails.
// This is the generic version that works with custom map types.
//
// Example:
//
// f := func(k string, v int) Option[string] {
// return Some(fmt.Sprintf("%s:%d", k, v))
// }
// input := map[string]int{"a": 1, "b": 2}
// result := TraverseRecordWithIndexG[map[string]int, map[string]string](f)(input) // Some(map[a:"a:1" b:"b:2"])
func TraverseRecordWithIndexG[GA ~map[K]A, GB ~map[K]B, K comparable, A, B any](f func(K, A) (B, bool)) Kleisli[GA, GB] {
return func(ga GA) (GB, bool) {
gb := make(GB)
for k, a := range ga {
if b, ok := f(k, a); ok {
gb[k] = b
} else {
return gb, false
}
}
return gb, true
}
}
// TraverseRecordWithIndex transforms a record by applying a function that receives both key and value.
// Returns Some containing the map of results if all operations succeed, None if any fails.
//
// Example:
//
// f := func(k string, v int) Option[int] {
// if v > 0 { return Some(v) }
// return None[int]()
// }
// input := map[string]int{"a": 1, "b": 2}
// result := TraverseRecordWithIndex(f)(input) // Some(map[a:1 b:2])
func TraverseRecordWithIndex[K comparable, A, B any](f func(K, A) (B, bool)) Kleisli[map[K]A, map[K]B] {
return TraverseRecordWithIndexG[map[K]A, map[K]B](f)
}

View File

@@ -0,0 +1,102 @@
// Copyright (c) 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 testing
import (
"testing"
EQ "github.com/IBM/fp-go/v2/eq"
L "github.com/IBM/fp-go/v2/internal/monad/testing"
O "github.com/IBM/fp-go/v2/option"
)
// AssertLaws asserts the monad laws for the Option monad.
// This function verifies that Option satisfies the functor, applicative, and monad laws.
//
// The laws tested include:
// - Functor laws: identity and composition
// - Applicative laws: identity, composition, homomorphism, and interchange
// - Monad laws: left identity, right identity, and associativity
//
// Parameters:
// - t: testing instance
// - eqa, eqb, eqc: equality predicates for types A, B, and C
// - ab: a function from A to B for testing
// - bc: a function from B to C for testing
//
// Returns a function that takes a value of type A and returns true if all laws hold.
//
// Example:
//
// func TestOptionLaws(t *testing.T) {
// eqInt := eq.FromStrictEquals[int]()
// eqString := eq.FromStrictEquals[string]()
// eqBool := eq.FromStrictEquals[bool]()
//
// ab := strconv.Itoa
// bc := func(s string) bool { return len(s) > 0 }
//
// assert := AssertLaws(t, eqInt, eqString, eqBool, ab, bc)
// assert(42) // verifies laws hold for value 42
// }
func AssertLaws[A, B, C any](t *testing.T,
eqa EQ.Eq[A],
eqb EQ.Eq[B],
eqc EQ.Eq[C],
ab func(A) B,
bc func(B) C,
) func(a A) bool {
return L.AssertLaws(t,
O.Eq(eqa),
O.Eq(eqb),
O.Eq(eqc),
O.Of[A],
O.Of[B],
O.Of[C],
O.Of[func(A) A],
O.Of[func(A) B],
O.Of[func(B) C],
O.Of[func(func(A) B) B],
O.MonadMap[A, A],
O.MonadMap[A, B],
O.MonadMap[A, C],
O.MonadMap[B, C],
O.MonadMap[func(B) C, func(func(A) B) func(A) C],
O.MonadChain[A, A],
O.MonadChain[A, B],
O.MonadChain[A, C],
O.MonadChain[B, C],
O.MonadAp[A, A],
O.MonadAp[B, A],
O.MonadAp[C, B],
O.MonadAp[C, A],
O.MonadAp[B, func(A) B],
O.MonadAp[func(A) C, func(A) B],
ab,
bc,
)
}

View File

@@ -0,0 +1,47 @@
// Copyright (c) 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 testing
import (
"fmt"
"testing"
EQ "github.com/IBM/fp-go/v2/eq"
"github.com/stretchr/testify/assert"
)
func TestMonadLaws(t *testing.T) {
// some comparison
eqa := EQ.FromStrictEquals[bool]()
eqb := EQ.FromStrictEquals[int]()
eqc := EQ.FromStrictEquals[string]()
ab := func(a bool) int {
if a {
return 1
}
return 0
}
bc := func(b int) string {
return fmt.Sprintf("value %d", b)
}
laws := AssertLaws(t, eqa, eqb, eqc, ab, bc)
assert.True(t, laws(true))
assert.True(t, laws(false))
}

View File

@@ -0,0 +1,50 @@
// Copyright (c) 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 option
func toType[T any](a any) (T, bool) {
b, ok := a.(T)
return b, ok
}
// ToType attempts to convert a value of type any to a specific type T using type assertion.
// Returns Some(value) if the type assertion succeeds, None if it fails.
//
// Example:
//
// var x any = 42
// result := ToType[int](x) // Some(42)
//
// var y any = "hello"
// result := ToType[int](y) // None (wrong type)
//
//go:inline
func ToType[T any](src any) (T, bool) {
return toType[T](src)
}
// ToAny converts a value of any type to Option[any].
// This always succeeds and returns Some containing the value as any.
//
// Example:
//
// result := ToAny(42) // Some(any(42))
// result := ToAny("hello") // Some(any("hello"))
//
//go:inline
func ToAny[T any](src T) (any, bool) {
return Of(any(src))
}

View File

@@ -0,0 +1,35 @@
// Copyright (c) 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 option
import (
"testing"
)
func TestTypeConversion(t *testing.T) {
var src any = "Carsten"
dst, dstOk := ToType[string](src)
AssertEq(Some("Carsten"))(dst, dstOk)(t)
}
func TestInvalidConversion(t *testing.T) {
var src any = make(map[string]string)
dst, dstOk := ToType[int](src)
AssertEq(None[int]())(dst, dstOk)(t)
}

View File

@@ -0,0 +1,12 @@
package option
import (
"iter"
"github.com/IBM/fp-go/v2/endomorphism"
)
type (
Seq[T any] = iter.Seq[T]
Endomorphism[T any] = endomorphism.Endomorphism[T]
)

View File

@@ -15,6 +15,10 @@
package array
func Of[GA ~[]A, A any](a A) GA {
return GA{a}
}
func Slice[GA ~[]A, A any](low, high int) func(as GA) GA {
return func(as GA) GA {
length := len(as)
@@ -77,8 +81,7 @@ func IsNonNil[GA ~[]A, A any](as GA) bool {
func Reduce[GA ~[]A, A, B any](fa GA, f func(B, A) B, initial B) B {
current := initial
count := len(fa)
for i := 0; i < count; i++ {
for i := range len(fa) {
current = f(current, fa[i])
}
return current
@@ -86,8 +89,7 @@ func Reduce[GA ~[]A, A, B any](fa GA, f func(B, A) B, initial B) B {
func ReduceWithIndex[GA ~[]A, A, B any](fa GA, f func(int, B, A) B, initial B) B {
current := initial
count := len(fa)
for i := 0; i < count; i++ {
for i := range len(fa) {
current = f(i, current, fa[i])
}
return current
@@ -142,7 +144,7 @@ func UpsertAt[GA ~[]A, A any](a A) func(GA) GA {
func MonadMap[GA ~[]A, GB ~[]B, A, B any](as GA, f func(a A) B) GB {
count := len(as)
bs := make(GB, count)
for i := count - 1; i >= 0; i-- {
for i := range count {
bs[i] = f(as[i])
}
return bs
@@ -157,7 +159,7 @@ func Map[GA ~[]A, GB ~[]B, A, B any](f func(a A) B) func(GA) GB {
func MonadMapWithIndex[GA ~[]A, GB ~[]B, A, B any](as GA, f func(idx int, a A) B) GB {
count := len(as)
bs := make(GB, count)
for i := count - 1; i >= 0; i-- {
for i := range count {
bs[i] = f(i, as[i])
}
return bs
@@ -166,3 +168,19 @@ func MonadMapWithIndex[GA ~[]A, GB ~[]B, A, B any](as GA, f func(idx int, a A) B
func ConstNil[GA ~[]A, A any]() GA {
return (GA)(nil)
}
func Concat[GT ~[]T, T any](left, right GT) GT {
// some performance checks
ll := len(left)
if ll == 0 {
return right
}
lr := len(right)
if lr == 0 {
return left
}
// need to copy
buf := make(GT, ll+lr)
copy(buf[copy(buf, left):], right)
return buf
}

View File

@@ -19,6 +19,72 @@ import (
F "github.com/IBM/fp-go/v2/function"
)
func MonadSequenceSegment[HKTB, HKTRB any](
fof func(HKTB) HKTRB,
empty HKTRB,
concat func(HKTRB, HKTRB) HKTRB,
fbs []HKTB,
start, end int,
) HKTRB {
switch end - start {
case 0:
return empty
case 1:
return fof(fbs[start])
default:
mid := (start + end) / 2
return concat(
MonadSequenceSegment(fof, empty, concat, fbs, start, mid),
MonadSequenceSegment(fof, empty, concat, fbs, mid, end),
)
}
}
func SequenceSegment[HKTB, HKTRB any](
fof func(HKTB) HKTRB,
empty HKTRB,
concat func(HKTRB, HKTRB) HKTRB,
) func([]HKTB) HKTRB {
concat_f := func(left, right func([]HKTB) HKTRB) func([]HKTB) HKTRB {
return func(fbs []HKTB) HKTRB {
return concat(left(fbs), right(fbs))
}
}
empty_f := F.Constant1[[]HKTB](empty)
at := func(idx int) func([]HKTB) HKTRB {
return func(fbs []HKTB) HKTRB {
return fof(fbs[idx])
}
}
var divide func(start, end int) func([]HKTB) HKTRB
divide = func(start, end int) func([]HKTB) HKTRB {
switch end - start {
case 0:
return empty_f
case 1:
return at(start)
default:
mid := (start + end) / 2
left := divide(start, mid)
right := divide(mid, end)
return concat_f(left, right)
}
}
// TODO this could be cached by length
get_divide := func(len int) func([]HKTB) HKTRB {
return divide(0, len)
}
return func(fbs []HKTB) HKTRB {
return get_divide(len(fbs))(fbs)
}
}
/*
*
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
@@ -79,6 +145,34 @@ func TraverseWithIndex[GA ~[]A, GB ~[]B, A, B, HKTB, HKTAB, HKTRB any](
}
}
/*
*
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
HKTRB = HKT<GB>
HKTB = HKT<B>
HKTAB = HKT<func(A)B>
*/
func MonadSequence[GA ~[]HKTA, HKTA, HKTRA any](
fof func(HKTA) HKTRA,
empty HKTRA,
concat func(HKTRA, HKTRA) HKTRA,
ta GA) HKTRA {
return MonadSequenceSegment(fof, empty, concat, ta, 0, len(ta))
}
func Sequence[GA ~[]HKTA, HKTA, HKTRA any](
fof func(HKTA) HKTRA,
empty HKTRA,
concat func(HKTRA, HKTRA) HKTRA,
) func(GA) HKTRA {
return func(ma GA) HKTRA {
return MonadSequence(fof, empty, concat, ma)
}
}
func MonadTraverseReduce[GA ~[]A, GB, A, B, HKTB, HKTAB, HKTRB any](
fof func(GB) HKTRB,
fmap func(func(GB) func(B) GB) func(HKTRB) HKTAB,

100
v2/internal/iter/iter.go Normal file
View File

@@ -0,0 +1,100 @@
package iter
import (
F "github.com/IBM/fp-go/v2/function"
M "github.com/IBM/fp-go/v2/monoid"
)
func MonadReduceWithIndex[GA ~func(yield func(A) bool), A, B any](fa GA, f func(int, B, A) B, initial B) B {
current := initial
var i int
for a := range fa {
current = f(i, current, a)
i += 1
}
return current
}
func MonadReduce[GA ~func(yield func(A) bool), A, B any](fa GA, f func(B, A) B, initial B) B {
current := initial
for a := range fa {
current = f(current, a)
}
return current
}
// Concat concatenates two sequences, yielding all elements from left followed by all elements from right.
func Concat[GT ~func(yield func(T) bool), T any](left, right GT) GT {
return func(yield func(T) bool) {
for t := range left {
if !yield(t) {
return
}
}
for t := range right {
if !yield(t) {
return
}
}
}
}
func Of[GA ~func(yield func(A) bool), A any](a A) GA {
return func(yield func(A) bool) {
yield(a)
}
}
func MonadAppend[GA ~func(yield func(A) bool), A any](f GA, tail A) GA {
return Concat(f, Of[GA](tail))
}
func Append[GA ~func(yield func(A) bool), A any](tail A) func(GA) GA {
return F.Bind2nd(Concat[GA], Of[GA](tail))
}
func Prepend[GA ~func(yield func(A) bool), A any](head A) func(GA) GA {
return F.Bind1st(Concat[GA], Of[GA](head))
}
func Empty[GA ~func(yield func(A) bool), A any]() GA {
return func(_ func(A) bool) {}
}
func ToArray[GA ~func(yield func(A) bool), GB ~[]A, A any](fa GA) GB {
bs := make(GB, 0)
for a := range fa {
bs = append(bs, a)
}
return bs
}
func MonadMapToArray[GA ~func(yield func(A) bool), GB ~[]B, A, B any](fa GA, f func(A) B) GB {
bs := make(GB, 0)
for a := range fa {
bs = append(bs, f(a))
}
return bs
}
func MapToArray[GA ~func(yield func(A) bool), GB ~[]B, A, B any](f func(A) B) func(GA) GB {
return F.Bind2nd(MonadMapToArray[GA, GB], f)
}
func MonadMapToArrayWithIndex[GA ~func(yield func(A) bool), GB ~[]B, A, B any](fa GA, f func(int, A) B) GB {
bs := make(GB, 0)
var i int
for a := range fa {
bs = append(bs, f(i, a))
i += 1
}
return bs
}
func MapToArrayWithIndex[GA ~func(yield func(A) bool), GB ~[]B, A, B any](f func(int, A) B) func(GA) GB {
return F.Bind2nd(MonadMapToArrayWithIndex[GA, GB], f)
}
func Monoid[GA ~func(yield func(A) bool), A any]() M.Monoid[GA] {
return M.MakeMonoid(Concat[GA], Empty[GA]())
}

View File

@@ -0,0 +1,203 @@
// 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 iter
import (
F "github.com/IBM/fp-go/v2/function"
INTA "github.com/IBM/fp-go/v2/internal/array"
M "github.com/IBM/fp-go/v2/monoid"
)
/*
*
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
HKTRB = HKT<GB>
HKTB = HKT<B>
HKTAB = HKT<func(A)B>
*/
func MonadTraverse[GA ~func(yield func(A) bool), GB ~func(yield func(B) bool), A, B, HKT_B, HKT_GB_GB, HKT_GB any](
fmap_b func(HKT_B, func(B) GB) HKT_GB,
fof_gb func(GB) HKT_GB,
fmap_gb func(HKT_GB, func(GB) func(GB) GB) HKT_GB_GB,
fap_gb func(HKT_GB_GB, HKT_GB) HKT_GB,
ta GA,
f func(A) HKT_B) HKT_GB {
fof := F.Bind2nd(fmap_b, Of[GB])
empty := fof_gb(Empty[GB]())
cb := F.Curry2(Concat[GB])
concat_gb := F.Bind2nd(fmap_gb, cb)
concat := func(first HKT_GB, second HKT_GB) HKT_GB {
return fap_gb(concat_gb(first), second)
}
// convert to an array
hktb := MonadMapToArray[GA, []HKT_B](ta, f)
return INTA.MonadSequenceSegment(fof, empty, concat, hktb, 0, len(hktb))
}
func Traverse[GA ~func(yield func(A) bool), GB ~func(yield func(B) bool), A, B, HKT_B, HKT_GB_GB, HKT_GB any](
fmap_b func(func(B) GB) func(HKT_B) HKT_GB,
fof_gb func(GB) HKT_GB,
fmap_gb func(func(GB) func(GB) GB) func(HKT_GB) HKT_GB_GB,
fap_gb func(HKT_GB_GB, HKT_GB) HKT_GB,
f func(A) HKT_B) func(GA) HKT_GB {
fof := fmap_b(Of[GB])
empty := fof_gb(Empty[GB]())
cb := F.Curry2(Concat[GB])
concat_gb := fmap_gb(cb)
concat := func(first, second HKT_GB) HKT_GB {
return fap_gb(concat_gb(first), second)
}
return func(ma GA) HKT_GB {
// return INTA.SequenceSegment(fof, empty, concat)(MapToArray[GA, []HKT_B](f)(ma))
hktb := MonadMapToArray[GA, []HKT_B](ma, f)
return INTA.MonadSequenceSegment(fof, empty, concat, hktb, 0, len(hktb))
}
}
func MonadSequence[GA ~func(yield func(HKTA) bool), HKTA, HKTRA any](
fof func(HKTA) HKTRA,
m M.Monoid[HKTRA],
ta GA) HKTRA {
// convert to an array
hktb := ToArray[GA, []HKTA](ta)
return INTA.MonadSequenceSegment(fof, m.Empty(), m.Concat, hktb, 0, len(hktb))
}
/*
*
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
HKTRB = HKT<GB>
HKTB = HKT<B>
HKTAB = HKT<func(A)B>
*/
func MonadTraverseWithIndex[GA ~func(yield func(A) bool), A, HKTB, HKTRB any](
fof func(HKTB) HKTRB,
m M.Monoid[HKTRB],
ta GA,
f func(int, A) HKTB) HKTRB {
// convert to an array
hktb := MonadMapToArrayWithIndex[GA, []HKTB](ta, f)
return INTA.MonadSequenceSegment(fof, m.Empty(), m.Concat, hktb, 0, len(hktb))
}
func Sequence[GA ~func(yield func(HKTA) bool), HKTA, HKTRA any](
fof func(HKTA) HKTRA,
m M.Monoid[HKTRA]) func(GA) HKTRA {
return func(ma GA) HKTRA {
return MonadSequence(fof, m, ma)
}
}
func TraverseWithIndex[GA ~func(yield func(A) bool), A, HKTB, HKTRB any](
fof func(HKTB) HKTRB,
m M.Monoid[HKTRB],
f func(int, A) HKTB) func(GA) HKTRB {
return func(ma GA) HKTRB {
return MonadTraverseWithIndex(fof, m, ma, f)
}
}
func MonadTraverseReduce[GA ~func(yield func(A) bool), GB, A, B, HKTB, HKTAB, HKTRB any](
fof func(GB) HKTRB,
fmap func(func(GB) func(B) GB) func(HKTRB) HKTAB,
fap func(HKTB) func(HKTAB) HKTRB,
ta GA,
transform func(A) HKTB,
reduce func(GB, B) GB,
initial GB,
) HKTRB {
mmap := fmap(F.Curry2(reduce))
return MonadReduce(ta, func(r HKTRB, a A) HKTRB {
return F.Pipe2(
r,
mmap,
fap(transform(a)),
)
}, fof(initial))
}
func MonadTraverseReduceWithIndex[GA ~func(yield func(A) bool), GB, A, B, HKTB, HKTAB, HKTRB any](
fof func(GB) HKTRB,
fmap func(func(GB) func(B) GB) func(HKTRB) HKTAB,
fap func(HKTB) func(HKTAB) HKTRB,
ta GA,
transform func(int, A) HKTB,
reduce func(GB, B) GB,
initial GB,
) HKTRB {
mmap := fmap(F.Curry2(reduce))
return MonadReduceWithIndex(ta, func(idx int, r HKTRB, a A) HKTRB {
return F.Pipe2(
r,
mmap,
fap(transform(idx, a)),
)
}, fof(initial))
}
func TraverseReduce[GA ~func(yield func(A) bool), GB, A, B, HKTB, HKTAB, HKTRB any](
fof func(GB) HKTRB,
fmap func(func(GB) func(B) GB) func(HKTRB) HKTAB,
fap func(HKTB) func(HKTAB) HKTRB,
transform func(A) HKTB,
reduce func(GB, B) GB,
initial GB,
) func(GA) HKTRB {
return func(ta GA) HKTRB {
return MonadTraverseReduce(fof, fmap, fap, ta, transform, reduce, initial)
}
}
func TraverseReduceWithIndex[GA ~func(yield func(A) bool), GB, A, B, HKTB, HKTAB, HKTRB any](
fof func(GB) HKTRB,
fmap func(func(GB) func(B) GB) func(HKTRB) HKTAB,
fap func(HKTB) func(HKTAB) HKTRB,
transform func(int, A) HKTB,
reduce func(GB, B) GB,
initial GB,
) func(GA) HKTRB {
return func(ta GA) HKTRB {
return MonadTraverseReduceWithIndex(fof, fmap, fap, ta, transform, reduce, initial)
}
}

View File

@@ -0,0 +1,9 @@
package iter
import (
I "iter"
)
type (
Seq[A any] = I.Seq[A]
)

View File

@@ -23,6 +23,7 @@ import (
F "github.com/IBM/fp-go/v2/function"
M "github.com/IBM/fp-go/v2/monoid"
N "github.com/IBM/fp-go/v2/number"
S "github.com/IBM/fp-go/v2/semigroup"
T "github.com/IBM/fp-go/v2/tuple"
"github.com/stretchr/testify/assert"
@@ -55,14 +56,14 @@ func TestMapTo(t *testing.T) {
// Test MonadApSeq
func TestMonadApSeq(t *testing.T) {
f := Of(func(x int) int { return x * 2 })
f := Of(N.Mul(2))
result := MonadApSeq(f, Of(21))
assert.Equal(t, 42, result())
}
// Test ApPar
func TestApPar(t *testing.T) {
f := Of(func(x int) int { return x * 2 })
f := Of(N.Mul(2))
result := F.Pipe1(f, ApPar[int](Of(21)))
assert.Equal(t, 42, result())
}
@@ -128,14 +129,14 @@ func TestDefer(t *testing.T) {
// Test MonadFlap
func TestMonadFlap(t *testing.T) {
f := Of(func(x int) int { return x * 2 })
f := Of(N.Mul(2))
result := MonadFlap(f, 21)
assert.Equal(t, 42, result())
}
// Test Flap
func TestFlap(t *testing.T) {
f := Of(func(x int) int { return x * 2 })
f := Of(N.Mul(2))
result := F.Pipe1(f, Flap[int](21))
assert.Equal(t, 42, result())
}
@@ -355,7 +356,7 @@ func TestApplicativeTypeClass(t *testing.T) {
assert.Equal(t, 21, io1())
// Test Map
io2 := app.Map(func(x int) int { return x * 2 })(io1)
io2 := app.Map(N.Mul(2))(io1)
assert.Equal(t, 42, io2())
}

View File

@@ -18,6 +18,7 @@ package io
import (
F "github.com/IBM/fp-go/v2/function"
INTA "github.com/IBM/fp-go/v2/internal/array"
INTI "github.com/IBM/fp-go/v2/internal/iter"
INTR "github.com/IBM/fp-go/v2/internal/record"
)
@@ -60,6 +61,18 @@ func TraverseArray[A, B any](f Kleisli[A, B]) Kleisli[[]A, []B] {
)
}
func TraverseIter[A, B any](f Kleisli[A, B]) Kleisli[Seq[A], Seq[B]] {
return INTI.Traverse[Seq[A]](
Map[B],
Of[Seq[B]],
Map[Seq[B]],
MonadAp[Seq[B]],
f,
)
}
// TraverseArrayWithIndex is like TraverseArray but the function also receives the index.
// Executes in parallel by default.
//

7
v2/io/types.go Normal file
View File

@@ -0,0 +1,7 @@
package io
import "iter"
type (
Seq[T any] = iter.Seq[T]
)

897
v2/iterator/iter/iter.go Normal file
View File

@@ -0,0 +1,897 @@
// 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 iter provides functional programming utilities for Go 1.23+ iterators.
//
// This package offers a comprehensive set of operations for working with lazy sequences
// using Go's native iter.Seq and iter.Seq2 types. It follows functional programming
// principles and provides monadic operations, transformations, and reductions.
//
// The package supports:
// - Functor operations (Map, MapWithIndex, MapWithKey)
// - Monad operations (Chain, Flatten, Ap)
// - Filtering (Filter, FilterMap, FilterWithIndex, FilterWithKey)
// - Folding and reduction (Reduce, Fold, FoldMap)
// - Sequence construction (Of, From, MakeBy, Replicate)
// - Sequence combination (Zip, Prepend, Append)
//
// All operations are lazy and only execute when the sequence is consumed via iteration.
//
// Example usage:
//
// // Create a sequence and transform it
// seq := From(1, 2, 3, 4, 5)
// doubled := Map(N.Mul(2))(seq)
//
// // Filter and reduce
// evens := Filter(func(x int) bool { return x%2 == 0 })(doubled)
// sum := MonadReduce(evens, func(acc, x int) int { return acc + x }, 0)
// // sum = 20 (2+4+6+8+10 from doubled evens)
package iter
import (
"slices"
I "iter"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/functor"
G "github.com/IBM/fp-go/v2/internal/iter"
M "github.com/IBM/fp-go/v2/monoid"
"github.com/IBM/fp-go/v2/option"
)
// Of creates a sequence containing a single element.
//
// Example:
//
// seq := Of(42)
// // yields: 42
//
//go:inline
func Of[A any](a A) Seq[A] {
return G.Of[Seq[A]](a)
}
// Of2 creates a key-value sequence containing a single key-value pair.
//
// Example:
//
// seq := Of2("key", 100)
// // yields: ("key", 100)
func Of2[K, A any](k K, a A) Seq2[K, A] {
return func(yield func(K, A) bool) {
yield(k, a)
}
}
// MonadMap transforms each element in a sequence using the provided function.
// This is the monadic version that takes the sequence as the first parameter.
//
// Example:
//
// seq := From(1, 2, 3)
// result := MonadMap(seq, N.Mul(2))
// // yields: 2, 4, 6
func MonadMap[A, B any](as Seq[A], f func(A) B) Seq[B] {
return func(yield Predicate[B]) {
for a := range as {
if !yield(f(a)) {
return
}
}
}
}
// Map returns a function that transforms each element in a sequence.
// This is the curried version of MonadMap.
//
// Example:
//
// double := Map(N.Mul(2))
// seq := From(1, 2, 3)
// result := double(seq)
// // yields: 2, 4, 6
//
//go:inline
func Map[A, B any](f func(A) B) Operator[A, B] {
return F.Bind2nd(MonadMap[A, B], f)
}
// MonadMapWithIndex transforms each element in a sequence using a function that also receives the element's index.
//
// Example:
//
// seq := From("a", "b", "c")
// result := MonadMapWithIndex(seq, func(i int, s string) string {
// return fmt.Sprintf("%d:%s", i, s)
// })
// // yields: "0:a", "1:b", "2:c"
func MonadMapWithIndex[A, B any](as Seq[A], f func(int, A) B) Seq[B] {
return func(yield Predicate[B]) {
var i int
for a := range as {
if !yield(f(i, a)) {
return
}
i += 1
}
}
}
// MapWithIndex returns a function that transforms elements with their indices.
// This is the curried version of MonadMapWithIndex.
//
// Example:
//
// addIndex := MapWithIndex(func(i int, s string) string {
// return fmt.Sprintf("%d:%s", i, s)
// })
// seq := From("a", "b", "c")
// result := addIndex(seq)
// // yields: "0:a", "1:b", "2:c"
//
//go:inline
func MapWithIndex[A, B any](f func(int, A) B) Operator[A, B] {
return F.Bind2nd(MonadMapWithIndex[A, B], f)
}
// MonadMapWithKey transforms values in a key-value sequence using a function that receives both key and value.
//
// Example:
//
// seq := Of2("x", 10)
// result := MonadMapWithKey(seq, func(k string, v int) int { return v * 2 })
// // yields: ("x", 20)
func MonadMapWithKey[K, A, B any](as Seq2[K, A], f func(K, A) B) Seq2[K, B] {
return func(yield func(K, B) bool) {
for k, a := range as {
if !yield(k, f(k, a)) {
return
}
}
}
}
// MapWithKey returns a function that transforms values using their keys.
// This is the curried version of MonadMapWithKey.
//
// Example:
//
// doubleValue := MapWithKey(func(k string, v int) int { return v * 2 })
// seq := Of2("x", 10)
// result := doubleValue(seq)
// // yields: ("x", 20)
//
//go:inline
func MapWithKey[K, A, B any](f func(K, A) B) Operator2[K, A, B] {
return F.Bind2nd(MonadMapWithKey[K, A, B], f)
}
// MonadFilter returns a sequence containing only elements that satisfy the predicate.
//
// Example:
//
// seq := From(1, 2, 3, 4, 5)
// result := MonadFilter(seq, func(x int) bool { return x%2 == 0 })
// // yields: 2, 4
func MonadFilter[A any](as Seq[A], pred func(A) bool) Seq[A] {
return func(yield Predicate[A]) {
for a := range as {
if pred(a) {
if !yield(a) {
return
}
}
}
}
}
// Filter returns a function that filters elements based on a predicate.
// This is the curried version of MonadFilter.
//
// Example:
//
// evens := Filter(func(x int) bool { return x%2 == 0 })
// seq := From(1, 2, 3, 4, 5)
// result := evens(seq)
// // yields: 2, 4
//
//go:inline
func Filter[A any](pred func(A) bool) Operator[A, A] {
return F.Bind2nd(MonadFilter[A], pred)
}
// MonadFilterWithIndex filters elements using a predicate that also receives the element's index.
//
// Example:
//
// seq := From("a", "b", "c", "d")
// result := MonadFilterWithIndex(seq, func(i int, s string) bool { return i%2 == 0 })
// // yields: "a", "c" (elements at even indices)
func MonadFilterWithIndex[A any](as Seq[A], pred func(int, A) bool) Seq[A] {
return func(yield Predicate[A]) {
var i int
for a := range as {
if pred(i, a) {
if !yield(a) {
return
}
}
i++
}
}
}
// FilterWithIndex returns a function that filters elements based on their index and value.
// This is the curried version of MonadFilterWithIndex.
//
// Example:
//
// evenIndices := FilterWithIndex(func(i int, s string) bool { return i%2 == 0 })
// seq := From("a", "b", "c", "d")
// result := evenIndices(seq)
// // yields: "a", "c"
//
//go:inline
func FilterWithIndex[A any](pred func(int, A) bool) Operator[A, A] {
return F.Bind2nd(MonadFilterWithIndex[A], pred)
}
// MonadFilterWithKey filters key-value pairs using a predicate that receives both key and value.
//
// Example:
//
// seq := Of2("x", 10)
// result := MonadFilterWithKey(seq, func(k string, v int) bool { return v > 5 })
// // yields: ("x", 10)
func MonadFilterWithKey[K, A any](as Seq2[K, A], pred func(K, A) bool) Seq2[K, A] {
return func(yield func(K, A) bool) {
for k, a := range as {
if pred(k, a) {
if !yield(k, a) {
return
}
}
}
}
}
// FilterWithKey returns a function that filters key-value pairs based on a predicate.
// This is the curried version of MonadFilterWithKey.
//
// Example:
//
// largeValues := FilterWithKey(func(k string, v int) bool { return v > 5 })
// seq := Of2("x", 10)
// result := largeValues(seq)
// // yields: ("x", 10)
//
//go:inline
func FilterWithKey[K, A any](pred func(K, A) bool) Operator2[K, A, A] {
return F.Bind2nd(MonadFilterWithKey[K, A], pred)
}
// MonadFilterMap applies a function that returns an Option to each element,
// keeping only the Some values and unwrapping them.
//
// Example:
//
// seq := From(1, 2, 3, 4, 5)
// result := MonadFilterMap(seq, func(x int) Option[int] {
// if x%2 == 0 {
// return option.Some(x * 10)
// }
// return option.None[int]()
// })
// // yields: 20, 40
func MonadFilterMap[A, B any](as Seq[A], f option.Kleisli[A, B]) Seq[B] {
return func(yield Predicate[B]) {
for a := range as {
if b, ok := option.Unwrap(f(a)); ok {
if !yield(b) {
return
}
}
}
}
}
// FilterMap returns a function that filters and maps in one operation.
// This is the curried version of MonadFilterMap.
//
// Example:
//
// evenDoubled := FilterMap(func(x int) Option[int] {
// if x%2 == 0 {
// return option.Some(x * 2)
// }
// return option.None[int]()
// })
// seq := From(1, 2, 3, 4)
// result := evenDoubled(seq)
// // yields: 4, 8
//
//go:inline
func FilterMap[A, B any](f option.Kleisli[A, B]) Operator[A, B] {
return F.Bind2nd(MonadFilterMap[A, B], f)
}
// MonadFilterMapWithIndex applies a function with index that returns an Option,
// keeping only the Some values.
//
// Example:
//
// seq := From("a", "b", "c")
// result := MonadFilterMapWithIndex(seq, func(i int, s string) Option[string] {
// if i%2 == 0 {
// return option.Some(fmt.Sprintf("%d:%s", i, s))
// }
// return option.None[string]()
// })
// // yields: "0:a", "2:c"
func MonadFilterMapWithIndex[A, B any](as Seq[A], f func(int, A) Option[B]) Seq[B] {
return func(yield Predicate[B]) {
var i int
for a := range as {
if b, ok := option.Unwrap(f(i, a)); ok {
if !yield(b) {
return
}
}
i++
}
}
}
// FilterMapWithIndex returns a function that filters and maps with index.
// This is the curried version of MonadFilterMapWithIndex.
//
// Example:
//
// evenIndexed := FilterMapWithIndex(func(i int, s string) Option[string] {
// if i%2 == 0 {
// return option.Some(s)
// }
// return option.None[string]()
// })
// seq := From("a", "b", "c", "d")
// result := evenIndexed(seq)
// // yields: "a", "c"
//
//go:inline
func FilterMapWithIndex[A, B any](f func(int, A) Option[B]) Operator[A, B] {
return F.Bind2nd(MonadFilterMapWithIndex[A, B], f)
}
// MonadFilterMapWithKey applies a function with key that returns an Option to key-value pairs,
// keeping only the Some values.
//
// Example:
//
// seq := Of2("x", 10)
// result := MonadFilterMapWithKey(seq, func(k string, v int) Option[int] {
// if v > 5 {
// return option.Some(v * 2)
// }
// return option.None[int]()
// })
// // yields: ("x", 20)
func MonadFilterMapWithKey[K, A, B any](as Seq2[K, A], f func(K, A) Option[B]) Seq2[K, B] {
return func(yield func(K, B) bool) {
for k, a := range as {
if b, ok := option.Unwrap(f(k, a)); ok {
if !yield(k, b) {
return
}
}
}
}
}
// FilterMapWithKey returns a function that filters and maps key-value pairs.
// This is the curried version of MonadFilterMapWithKey.
//
// Example:
//
// largeDoubled := FilterMapWithKey(func(k string, v int) Option[int] {
// if v > 5 {
// return option.Some(v * 2)
// }
// return option.None[int]()
// })
// seq := Of2("x", 10)
// result := largeDoubled(seq)
// // yields: ("x", 20)
//
//go:inline
func FilterMapWithKey[K, A, B any](f func(K, A) Option[B]) Operator2[K, A, B] {
return F.Bind2nd(MonadFilterMapWithKey[K, A, B], f)
}
// MonadChain applies a function that returns a sequence to each element and flattens the results.
// This is the monadic bind operation (flatMap).
//
// Example:
//
// seq := From(1, 2, 3)
// result := MonadChain(seq, func(x int) Seq[int] {
// return From(x, x*10)
// })
// // yields: 1, 10, 2, 20, 3, 30
func MonadChain[A, B any](as Seq[A], f Kleisli[A, B]) Seq[B] {
return func(yield Predicate[B]) {
for a := range as {
for b := range f(a) {
if !yield(b) {
return
}
}
}
}
}
// Chain returns a function that chains (flatMaps) a sequence transformation.
// This is the curried version of MonadChain.
//
// Example:
//
// duplicate := Chain(func(x int) Seq[int] { return From(x, x) })
// seq := From(1, 2, 3)
// result := duplicate(seq)
// // yields: 1, 1, 2, 2, 3, 3
//
//go:inline
func Chain[A, B any](f func(A) Seq[B]) Operator[A, B] {
return F.Bind2nd(MonadChain[A, B], f)
}
// Flatten flattens a sequence of sequences into a single sequence.
//
// Example:
//
// nested := From(From(1, 2), From(3, 4), From(5))
// result := Flatten(nested)
// // yields: 1, 2, 3, 4, 5
//
//go:inline
func Flatten[A any](mma Seq[Seq[A]]) Seq[A] {
return MonadChain(mma, F.Identity[Seq[A]])
}
// MonadAp applies a sequence of functions to a sequence of values.
// This is the applicative apply operation.
//
// Example:
//
// fns := From(N.Mul(2), func(x int) int { return x + 10 })
// vals := From(5, 3)
// result := MonadAp(fns, vals)
// // yields: 10, 6, 15, 13 (each function applied to each value)
//
//go:inline
func MonadAp[B, A any](fab Seq[func(A) B], fa Seq[A]) Seq[B] {
return MonadChain(fab, F.Bind1st(MonadMap[A, B], fa))
}
// Ap returns a function that applies functions to values.
// This is the curried version of MonadAp.
//
// Example:
//
// applyTo5 := Ap(From(5))
// fns := From(N.Mul(2), func(x int) int { return x + 10 })
// result := applyTo5(fns)
// // yields: 10, 15
//
//go:inline
func Ap[B, A any](fa Seq[A]) Operator[func(A) B, B] {
return F.Bind2nd(MonadAp[B, A], fa)
}
// From creates a sequence from a variadic list of elements.
//
// Example:
//
// seq := From(1, 2, 3, 4, 5)
// // yields: 1, 2, 3, 4, 5
//
//go:inline
func From[A any](data ...A) Seq[A] {
return slices.Values(data)
}
// Empty returns an empty sequence that yields no elements.
//
// Example:
//
// seq := Empty[int]()
// // yields nothing
//
//go:inline
func Empty[A any]() Seq[A] {
return G.Empty[Seq[A]]()
}
// MakeBy creates a sequence of n elements by applying a function to each index.
// Returns an empty sequence if n <= 0.
//
// Example:
//
// seq := MakeBy(5, func(i int) int { return i * i })
// // yields: 0, 1, 4, 9, 16
func MakeBy[A any](n int, f func(int) A) Seq[A] {
// sanity check
if n <= 0 {
return Empty[A]()
}
// run the generator function across the input
return func(yield Predicate[A]) {
for i := range n {
if !yield(f(i)) {
return
}
}
}
}
// Replicate creates a sequence containing n copies of the same element.
//
// Example:
//
// seq := Replicate(3, "hello")
// // yields: "hello", "hello", "hello"
//
//go:inline
func Replicate[A any](n int, a A) Seq[A] {
return MakeBy(n, F.Constant1[int](a))
}
// MonadReduce reduces a sequence to a single value by applying a function to each element
// and an accumulator, starting with an initial value.
//
// Example:
//
// seq := From(1, 2, 3, 4, 5)
// sum := MonadReduce(seq, func(acc, x int) int { return acc + x }, 0)
// // returns: 15
//
//go:inline
func MonadReduce[A, B any](fa Seq[A], f func(B, A) B, initial B) B {
return G.MonadReduce(fa, f, initial)
}
// Reduce returns a function that reduces a sequence to a single value.
// This is the curried version of MonadReduce.
//
// Example:
//
// sum := Reduce(func(acc, x int) int { return acc + x }, 0)
// seq := From(1, 2, 3, 4, 5)
// result := sum(seq)
// // returns: 15
func Reduce[A, B any](f func(B, A) B, initial B) func(Seq[A]) B {
return func(fa Seq[A]) B {
return MonadReduce(fa, f, initial)
}
}
// MonadReduceWithIndex reduces a sequence using a function that also receives the element's index.
//
// Example:
//
// seq := From(10, 20, 30)
// result := MonadReduceWithIndex(seq, func(i, acc, x int) int {
// return acc + (i * x)
// }, 0)
// // returns: 0*10 + 1*20 + 2*30 = 80
//
//go:inline
func MonadReduceWithIndex[A, B any](fa Seq[A], f func(int, B, A) B, initial B) B {
return G.MonadReduceWithIndex(fa, f, initial)
}
// ReduceWithIndex returns a function that reduces with index.
// This is the curried version of MonadReduceWithIndex.
//
// Example:
//
// weightedSum := ReduceWithIndex(func(i, acc, x int) int {
// return acc + (i * x)
// }, 0)
// seq := From(10, 20, 30)
// result := weightedSum(seq)
// // returns: 80
func ReduceWithIndex[A, B any](f func(int, B, A) B, initial B) func(Seq[A]) B {
return func(fa Seq[A]) B {
return MonadReduceWithIndex(fa, f, initial)
}
}
// MonadReduceWithKey reduces a key-value sequence using a function that receives the key.
//
// Example:
//
// seq := Of2("x", 10)
// result := MonadReduceWithKey(seq, func(k string, acc int, v int) int {
// return acc + v
// }, 0)
// // returns: 10
func MonadReduceWithKey[K, A, B any](fa Seq2[K, A], f func(K, B, A) B, initial B) B {
current := initial
for k, a := range fa {
current = f(k, current, a)
}
return current
}
// ReduceWithKey returns a function that reduces key-value pairs.
// This is the curried version of MonadReduceWithKey.
//
// Example:
//
// sumValues := ReduceWithKey(func(k string, acc int, v int) int {
// return acc + v
// }, 0)
// seq := Of2("x", 10)
// result := sumValues(seq)
// // returns: 10
func ReduceWithKey[K, A, B any](f func(K, B, A) B, initial B) func(Seq2[K, A]) B {
return func(fa Seq2[K, A]) B {
return MonadReduceWithKey(fa, f, initial)
}
}
// MonadFold folds a sequence using a monoid's concat operation and empty value.
//
// Example:
//
// import "github.com/IBM/fp-go/v2/number"
// seq := From(1, 2, 3, 4, 5)
// sum := MonadFold(seq, number.MonoidSum[int]())
// // returns: 15
//
//go:inline
func MonadFold[A any](fa Seq[A], m M.Monoid[A]) A {
return MonadReduce(fa, m.Concat, m.Empty())
}
// Fold returns a function that folds a sequence using a monoid.
// This is the curried version of MonadFold.
//
// Example:
//
// import "github.com/IBM/fp-go/v2/number"
// sumAll := Fold(number.MonoidSum[int]())
// seq := From(1, 2, 3, 4, 5)
// result := sumAll(seq)
// // returns: 15
//
//go:inline
func Fold[A any](m M.Monoid[A]) func(Seq[A]) A {
return Reduce(m.Concat, m.Empty())
}
// MonadFoldMap maps each element to a monoid value and combines them using the monoid.
//
// Example:
//
// import "github.com/IBM/fp-go/v2/string"
// seq := From(1, 2, 3)
// result := MonadFoldMap(seq, func(x int) string {
// return fmt.Sprintf("%d ", x)
// }, string.Monoid)
// // returns: "1 2 3 "
//
//go:inline
func MonadFoldMap[A, B any](fa Seq[A], f func(A) B, m M.Monoid[B]) B {
return MonadReduce(fa, func(b B, a A) B {
return m.Concat(b, f(a))
}, m.Empty())
}
// FoldMap returns a function that maps and folds using a monoid.
// This is the curried version of MonadFoldMap.
//
// Example:
//
// import "github.com/IBM/fp-go/v2/string"
// stringify := FoldMap(string.Monoid)(func(x int) string {
// return fmt.Sprintf("%d ", x)
// })
// seq := From(1, 2, 3)
// result := stringify(seq)
// // returns: "1 2 3 "
//
//go:inline
func FoldMap[A, B any](m M.Monoid[B]) func(func(A) B) func(Seq[A]) B {
return func(f func(A) B) func(Seq[A]) B {
return func(as Seq[A]) B {
return MonadFoldMap(as, f, m)
}
}
}
// MonadFoldMapWithIndex maps each element with its index to a monoid value and combines them.
//
// Example:
//
// import "github.com/IBM/fp-go/v2/string"
// seq := From("a", "b", "c")
// result := MonadFoldMapWithIndex(seq, func(i int, s string) string {
// return fmt.Sprintf("%d:%s ", i, s)
// }, string.Monoid)
// // returns: "0:a 1:b 2:c "
//
//go:inline
func MonadFoldMapWithIndex[A, B any](fa Seq[A], f func(int, A) B, m M.Monoid[B]) B {
return MonadReduceWithIndex(fa, func(i int, b B, a A) B {
return m.Concat(b, f(i, a))
}, m.Empty())
}
// FoldMapWithIndex returns a function that maps with index and folds.
// This is the curried version of MonadFoldMapWithIndex.
//
// Example:
//
// import "github.com/IBM/fp-go/v2/string"
// indexedStringify := FoldMapWithIndex(string.Monoid)(func(i int, s string) string {
// return fmt.Sprintf("%d:%s ", i, s)
// })
// seq := From("a", "b", "c")
// result := indexedStringify(seq)
// // returns: "0:a 1:b 2:c "
//
//go:inline
func FoldMapWithIndex[A, B any](m M.Monoid[B]) func(func(int, A) B) func(Seq[A]) B {
return func(f func(int, A) B) func(Seq[A]) B {
return func(as Seq[A]) B {
return MonadFoldMapWithIndex(as, f, m)
}
}
}
// MonadFoldMapWithKey maps each key-value pair to a monoid value and combines them.
//
// Example:
//
// import "github.com/IBM/fp-go/v2/string"
// seq := Of2("x", 10)
// result := MonadFoldMapWithKey(seq, func(k string, v int) string {
// return fmt.Sprintf("%s:%d ", k, v)
// }, string.Monoid)
// // returns: "x:10 "
//
//go:inline
func MonadFoldMapWithKey[K, A, B any](fa Seq2[K, A], f func(K, A) B, m M.Monoid[B]) B {
return MonadReduceWithKey(fa, func(k K, b B, a A) B {
return m.Concat(b, f(k, a))
}, m.Empty())
}
// FoldMapWithKey returns a function that maps with key and folds.
// This is the curried version of MonadFoldMapWithKey.
//
//go:inline
func FoldMapWithKey[K, A, B any](m M.Monoid[B]) func(func(K, A) B) func(Seq2[K, A]) B {
return func(f func(K, A) B) func(Seq2[K, A]) B {
return func(as Seq2[K, A]) B {
return MonadFoldMapWithKey(as, f, m)
}
}
}
// MonadFlap applies a fixed value to a sequence of functions.
// This is the dual of MonadAp.
//
// Example:
//
// fns := From(N.Mul(2), func(x int) int { return x + 10 })
// result := MonadFlap(fns, 5)
// // yields: 10, 15
//
//go:inline
func MonadFlap[B, A any](fab Seq[func(A) B], a A) Seq[B] {
return functor.MonadFlap(MonadMap[func(A) B, B], fab, a)
}
// Flap returns a function that applies a fixed value to functions.
// This is the curried version of MonadFlap.
//
//go:inline
func Flap[B, A any](a A) Operator[func(A) B, B] {
return functor.Flap(Map[func(A) B, B], a)
}
// Prepend returns a function that adds an element to the beginning of a sequence.
//
// Example:
//
// seq := From(2, 3, 4)
// result := Prepend(1)(seq)
// // yields: 1, 2, 3, 4
//
//go:inline
func Prepend[A any](head A) Operator[A, A] {
return G.Prepend[Seq[A]](head)
}
// Append returns a function that adds an element to the end of a sequence.
//
// Example:
//
// seq := From(1, 2, 3)
// result := Append(4)(seq)
// // yields: 1, 2, 3, 4
//
//go:inline
func Append[A any](tail A) Operator[A, A] {
return G.Append[Seq[A]](tail)
}
// MonadZip combines two sequences into a sequence of pairs.
// The resulting sequence stops when either input sequence is exhausted.
//
// Example:
//
// seqA := From(1, 2, 3)
// seqB := From("a", "b")
// result := MonadZip(seqB, seqA)
// // yields: (1, "a"), (2, "b")
func MonadZip[A, B any](fb Seq[B], fa Seq[A]) Seq2[A, B] {
return func(yield func(A, B) bool) {
na, sa := I.Pull(fa)
defer sa()
for b := range fb {
a, ok := na()
if !ok {
return
}
if !yield(a, b) {
return
}
}
}
}
// Zip returns a function that zips a sequence with another sequence.
// This is the curried version of MonadZip.
//
// Example:
//
// seqA := From(1, 2, 3)
// zipWithA := Zip(seqA)
// seqB := From("a", "b", "c")
// result := zipWithA(seqB)
// // yields: (1, "a"), (2, "b"), (3, "c")
//
//go:inline
func Zip[A, B any](fa Seq[A]) func(Seq[B]) Seq2[A, B] {
return F.Bind2nd(MonadZip[A, B], fa)
}
//go:inline
func MonadMapToArray[A, B any](fa Seq[A], f func(A) B) []B {
return G.MonadMapToArray[Seq[A], []B](fa, f)
}
//go:inline
func MapToArray[A, B any](f func(A) B) func(Seq[A]) []B {
return G.MapToArray[Seq[A], []B](f)
}

View File

@@ -0,0 +1,589 @@
// 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 iter
import (
"fmt"
"maps"
"slices"
"strings"
"testing"
F "github.com/IBM/fp-go/v2/function"
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"
)
// Helper function to collect sequence into a slice
func toSlice[T any](seq Seq[T]) []T {
return slices.Collect(seq)
}
// Helper function to collect Seq2 into a map
func toMap[K comparable, V any](seq Seq2[K, V]) map[K]V {
return maps.Collect(seq)
}
func TestOf(t *testing.T) {
seq := Of(42)
result := toSlice(seq)
assert.Equal(t, []int{42}, result)
}
func TestOf2(t *testing.T) {
seq := Of2("key", 100)
result := toMap(seq)
assert.Equal(t, map[string]int{"key": 100}, result)
}
func TestFrom(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
result := toSlice(seq)
assert.Equal(t, []int{1, 2, 3, 4, 5}, result)
}
func TestEmpty(t *testing.T) {
seq := Empty[int]()
result := toSlice(seq)
assert.Empty(t, result)
}
func TestMonadMap(t *testing.T) {
seq := From(1, 2, 3)
doubled := MonadMap(seq, N.Mul(2))
result := toSlice(doubled)
assert.Equal(t, []int{2, 4, 6}, result)
}
func TestMap(t *testing.T) {
seq := From(1, 2, 3)
double := Map(N.Mul(2))
result := toSlice(double(seq))
assert.Equal(t, []int{2, 4, 6}, result)
}
func TestMonadMapWithIndex(t *testing.T) {
seq := From("a", "b", "c")
indexed := MonadMapWithIndex(seq, func(i int, s string) string {
return fmt.Sprintf("%d:%s", i, s)
})
result := toSlice(indexed)
assert.Equal(t, []string{"0:a", "1:b", "2:c"}, result)
}
func TestMapWithIndex(t *testing.T) {
seq := From("a", "b", "c")
indexer := MapWithIndex(func(i int, s string) string {
return fmt.Sprintf("%d:%s", i, s)
})
result := toSlice(indexer(seq))
assert.Equal(t, []string{"0:a", "1:b", "2:c"}, result)
}
func TestMonadMapWithKey(t *testing.T) {
seq := Of2("x", 10)
doubled := MonadMapWithKey(seq, func(k string, v int) int { return v * 2 })
result := toMap(doubled)
assert.Equal(t, map[string]int{"x": 20}, result)
}
func TestMapWithKey(t *testing.T) {
seq := Of2("x", 10)
doubler := MapWithKey(func(k string, v int) int { return v * 2 })
result := toMap(doubler(seq))
assert.Equal(t, map[string]int{"x": 20}, result)
}
func TestMonadFilter(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
evens := MonadFilter(seq, func(x int) bool { return x%2 == 0 })
result := toSlice(evens)
assert.Equal(t, []int{2, 4}, result)
}
func TestFilter(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
isEven := Filter(func(x int) bool { return x%2 == 0 })
result := toSlice(isEven(seq))
assert.Equal(t, []int{2, 4}, result)
}
func TestMonadFilterWithIndex(t *testing.T) {
seq := From("a", "b", "c", "d")
oddIndices := MonadFilterWithIndex(seq, func(i int, _ string) bool { return i%2 == 1 })
result := toSlice(oddIndices)
assert.Equal(t, []string{"b", "d"}, result)
}
func TestFilterWithIndex(t *testing.T) {
seq := From("a", "b", "c", "d")
oddIndexFilter := FilterWithIndex(func(i int, _ string) bool { return i%2 == 1 })
result := toSlice(oddIndexFilter(seq))
assert.Equal(t, []string{"b", "d"}, result)
}
func TestMonadFilterWithKey(t *testing.T) {
seq := Of2("x", 10)
filtered := MonadFilterWithKey(seq, func(k string, v int) bool { return v > 5 })
result := toMap(filtered)
assert.Equal(t, map[string]int{"x": 10}, result)
seq2 := Of2("y", 3)
filtered2 := MonadFilterWithKey(seq2, func(k string, v int) bool { return v > 5 })
result2 := toMap(filtered2)
assert.Equal(t, map[string]int{}, result2)
}
func TestFilterWithKey(t *testing.T) {
seq := Of2("x", 10)
filter := FilterWithKey(func(k string, v int) bool { return v > 5 })
result := toMap(filter(seq))
assert.Equal(t, map[string]int{"x": 10}, result)
}
func TestMonadFilterMap(t *testing.T) {
seq := From(1, 2, 3, 4)
result := MonadFilterMap(seq, func(x int) Option[int] {
if x%2 == 0 {
return O.Some(x * 10)
}
return O.None[int]()
})
assert.Equal(t, []int{20, 40}, toSlice(result))
}
func TestFilterMap(t *testing.T) {
seq := From(1, 2, 3, 4)
filterMapper := FilterMap(func(x int) Option[int] {
if x%2 == 0 {
return O.Some(x * 10)
}
return O.None[int]()
})
result := toSlice(filterMapper(seq))
assert.Equal(t, []int{20, 40}, result)
}
func TestMonadFilterMapWithIndex(t *testing.T) {
seq := From("a", "b", "c")
result := MonadFilterMapWithIndex(seq, func(i int, s string) Option[string] {
if i%2 == 0 {
return O.Some(strings.ToUpper(s))
}
return O.None[string]()
})
assert.Equal(t, []string{"A", "C"}, toSlice(result))
}
func TestFilterMapWithIndex(t *testing.T) {
seq := From("a", "b", "c")
filterMapper := FilterMapWithIndex(func(i int, s string) Option[string] {
if i%2 == 0 {
return O.Some(strings.ToUpper(s))
}
return O.None[string]()
})
result := toSlice(filterMapper(seq))
assert.Equal(t, []string{"A", "C"}, result)
}
func TestMonadFilterMapWithKey(t *testing.T) {
seq := Of2("x", 10)
result := MonadFilterMapWithKey(seq, func(k string, v int) Option[int] {
if v > 5 {
return O.Some(v * 2)
}
return O.None[int]()
})
assert.Equal(t, map[string]int{"x": 20}, toMap(result))
}
func TestFilterMapWithKey(t *testing.T) {
seq := Of2("x", 10)
filterMapper := FilterMapWithKey(func(k string, v int) Option[int] {
if v > 5 {
return O.Some(v * 2)
}
return O.None[int]()
})
result := toMap(filterMapper(seq))
assert.Equal(t, map[string]int{"x": 20}, result)
}
func TestMonadChain(t *testing.T) {
seq := From(1, 2)
result := MonadChain(seq, func(x int) Seq[int] {
return From(x, x*10)
})
assert.Equal(t, []int{1, 10, 2, 20}, toSlice(result))
}
func TestChain(t *testing.T) {
seq := From(1, 2)
chainer := Chain(func(x int) Seq[int] {
return From(x, x*10)
})
result := toSlice(chainer(seq))
assert.Equal(t, []int{1, 10, 2, 20}, result)
}
func TestFlatten(t *testing.T) {
seq := From(From(1, 2), From(3, 4))
result := Flatten(seq)
assert.Equal(t, []int{1, 2, 3, 4}, toSlice(result))
}
func TestMonadAp(t *testing.T) {
fns := From(
N.Mul(2),
func(x int) int { return x + 10 },
)
vals := From(1, 2)
result := MonadAp(fns, vals)
assert.Equal(t, []int{2, 4, 11, 12}, toSlice(result))
}
func TestAp(t *testing.T) {
fns := From(
N.Mul(2),
func(x int) int { return x + 10 },
)
vals := From(1, 2)
applier := Ap[int](vals)
result := toSlice(applier(fns))
assert.Equal(t, []int{2, 4, 11, 12}, result)
}
func TestApCurried(t *testing.T) {
f := F.Curry3(func(s1 string, n int, s2 string) string {
return fmt.Sprintf("%s-%d-%s", s1, n, s2)
})
result := F.Pipe4(
Of(f),
Ap[func(int) func(string) string](From("a", "b")),
Ap[func(string) string](From(1, 2)),
Ap[string](From("c", "d")),
toSlice[string],
)
expected := []string{"a-1-c", "a-1-d", "a-2-c", "a-2-d", "b-1-c", "b-1-d", "b-2-c", "b-2-d"}
assert.Equal(t, expected, result)
}
func TestMakeBy(t *testing.T) {
seq := MakeBy(5, func(i int) int { return i * i })
result := toSlice(seq)
assert.Equal(t, []int{0, 1, 4, 9, 16}, result)
}
func TestMakeByZero(t *testing.T) {
seq := MakeBy(0, func(i int) int { return i })
result := toSlice(seq)
assert.Empty(t, result)
}
func TestMakeByNegative(t *testing.T) {
seq := MakeBy(-5, func(i int) int { return i })
result := toSlice(seq)
assert.Empty(t, result)
}
func TestReplicate(t *testing.T) {
seq := Replicate(3, "hello")
result := toSlice(seq)
assert.Equal(t, []string{"hello", "hello", "hello"}, result)
}
func TestMonadReduce(t *testing.T) {
seq := From(1, 2, 3, 4)
sum := MonadReduce(seq, func(acc, x int) int { return acc + x }, 0)
assert.Equal(t, 10, sum)
}
func TestReduce(t *testing.T) {
seq := From(1, 2, 3, 4)
sum := Reduce(func(acc, x int) int { return acc + x }, 0)
result := sum(seq)
assert.Equal(t, 10, result)
}
func TestMonadReduceWithIndex(t *testing.T) {
seq := From(10, 20, 30)
result := MonadReduceWithIndex(seq, func(i, acc, x int) int {
return acc + (i * x)
}, 0)
// 0*10 + 1*20 + 2*30 = 0 + 20 + 60 = 80
assert.Equal(t, 80, result)
}
func TestReduceWithIndex(t *testing.T) {
seq := From(10, 20, 30)
reducer := ReduceWithIndex(func(i, acc, x int) int {
return acc + (i * x)
}, 0)
result := reducer(seq)
assert.Equal(t, 80, result)
}
func TestMonadReduceWithKey(t *testing.T) {
seq := Of2("x", 10)
result := MonadReduceWithKey(seq, func(k string, acc, v int) int {
return acc + v
}, 0)
assert.Equal(t, 10, result)
}
func TestReduceWithKey(t *testing.T) {
seq := Of2("x", 10)
reducer := ReduceWithKey(func(k string, acc, v int) int {
return acc + v
}, 0)
result := reducer(seq)
assert.Equal(t, 10, result)
}
func TestMonadFold(t *testing.T) {
seq := From("Hello", " ", "World")
result := MonadFold(seq, S.Monoid)
assert.Equal(t, "Hello World", result)
}
func TestFold(t *testing.T) {
seq := From("Hello", " ", "World")
folder := Fold(S.Monoid)
result := folder(seq)
assert.Equal(t, "Hello World", result)
}
func TestMonadFoldMap(t *testing.T) {
seq := From(1, 2, 3)
result := MonadFoldMap(seq, func(x int) string {
return fmt.Sprintf("%d", x)
}, S.Monoid)
assert.Equal(t, "123", result)
}
func TestFoldMap(t *testing.T) {
seq := From(1, 2, 3)
folder := FoldMap[int](S.Monoid)(func(x int) string {
return fmt.Sprintf("%d", x)
})
result := folder(seq)
assert.Equal(t, "123", result)
}
func TestMonadFoldMapWithIndex(t *testing.T) {
seq := From("a", "b", "c")
result := MonadFoldMapWithIndex(seq, func(i int, s string) string {
return fmt.Sprintf("%d:%s ", i, s)
}, S.Monoid)
assert.Equal(t, "0:a 1:b 2:c ", result)
}
func TestFoldMapWithIndex(t *testing.T) {
seq := From("a", "b", "c")
folder := FoldMapWithIndex[string](S.Monoid)(func(i int, s string) string {
return fmt.Sprintf("%d:%s ", i, s)
})
result := folder(seq)
assert.Equal(t, "0:a 1:b 2:c ", result)
}
func TestMonadFoldMapWithKey(t *testing.T) {
seq := Of2("x", 10)
result := MonadFoldMapWithKey(seq, func(k string, v int) string {
return fmt.Sprintf("%s:%d ", k, v)
}, S.Monoid)
assert.Equal(t, "x:10 ", result)
}
func TestFoldMapWithKey(t *testing.T) {
seq := Of2("x", 10)
folder := FoldMapWithKey[string, int](S.Monoid)(func(k string, v int) string {
return fmt.Sprintf("%s:%d ", k, v)
})
result := folder(seq)
assert.Equal(t, "x:10 ", result)
}
func TestMonadFlap(t *testing.T) {
fns := From(
N.Mul(2),
func(x int) int { return x + 10 },
)
result := MonadFlap(fns, 5)
assert.Equal(t, []int{10, 15}, toSlice(result))
}
func TestFlap(t *testing.T) {
fns := From(
N.Mul(2),
func(x int) int { return x + 10 },
)
flapper := Flap[int](5)
result := toSlice(flapper(fns))
assert.Equal(t, []int{10, 15}, result)
}
func TestPrepend(t *testing.T) {
seq := From(2, 3, 4)
result := Prepend(1)(seq)
assert.Equal(t, []int{1, 2, 3, 4}, toSlice(result))
}
func TestAppend(t *testing.T) {
seq := From(1, 2, 3)
result := Append(4)(seq)
assert.Equal(t, []int{1, 2, 3, 4}, toSlice(result))
}
func TestMonadZip(t *testing.T) {
seqA := From(1, 2, 3)
seqB := From("a", "b")
result := MonadZip(seqB, seqA)
var pairs []string
for a, b := range result {
pairs = append(pairs, fmt.Sprintf("%d:%s", a, b))
}
assert.Equal(t, []string{"1:a", "2:b"}, pairs)
}
func TestZip(t *testing.T) {
seqA := From(1, 2, 3)
seqB := From("a", "b", "c")
zipWithA := Zip[int, string](seqA)
result := zipWithA(seqB)
var pairs []string
for a, b := range result {
pairs = append(pairs, fmt.Sprintf("%d:%s", a, b))
}
assert.Equal(t, []string{"1:a", "2:b", "3:c"}, pairs)
}
func TestMonoid(t *testing.T) {
m := Monoid[int]()
seq1 := From(1, 2)
seq2 := From(3, 4)
result := m.Concat(seq1, seq2)
assert.Equal(t, []int{1, 2, 3, 4}, toSlice(result))
}
func TestMonoidEmpty(t *testing.T) {
m := Monoid[int]()
empty := m.Empty()
assert.Empty(t, toSlice(empty))
}
func TestMonoidAssociativity(t *testing.T) {
m := Monoid[int]()
seq1 := From(1, 2)
seq2 := From(3, 4)
seq3 := From(5, 6)
// (seq1 + seq2) + seq3
left := m.Concat(m.Concat(seq1, seq2), seq3)
// seq1 + (seq2 + seq3)
right := m.Concat(seq1, m.Concat(seq2, seq3))
assert.Equal(t, toSlice(left), toSlice(right))
}
func TestMonoidIdentity(t *testing.T) {
m := Monoid[int]()
seq := From(1, 2, 3)
empty := m.Empty()
// seq + empty = seq
leftIdentity := m.Concat(seq, empty)
assert.Equal(t, []int{1, 2, 3}, toSlice(leftIdentity))
// empty + seq = seq
rightIdentity := m.Concat(empty, seq)
assert.Equal(t, []int{1, 2, 3}, toSlice(rightIdentity))
}
func TestPipelineComposition(t *testing.T) {
// Test a complex pipeline
result := F.Pipe4(
From(1, 2, 3, 4, 5, 6),
Filter(func(x int) bool { return x%2 == 0 }),
Map(func(x int) int { return x * 10 }),
Prepend(0),
toSlice[int],
)
assert.Equal(t, []int{0, 20, 40, 60}, result)
}
func TestLazyEvaluation(t *testing.T) {
// Test that operations are lazy
callCount := 0
seq := From(1, 2, 3, 4, 5)
mapped := MonadMap(seq, func(x int) int {
callCount++
return x * 2
})
// No calls yet since we haven't iterated
assert.Equal(t, 0, callCount)
// Iterate only first 2 elements
count := 0
for range mapped {
count++
if count == 2 {
break
}
}
// Should have called the function only twice
assert.Equal(t, 2, callCount)
}
func ExampleFoldMap() {
seq := From("a", "b", "c")
fold := FoldMap[string](S.Monoid)(strings.ToUpper)
result := fold(seq)
fmt.Println(result)
// Output: ABC
}
func ExampleChain() {
seq := From(1, 2)
result := F.Pipe2(
seq,
Chain(func(x int) Seq[int] {
return From(x, x*10)
}),
toSlice[int],
)
fmt.Println(result)
// Output: [1 10 2 20]
}
func ExampleMonoid() {
m := Monoid[int]()
seq1 := From(1, 2, 3)
seq2 := From(4, 5, 6)
combined := m.Concat(seq1, seq2)
result := toSlice(combined)
fmt.Println(result)
// Output: [1 2 3 4 5 6]
}

View File

@@ -0,0 +1,37 @@
// 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 iter
import (
G "github.com/IBM/fp-go/v2/internal/iter"
M "github.com/IBM/fp-go/v2/monoid"
)
// Monoid returns a Monoid instance for Seq[T].
// The monoid's concat operation concatenates sequences, and the empty value is an empty sequence.
//
// Example:
//
// m := Monoid[int]()
// seq1 := From(1, 2)
// seq2 := From(3, 4)
// result := m.Concat(seq1, seq2)
// // yields: 1, 2, 3, 4
//
//go:inline
func Monoid[T any]() M.Monoid[Seq[T]] {
return G.Monoid[Seq[T]]()
}

57
v2/iterator/iter/types.go Normal file
View File

@@ -0,0 +1,57 @@
// 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 iter
import (
I "iter"
"github.com/IBM/fp-go/v2/iterator/stateless"
"github.com/IBM/fp-go/v2/optics/lens/option"
"github.com/IBM/fp-go/v2/predicate"
)
type (
// Option represents an optional value, either Some(value) or None.
Option[A any] = option.Option[A]
// Seq is a single-value iterator sequence from Go 1.23+.
// It represents a lazy sequence of values that can be iterated using range.
Seq[T any] = I.Seq[T]
// Seq2 is a key-value iterator sequence from Go 1.23+.
// It represents a lazy sequence of key-value pairs that can be iterated using range.
Seq2[K, V any] = I.Seq2[K, V]
// Iterator is a stateless iterator type.
Iterator[T any] = stateless.Iterator[T]
// Predicate is a function that tests a value and returns a boolean.
Predicate[T any] = predicate.Predicate[T]
// Kleisli represents a function that takes a value and returns a sequence.
// This is the monadic bind operation for sequences.
Kleisli[A, B any] = func(A) Seq[B]
// Kleisli2 represents a function that takes a value and returns a key-value sequence.
Kleisli2[K, A, B any] = func(A) Seq2[K, B]
// Operator represents a transformation from one sequence to another.
// It's a function that takes a Seq[A] and returns a Seq[B].
Operator[A, B any] = Kleisli[Seq[A], B]
// Operator2 represents a transformation from one key-value sequence to another.
Operator2[K, A, B any] = Kleisli2[K, Seq2[K, A], B]
)

View File

@@ -21,6 +21,6 @@ import (
// Any returns `true` if any element of the iterable is `true`. If the iterable is empty, return `false`
// Similar to the [https://docs.python.org/3/library/functions.html#any] function
func Any[U any](pred func(U) bool) func(ma Iterator[U]) bool {
func Any[U any](pred Predicate[U]) Predicate[Iterator[U]] {
return G.Any[Iterator[U]](pred)
}

View File

@@ -72,7 +72,7 @@ func Do[S any](
func Bind[S1, S2, T any](
setter func(T) func(S1) S2,
f Kleisli[S1, T],
) Kleisli[Iterator[S1], S2] {
) Operator[S1, S2] {
return G.Bind[Iterator[S1], Iterator[S2]](setter, f)
}
@@ -80,7 +80,7 @@ func Bind[S1, S2, T any](
func Let[S1, S2, T any](
setter func(T) func(S1) S2,
f func(S1) T,
) Kleisli[Iterator[S1], S2] {
) Operator[S1, S2] {
return G.Let[Iterator[S1], Iterator[S2]](setter, f)
}
@@ -88,14 +88,14 @@ func Let[S1, S2, T any](
func LetTo[S1, S2, T any](
setter func(T) func(S1) S2,
b T,
) Kleisli[Iterator[S1], S2] {
) Operator[S1, S2] {
return G.LetTo[Iterator[S1], Iterator[S2]](setter, b)
}
// BindTo initializes a new state [S1] from a value [T]
func BindTo[S1, T any](
setter func(T) S1,
) Kleisli[Iterator[T], S1] {
) Operator[T, S1] {
return G.BindTo[Iterator[S1], Iterator[T]](setter)
}
@@ -135,6 +135,6 @@ func BindTo[S1, T any](
func ApS[S1, S2, T any](
setter func(T) func(S1) S2,
fa Iterator[T],
) Kleisli[Iterator[S1], S2] {
) Operator[S1, S2] {
return G.ApS[Iterator[func(T) S2], Iterator[S1], Iterator[S2]](setter, fa)
}

View File

@@ -17,11 +17,10 @@ package stateless
import (
G "github.com/IBM/fp-go/v2/iterator/stateless/generic"
P "github.com/IBM/fp-go/v2/pair"
)
// Compress returns an [Iterator] that filters elements from a data [Iterator] returning only those that have a corresponding element in selector [Iterator] that evaluates to `true`.
// Stops when either the data or selectors iterator has been exhausted.
func Compress[U any](sel Iterator[bool]) Kleisli[Iterator[U], U] {
return G.Compress[Iterator[U], Iterator[bool], Iterator[P.Pair[U, bool]]](sel)
func Compress[U any](sel Iterator[bool]) Operator[U, U] {
return G.Compress[Iterator[U], Iterator[bool], Iterator[Pair[U, bool]]](sel)
}

View File

@@ -21,6 +21,6 @@ import (
// DropWhile creates an [Iterator] that drops elements from the [Iterator] as long as the predicate is true; afterwards, returns every element.
// Note, the [Iterator] does not produce any output until the predicate first becomes false
func DropWhile[U any](pred func(U) bool) Kleisli[Iterator[U], U] {
func DropWhile[U any](pred Predicate[U]) Operator[U, U] {
return G.DropWhile[Iterator[U]](pred)
}

View File

@@ -17,10 +17,9 @@ package stateless
import (
G "github.com/IBM/fp-go/v2/iterator/stateless/generic"
O "github.com/IBM/fp-go/v2/option"
)
// First returns the first item in an iterator if such an item exists
func First[U any](mu Iterator[U]) O.Option[U] {
func First[U any](mu Iterator[U]) Option[U] {
return G.First(mu)
}

View File

@@ -18,11 +18,10 @@ package generic
import (
F "github.com/IBM/fp-go/v2/function"
O "github.com/IBM/fp-go/v2/option"
P "github.com/IBM/fp-go/v2/pair"
)
// Any returns `true` if any element of the iterable is `true`. If the iterable is empty, return `false`
func Any[GU ~func() O.Option[P.Pair[GU, U]], FCT ~func(U) bool, U any](pred FCT) func(ma GU) bool {
func Any[GU ~func() Option[Pair[GU, U]], FCT ~Predicate[U], U any](pred FCT) func(ma GU) bool {
return F.Flow3(
Filter[GU](pred),
First[GU],

View File

@@ -19,8 +19,6 @@ import (
"github.com/IBM/fp-go/v2/internal/apply"
C "github.com/IBM/fp-go/v2/internal/chain"
F "github.com/IBM/fp-go/v2/internal/functor"
O "github.com/IBM/fp-go/v2/option"
P "github.com/IBM/fp-go/v2/pair"
)
// Do creates an empty context of type [S] to be used with the [Bind] operation.
@@ -33,7 +31,7 @@ import (
// Y int
// }
// result := generic.Do[Iterator[State]](State{})
func Do[GS ~func() O.Option[P.Pair[GS, S]], S any](
func Do[GS ~func() Option[Pair[GS, S]], S any](
empty S,
) GS {
return Of[GS](empty)
@@ -73,7 +71,7 @@ func Do[GS ~func() O.Option[P.Pair[GS, S]], S any](
// },
// ),
// ) // Produces: {1,10}, {1,20}, {2,20}, {2,40}, {3,30}, {3,60}
func Bind[GS1 ~func() O.Option[P.Pair[GS1, S1]], GS2 ~func() O.Option[P.Pair[GS2, S2]], GA ~func() O.Option[P.Pair[GA, A]], S1, S2, A any](
func Bind[GS1 ~func() Option[Pair[GS1, S1]], GS2 ~func() Option[Pair[GS2, S2]], GA ~func() Option[Pair[GA, A]], S1, S2, A any](
setter func(A) func(S1) S2,
f func(S1) GA,
) func(GS1) GS2 {
@@ -87,7 +85,7 @@ func Bind[GS1 ~func() O.Option[P.Pair[GS1, S1]], GS2 ~func() O.Option[P.Pair[GS2
}
// Let attaches the result of a computation to a context [S1] to produce a context [S2]
func Let[GS1 ~func() O.Option[P.Pair[GS1, S1]], GS2 ~func() O.Option[P.Pair[GS2, S2]], S1, S2, A any](
func Let[GS1 ~func() Option[Pair[GS1, S1]], GS2 ~func() Option[Pair[GS2, S2]], S1, S2, A any](
key func(A) func(S1) S2,
f func(S1) A,
) func(GS1) GS2 {
@@ -99,7 +97,7 @@ func Let[GS1 ~func() O.Option[P.Pair[GS1, S1]], GS2 ~func() O.Option[P.Pair[GS2,
}
// LetTo attaches the a value to a context [S1] to produce a context [S2]
func LetTo[GS1 ~func() O.Option[P.Pair[GS1, S1]], GS2 ~func() O.Option[P.Pair[GS2, S2]], S1, S2, B any](
func LetTo[GS1 ~func() Option[Pair[GS1, S1]], GS2 ~func() Option[Pair[GS2, S2]], S1, S2, B any](
key func(B) func(S1) S2,
b B,
) func(GS1) GS2 {
@@ -111,7 +109,7 @@ func LetTo[GS1 ~func() O.Option[P.Pair[GS1, S1]], GS2 ~func() O.Option[P.Pair[GS
}
// BindTo initializes a new state [S1] from a value [T]
func BindTo[GS1 ~func() O.Option[P.Pair[GS1, S1]], GA ~func() O.Option[P.Pair[GA, A]], S1, A any](
func BindTo[GS1 ~func() Option[Pair[GS1, S1]], GA ~func() Option[Pair[GA, A]], S1, A any](
setter func(A) S1,
) func(GA) GS1 {
return C.BindTo(
@@ -153,7 +151,7 @@ func BindTo[GS1 ~func() O.Option[P.Pair[GS1, S1]], GA ~func() O.Option[P.Pair[GA
// yIter,
// ),
// ) // Produces: {1,"a"}, {1,"b"}, {2,"a"}, {2,"b"}, {3,"a"}, {3,"b"}
func ApS[GAS2 ~func() O.Option[P.Pair[GAS2, func(A) S2]], GS1 ~func() O.Option[P.Pair[GS1, S1]], GS2 ~func() O.Option[P.Pair[GS2, S2]], GA ~func() O.Option[P.Pair[GA, A]], S1, S2, A any](
func ApS[GAS2 ~func() Option[Pair[GAS2, func(A) S2]], GS1 ~func() Option[Pair[GS1, S1]], GS2 ~func() Option[Pair[GS2, S2]], GA ~func() Option[Pair[GA, A]], S1, S2, A any](
setter func(A) func(S1) S2,
fa GA,
) func(GS1) GS2 {

View File

@@ -23,7 +23,7 @@ import (
// Compress returns an [Iterator] that filters elements from a data [Iterator] returning only those that have a corresponding element in selector [Iterator] that evaluates to `true`.
// Stops when either the data or selectors iterator has been exhausted.
func Compress[GU ~func() O.Option[P.Pair[GU, U]], GB ~func() O.Option[P.Pair[GB, bool]], CS ~func() O.Option[P.Pair[CS, P.Pair[U, bool]]], U any](sel GB) func(GU) GU {
func Compress[GU ~func() Option[Pair[GU, U]], GB ~func() Option[Pair[GB, bool]], CS ~func() Option[Pair[CS, Pair[U, bool]]], U any](sel GB) func(GU) GU {
return F.Flow2(
Zip[GU, GB, CS](sel),
FilterMap[GU, CS](F.Flow2(

View File

@@ -21,9 +21,9 @@ import (
P "github.com/IBM/fp-go/v2/pair"
)
func Cycle[GU ~func() O.Option[P.Pair[GU, U]], U any](ma GU) GU {
func Cycle[GU ~func() Option[Pair[GU, U]], U any](ma GU) GU {
// avoid cyclic references
var m func(O.Option[P.Pair[GU, U]]) O.Option[P.Pair[GU, U]]
var m func(Option[Pair[GU, U]]) Option[Pair[GU, U]]
recurse := func(mu GU) GU {
return F.Nullary2(
@@ -32,11 +32,11 @@ func Cycle[GU ~func() O.Option[P.Pair[GU, U]], U any](ma GU) GU {
)
}
m = O.Fold(func() O.Option[P.Pair[GU, U]] {
m = O.Fold(func() Option[Pair[GU, U]] {
return recurse(ma)()
}, F.Flow2(
P.BiMap(recurse, F.Identity[U]),
O.Of[P.Pair[GU, U]],
O.Of[Pair[GU, U]],
))
return recurse(ma)

View File

@@ -24,9 +24,9 @@ import (
// DropWhile creates an [Iterator] that drops elements from the [Iterator] as long as the predicate is true; afterwards, returns every element.
// Note, the [Iterator] does not produce any output until the predicate first becomes false
func DropWhile[GU ~func() O.Option[P.Pair[GU, U]], U any](pred func(U) bool) func(GU) GU {
func DropWhile[GU ~func() Option[Pair[GU, U]], U any](pred Predicate[U]) func(GU) GU {
// avoid cyclic references
var m func(O.Option[P.Pair[GU, U]]) O.Option[P.Pair[GU, U]]
var m func(Option[Pair[GU, U]]) Option[Pair[GU, U]]
fromPred := O.FromPredicate(PR.Not(PR.ContraMap(P.Tail[GU, U])(pred)))
@@ -37,11 +37,11 @@ func DropWhile[GU ~func() O.Option[P.Pair[GU, U]], U any](pred func(U) bool) fun
)
}
m = O.Chain(func(t P.Pair[GU, U]) O.Option[P.Pair[GU, U]] {
m = O.Chain(func(t Pair[GU, U]) Option[Pair[GU, U]] {
return F.Pipe2(
t,
fromPred,
O.Fold(recurse(Next(t)), O.Of[P.Pair[GU, U]]),
O.Fold(recurse(Next(t)), O.Of[Pair[GU, U]]),
)
})

View File

@@ -22,7 +22,7 @@ import (
)
// First returns the first item in an iterator if such an item exists
func First[GU ~func() O.Option[P.Pair[GU, U]], U any](mu GU) O.Option[U] {
func First[GU ~func() Option[Pair[GU, U]], U any](mu GU) Option[U] {
return F.Pipe1(
mu(),
O.Map(P.Tail[GU, U]),

View File

@@ -23,12 +23,12 @@ import (
)
// FromLazy returns an iterator on top of a lazy function
func FromLazy[GU ~func() O.Option[P.Pair[GU, U]], LZ ~func() U, U any](l LZ) GU {
func FromLazy[GU ~func() Option[Pair[GU, U]], LZ ~func() U, U any](l LZ) GU {
return F.Pipe1(
l,
L.Map[LZ, GU](F.Flow2(
F.Bind1st(P.MakePair[GU, U], Empty[GU]()),
O.Of[P.Pair[GU, U]],
O.Of[Pair[GU, U]],
)),
)
}

View File

@@ -27,42 +27,42 @@ import (
P "github.com/IBM/fp-go/v2/pair"
)
// Next returns the iterator for the next element in an iterator `P.Pair`
func Next[GU ~func() O.Option[P.Pair[GU, U]], U any](m P.Pair[GU, U]) GU {
// Next returns the iterator for the next element in an iterator `Pair`
func Next[GU ~func() Option[Pair[GU, U]], U any](m Pair[GU, U]) GU {
return P.Head(m)
}
// Current returns the current element in an iterator `P.Pair`
func Current[GU ~func() O.Option[P.Pair[GU, U]], U any](m P.Pair[GU, U]) U {
// Current returns the current element in an iterator `Pair`
func Current[GU ~func() Option[Pair[GU, U]], U any](m Pair[GU, U]) U {
return P.Tail(m)
}
// From constructs an array from a set of variadic arguments
func From[GU ~func() O.Option[P.Pair[GU, U]], U any](data ...U) GU {
func From[GU ~func() Option[Pair[GU, U]], U any](data ...U) GU {
return FromArray[GU](data)
}
// Empty returns the empty iterator
func Empty[GU ~func() O.Option[P.Pair[GU, U]], U any]() GU {
func Empty[GU ~func() Option[Pair[GU, U]], U any]() GU {
return IO.None[GU]()
}
// Of returns an iterator with one single element
func Of[GU ~func() O.Option[P.Pair[GU, U]], U any](a U) GU {
func Of[GU ~func() Option[Pair[GU, U]], U any](a U) GU {
return IO.Of[GU](P.MakePair(Empty[GU](), a))
}
// FromArray returns an iterator from multiple elements
func FromArray[GU ~func() O.Option[P.Pair[GU, U]], US ~[]U, U any](as US) GU {
func FromArray[GU ~func() Option[Pair[GU, U]], US ~[]U, U any](as US) GU {
return A.MatchLeft(Empty[GU], func(head U, tail US) GU {
return func() O.Option[P.Pair[GU, U]] {
return func() Option[Pair[GU, U]] {
return O.Of(P.MakePair(FromArray[GU](tail), head))
}
})(as)
}
// reduce applies a function for each value of the iterator with a floating result
func reduce[GU ~func() O.Option[P.Pair[GU, U]], U, V any](as GU, f func(V, U) V, initial V) V {
func reduce[GU ~func() Option[Pair[GU, U]], U, V any](as GU, f func(V, U) V, initial V) V {
next, ok := O.Unwrap(as())
current := initial
for ok {
@@ -74,18 +74,18 @@ func reduce[GU ~func() O.Option[P.Pair[GU, U]], U, V any](as GU, f func(V, U) V,
}
// Reduce applies a function for each value of the iterator with a floating result
func Reduce[GU ~func() O.Option[P.Pair[GU, U]], U, V any](f func(V, U) V, initial V) func(GU) V {
func Reduce[GU ~func() Option[Pair[GU, U]], U, V any](f func(V, U) V, initial V) func(GU) V {
return F.Bind23of3(reduce[GU, U, V])(f, initial)
}
// ToArray converts the iterator to an array
func ToArray[GU ~func() O.Option[P.Pair[GU, U]], US ~[]U, U any](u GU) US {
func ToArray[GU ~func() Option[Pair[GU, U]], US ~[]U, U any](u GU) US {
return Reduce[GU](A.Append[US], A.Empty[US]())(u)
}
func Map[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], FCT ~func(U) V, U, V any](f FCT) func(ma GU) GV {
func Map[GV ~func() Option[Pair[GV, V]], GU ~func() Option[Pair[GU, U]], FCT ~func(U) V, U, V any](f FCT) func(ma GU) GV {
// pre-declare to avoid cyclic reference
var m func(O.Option[P.Pair[GU, U]]) O.Option[P.Pair[GV, V]]
var m func(Option[Pair[GU, U]]) Option[Pair[GV, V]]
recurse := func(ma GU) GV {
return F.Nullary2(
@@ -99,12 +99,12 @@ func Map[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]],
return recurse
}
func MonadMap[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], U, V any](ma GU, f func(U) V) GV {
func MonadMap[GV ~func() Option[Pair[GV, V]], GU ~func() Option[Pair[GU, U]], U, V any](ma GU, f func(U) V) GV {
return Map[GV, GU](f)(ma)
}
func concat[GU ~func() O.Option[P.Pair[GU, U]], U any](right, left GU) GU {
var m func(ma O.Option[P.Pair[GU, U]]) O.Option[P.Pair[GU, U]]
func concat[GU ~func() Option[Pair[GU, U]], U any](right, left GU) GU {
var m func(ma Option[Pair[GU, U]]) Option[Pair[GU, U]]
recurse := func(left GU) GU {
return F.Nullary2(left, m)
@@ -114,15 +114,15 @@ func concat[GU ~func() O.Option[P.Pair[GU, U]], U any](right, left GU) GU {
right,
F.Flow2(
P.BiMap(recurse, F.Identity[U]),
O.Some[P.Pair[GU, U]],
O.Some[Pair[GU, U]],
))
return recurse(left)
}
func Chain[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], U, V any](f func(U) GV) func(GU) GV {
func Chain[GV ~func() Option[Pair[GV, V]], GU ~func() Option[Pair[GU, U]], U, V any](f func(U) GV) func(GU) GV {
// pre-declare to avoid cyclic reference
var m func(O.Option[P.Pair[GU, U]]) O.Option[P.Pair[GV, V]]
var m func(Option[Pair[GU, U]]) Option[Pair[GV, V]]
recurse := func(ma GU) GV {
return F.Nullary2(
@@ -134,7 +134,7 @@ func Chain[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]
F.Flow3(
P.BiMap(recurse, f),
P.Paired(concat[GV]),
func(v GV) O.Option[P.Pair[GV, V]] {
func(v GV) Option[Pair[GV, V]] {
return v()
},
),
@@ -143,11 +143,11 @@ func Chain[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]
return recurse
}
func MonadChain[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], U, V any](ma GU, f func(U) GV) GV {
func MonadChain[GV ~func() Option[Pair[GV, V]], GU ~func() Option[Pair[GU, U]], U, V any](ma GU, f func(U) GV) GV {
return Chain[GV, GU](f)(ma)
}
func MonadChainFirst[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], U, V any](ma GU, f func(U) GV) GU {
func MonadChainFirst[GV ~func() Option[Pair[GV, V]], GU ~func() Option[Pair[GU, U]], U, V any](ma GU, f func(U) GV) GU {
return C.MonadChainFirst(
MonadChain[GU, GU, U, U],
MonadMap[GU, GV, V, U],
@@ -156,7 +156,7 @@ func MonadChainFirst[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.P
)
}
func ChainFirst[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], U, V any](f func(U) GV) func(GU) GU {
func ChainFirst[GV ~func() Option[Pair[GV, V]], GU ~func() Option[Pair[GU, U]], U, V any](f func(U) GV) func(GU) GU {
return C.ChainFirst(
Chain[GU, GU, U, U],
Map[GU, GV, func(V) U, V, U],
@@ -164,14 +164,14 @@ func ChainFirst[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[G
)
}
func Flatten[GV ~func() O.Option[P.Pair[GV, GU]], GU ~func() O.Option[P.Pair[GU, U]], U any](ma GV) GU {
func Flatten[GV ~func() Option[Pair[GV, GU]], GU ~func() Option[Pair[GU, U]], U any](ma GV) GU {
return MonadChain(ma, F.Identity[GU])
}
// MakeBy returns an [Iterator] with an infinite number of elements initialized with `f(i)`
func MakeBy[GU ~func() O.Option[P.Pair[GU, U]], FCT ~func(int) U, U any](f FCT) GU {
func MakeBy[GU ~func() Option[Pair[GU, U]], FCT ~func(int) U, U any](f FCT) GU {
var m func(int) O.Option[P.Pair[GU, U]]
var m func(int) Option[Pair[GU, U]]
recurse := func(i int) GU {
return F.Nullary2(
@@ -186,7 +186,7 @@ func MakeBy[GU ~func() O.Option[P.Pair[GU, U]], FCT ~func(int) U, U any](f FCT)
utils.Inc,
recurse),
f),
O.Of[P.Pair[GU, U]],
O.Of[Pair[GU, U]],
)
// bootstrap
@@ -194,13 +194,13 @@ func MakeBy[GU ~func() O.Option[P.Pair[GU, U]], FCT ~func(int) U, U any](f FCT)
}
// Replicate creates an infinite [Iterator] containing a value.
func Replicate[GU ~func() O.Option[P.Pair[GU, U]], U any](a U) GU {
func Replicate[GU ~func() Option[Pair[GU, U]], U any](a U) GU {
return MakeBy[GU](F.Constant1[int](a))
}
// Repeat creates an [Iterator] containing a value repeated the specified number of times.
// Alias of [Replicate] combined with [Take]
func Repeat[GU ~func() O.Option[P.Pair[GU, U]], U any](n int, a U) GU {
func Repeat[GU ~func() Option[Pair[GU, U]], U any](n int, a U) GU {
return F.Pipe2(
a,
Replicate[GU],
@@ -209,13 +209,13 @@ func Repeat[GU ~func() O.Option[P.Pair[GU, U]], U any](n int, a U) GU {
}
// Count creates an [Iterator] containing a consecutive sequence of integers starting with the provided start value
func Count[GU ~func() O.Option[P.Pair[GU, int]]](start int) GU {
func Count[GU ~func() Option[Pair[GU, int]]](start int) GU {
return MakeBy[GU](N.Add(start))
}
func FilterMap[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], FCT ~func(U) O.Option[V], U, V any](f FCT) func(ma GU) GV {
func FilterMap[GV ~func() Option[Pair[GV, V]], GU ~func() Option[Pair[GU, U]], FCT ~func(U) Option[V], U, V any](f FCT) func(ma GU) GV {
// pre-declare to avoid cyclic reference
var m func(O.Option[P.Pair[GU, U]]) O.Option[P.Pair[GV, V]]
var m func(Option[Pair[GU, U]]) Option[Pair[GV, V]]
recurse := func(ma GU) GV {
return F.Nullary2(
@@ -226,11 +226,11 @@ func FilterMap[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU
m = O.Fold(
Empty[GV](),
func(t P.Pair[GU, U]) O.Option[P.Pair[GV, V]] {
func(t Pair[GU, U]) Option[Pair[GV, V]] {
r := recurse(Next(t))
return O.MonadFold(f(Current(t)), r, F.Flow2(
F.Bind1st(P.MakePair[GV, V], r),
O.Some[P.Pair[GV, V]],
O.Some[Pair[GV, V]],
))
},
)
@@ -238,26 +238,26 @@ func FilterMap[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU
return recurse
}
func Filter[GU ~func() O.Option[P.Pair[GU, U]], FCT ~func(U) bool, U any](f FCT) func(ma GU) GU {
func Filter[GU ~func() Option[Pair[GU, U]], FCT ~Predicate[U], U any](f FCT) func(ma GU) GU {
return FilterMap[GU, GU](O.FromPredicate(f))
}
func Ap[GUV ~func() O.Option[P.Pair[GUV, func(U) V]], GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], U, V any](ma GU) func(fab GUV) GV {
func Ap[GUV ~func() Option[Pair[GUV, func(U) V]], GV ~func() Option[Pair[GV, V]], GU ~func() Option[Pair[GU, U]], U, V any](ma GU) func(fab GUV) GV {
return Chain[GV, GUV](F.Bind1st(MonadMap[GV, GU], ma))
}
func MonadAp[GUV ~func() O.Option[P.Pair[GUV, func(U) V]], GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], U, V any](fab GUV, ma GU) GV {
func MonadAp[GUV ~func() Option[Pair[GUV, func(U) V]], GV ~func() Option[Pair[GV, V]], GU ~func() Option[Pair[GU, U]], U, V any](fab GUV, ma GU) GV {
return Ap[GUV, GV](ma)(fab)
}
func FilterChain[GVV ~func() O.Option[P.Pair[GVV, GV]], GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], FCT ~func(U) O.Option[GV], U, V any](f FCT) func(ma GU) GV {
func FilterChain[GVV ~func() Option[Pair[GVV, GV]], GV ~func() Option[Pair[GV, V]], GU ~func() Option[Pair[GU, U]], FCT ~func(U) Option[GV], U, V any](f FCT) func(ma GU) GV {
return F.Flow2(
FilterMap[GVV, GU](f),
Flatten[GVV],
)
}
func FoldMap[GU ~func() O.Option[P.Pair[GU, U]], FCT ~func(U) V, U, V any](m M.Monoid[V]) func(FCT) func(ma GU) V {
func FoldMap[GU ~func() Option[Pair[GU, U]], FCT ~func(U) V, U, V any](m M.Monoid[V]) func(FCT) func(ma GU) V {
return func(f FCT) func(ma GU) V {
return Reduce[GU](func(cur V, a U) V {
return m.Concat(cur, f(a))
@@ -265,6 +265,6 @@ func FoldMap[GU ~func() O.Option[P.Pair[GU, U]], FCT ~func(U) V, U, V any](m M.M
}
}
func Fold[GU ~func() O.Option[P.Pair[GU, U]], U any](m M.Monoid[U]) func(ma GU) U {
func Fold[GU ~func() Option[Pair[GU, U]], U any](m M.Monoid[U]) func(ma GU) U {
return Reduce[GU](m.Concat, m.Empty())
}

View File

@@ -18,10 +18,9 @@ package generic
import (
F "github.com/IBM/fp-go/v2/function"
O "github.com/IBM/fp-go/v2/option"
P "github.com/IBM/fp-go/v2/pair"
)
// Last returns the last item in an iterator if such an item exists
func Last[GU ~func() O.Option[P.Pair[GU, U]], U any](mu GU) O.Option[U] {
return reduce(mu, F.Ignore1of2[O.Option[U]](O.Of[U]), O.None[U]())
func Last[GU ~func() Option[Pair[GU, U]], U any](mu GU) Option[U] {
return reduce(mu, F.Ignore1of2[Option[U]](O.Of[U]), O.None[U]())
}

View File

@@ -17,11 +17,9 @@ package generic
import (
"github.com/IBM/fp-go/v2/internal/monad"
O "github.com/IBM/fp-go/v2/option"
P "github.com/IBM/fp-go/v2/pair"
)
type iteratorMonad[A, B any, GA ~func() O.Option[P.Pair[GA, A]], GB ~func() O.Option[P.Pair[GB, B]], GAB ~func() O.Option[P.Pair[GAB, func(A) B]]] struct{}
type iteratorMonad[A, B any, GA ~func() Option[Pair[GA, A]], GB ~func() Option[Pair[GB, B]], GAB ~func() Option[Pair[GAB, func(A) B]]] struct{}
func (o *iteratorMonad[A, B, GA, GB, GAB]) Of(a A) GA {
return Of[GA](a)
@@ -40,6 +38,6 @@ func (o *iteratorMonad[A, B, GA, GB, GAB]) Ap(fa GA) func(GAB) GB {
}
// Monad implements the monadic operations for iterators
func Monad[A, B any, GA ~func() O.Option[P.Pair[GA, A]], GB ~func() O.Option[P.Pair[GB, B]], GAB ~func() O.Option[P.Pair[GAB, func(A) B]]]() monad.Monad[A, B, GA, GB, GAB] {
func Monad[A, B any, GA ~func() Option[Pair[GA, A]], GB ~func() Option[Pair[GB, B]], GAB ~func() Option[Pair[GAB, func(A) B]]]() monad.Monad[A, B, GA, GB, GAB] {
return &iteratorMonad[A, B, GA, GB, GAB]{}
}

View File

@@ -18,11 +18,9 @@ package generic
import (
F "github.com/IBM/fp-go/v2/function"
M "github.com/IBM/fp-go/v2/monoid"
O "github.com/IBM/fp-go/v2/option"
P "github.com/IBM/fp-go/v2/pair"
)
func Monoid[GU ~func() O.Option[P.Pair[GU, U]], U any]() M.Monoid[GU] {
func Monoid[GU ~func() Option[Pair[GU, U]], U any]() M.Monoid[GU] {
return M.MakeMonoid(
F.Swap(concat[GU]),
Empty[GU](),

Some files were not shown because too many files have changed in this diff Show More