mirror of
https://github.com/IBM/fp-go.git
synced 2026-04-09 15:26:02 +02:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21b517d388 | ||
|
|
0df62c0031 | ||
|
|
57318e2d1d | ||
|
|
2b937d3e93 | ||
|
|
747a1794e5 | ||
|
|
c754cacf1f | ||
|
|
d357b32847 | ||
|
|
a3af003e74 |
17
v2/AGENTS.md
17
v2/AGENTS.md
@@ -22,8 +22,10 @@ This document provides guidelines for AI agents working on the fp-go/v2 project.
|
||||
|
||||
1. **Use Standard Go Doc Format**
|
||||
- Do NOT use markdown-style links like `[text](url)`
|
||||
- Do NOT use markdown-style headers like `# Section` or `## Subsection`
|
||||
- Use simple type references: `ReaderResult`, `Validate[I, A]`, `validation.Success`
|
||||
- Go's documentation system will automatically create links
|
||||
- Use plain text with blank lines to separate sections
|
||||
|
||||
2. **Structure**
|
||||
```go
|
||||
@@ -31,24 +33,20 @@ This document provides guidelines for AI agents working on the fp-go/v2 project.
|
||||
//
|
||||
// Longer description explaining the purpose and behavior.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// Type Parameters:
|
||||
// - T: Description of type parameter
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// Parameters:
|
||||
// - param: Description of parameter
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// Returns:
|
||||
// - ReturnType: Description of return value
|
||||
//
|
||||
// # Example Usage
|
||||
// Example:
|
||||
//
|
||||
// code example here
|
||||
//
|
||||
// # See Also
|
||||
//
|
||||
// See Also:
|
||||
// - RelatedFunction: Brief description
|
||||
func FunctionName[T any](param T) ReturnType {
|
||||
```
|
||||
@@ -57,6 +55,7 @@ This document provides guidelines for AI agents working on the fp-go/v2 project.
|
||||
- Use idiomatic Go patterns
|
||||
- Prefer `result.Eitherize1(strconv.Atoi)` over manual error handling
|
||||
- Show realistic, runnable examples
|
||||
- Indent code examples with spaces (not tabs) for proper godoc rendering
|
||||
|
||||
### File Headers
|
||||
|
||||
|
||||
@@ -24,21 +24,75 @@ import (
|
||||
"github.com/IBM/fp-go/v2/pair"
|
||||
)
|
||||
|
||||
// From constructs an array from a set of variadic arguments
|
||||
// From constructs an array from a set of variadic arguments.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - data: Variadic arguments to include in the array
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A new array containing all provided arguments
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// arr := array.From(1, 2, 3, 4, 5)
|
||||
// // arr: []int{1, 2, 3, 4, 5}
|
||||
//
|
||||
//go:inline
|
||||
func From[A any](data ...A) []A {
|
||||
return G.From[[]A](data...)
|
||||
}
|
||||
|
||||
// MakeBy returns a `Array` of length `n` with element `i` initialized with `f(i)`.
|
||||
// MakeBy returns an array of length n with element i initialized with f(i).
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - F: Function type that takes an int and returns A
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - n: The length of the array to create
|
||||
// - f: Function to generate each element based on its index
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A new array where element at index i equals f(i)
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// squares := array.MakeBy(5, func(i int) int { return i * i })
|
||||
// // squares: []int{0, 1, 4, 9, 16}
|
||||
//
|
||||
//go:inline
|
||||
func MakeBy[F ~func(int) A, A any](n int, f F) []A {
|
||||
return G.MakeBy[[]A](n, f)
|
||||
}
|
||||
|
||||
// Replicate creates a `Array` containing a value repeated the specified number of times.
|
||||
// Replicate creates an array containing a value repeated the specified number of times.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - n: The number of times to repeat the value
|
||||
// - a: The value to repeat
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A new array containing n copies of a
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// zeros := array.Replicate(5, 0)
|
||||
// // zeros: []int{0, 0, 0, 0, 0}
|
||||
//
|
||||
//go:inline
|
||||
func Replicate[A any](n int, a A) []A {
|
||||
@@ -55,6 +109,27 @@ func MonadMap[A, B any](as []A, f func(A) B) []B {
|
||||
|
||||
// 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.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the input array
|
||||
// - B: The type of elements in the output array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - as: The input array
|
||||
// - f: Function that takes a pointer to an element and returns a transformed value
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A new array with transformed elements
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// type Point struct { X, Y int }
|
||||
// points := []Point{{1, 2}, {3, 4}}
|
||||
// xs := array.MonadMapRef(points, func(p *Point) int { return p.X })
|
||||
// // xs: []int{1, 3}
|
||||
func MonadMapRef[A, B any](as []A, f func(*A) B) []B {
|
||||
count := len(as)
|
||||
bs := make([]B, count)
|
||||
@@ -86,6 +161,27 @@ func Map[A, B any](f func(A) B) Operator[A, B] {
|
||||
|
||||
// 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.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the input array
|
||||
// - B: The type of elements in the output array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: Function that takes a pointer to an element and returns a transformed value
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that transforms an array of A into an array of B
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// type Point struct { X, Y int }
|
||||
// extractX := array.MapRef(func(p *Point) int { return p.X })
|
||||
// points := []Point{{1, 2}, {3, 4}}
|
||||
// xs := extractX(points)
|
||||
// // xs: []int{1, 3}
|
||||
func MapRef[A, B any](f func(*A) B) Operator[A, B] {
|
||||
return F.Bind2nd(MonadMapRef[A, B], f)
|
||||
}
|
||||
@@ -114,14 +210,51 @@ func filterMapRef[A, B any](fa []A, pred func(*A) bool, f func(*A) B) []B {
|
||||
return result
|
||||
}
|
||||
|
||||
// Filter returns a new array with all elements from the original array that match a predicate
|
||||
// Filter returns a new array with all elements from the original array that match a predicate.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - pred: Predicate function to test each element
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that filters an array based on the predicate
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// isEven := array.Filter(func(x int) bool { return x%2 == 0 })
|
||||
// result := isEven([]int{1, 2, 3, 4, 5, 6})
|
||||
// // result: []int{2, 4, 6}
|
||||
//
|
||||
//go:inline
|
||||
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
|
||||
// FilterWithIndex returns a new array with all elements from the original array that match a predicate.
|
||||
// The predicate receives both the index and the element.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - pred: Predicate function that takes an index and element
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that filters an array based on the predicate
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// filterOddIndices := array.FilterWithIndex(func(i int, _ int) bool { return i%2 == 1 })
|
||||
// result := filterOddIndices([]int{10, 20, 30, 40, 50})
|
||||
// // result: []int{20, 40}
|
||||
//
|
||||
//go:inline
|
||||
func FilterWithIndex[A any](pred func(int, A) bool) Operator[A, A] {
|
||||
@@ -129,6 +262,26 @@ func FilterWithIndex[A any](pred func(int, A) bool) Operator[A, A] {
|
||||
}
|
||||
|
||||
// FilterRef returns a new array with all elements from the original array that match a predicate operating on pointers.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - pred: Predicate function that takes a pointer to an element
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that filters an array based on the predicate
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// type Point struct { X, Y int }
|
||||
// filterPositiveX := array.FilterRef(func(p *Point) bool { return p.X > 0 })
|
||||
// points := []Point{{-1, 2}, {3, 4}, {-5, 6}}
|
||||
// result := filterPositiveX(points)
|
||||
// // result: []Point{{3, 4}}
|
||||
func FilterRef[A any](pred func(*A) bool) Operator[A, A] {
|
||||
return F.Bind2nd(filterRef[A], pred)
|
||||
}
|
||||
@@ -149,21 +302,73 @@ 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 [Option] and it keeps only the Some values discarding the Nones.
|
||||
// FilterMap maps an array with an iterating function that returns an Option and keeps only the Some values discarding the Nones.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the input array
|
||||
// - B: The type of elements in the output array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: Function that maps elements to Option values
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that transforms and filters an array
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// parseInt := array.FilterMap(func(s string) option.Option[int] {
|
||||
// if n, err := strconv.Atoi(s); err == nil {
|
||||
// return option.Some(n)
|
||||
// }
|
||||
// return option.None[int]()
|
||||
// })
|
||||
// result := parseInt([]string{"1", "bad", "3", "4"})
|
||||
// // result: []int{1, 3, 4}
|
||||
//
|
||||
//go:inline
|
||||
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 [Option] and it keeps only the Some values discarding the Nones.
|
||||
// FilterMapWithIndex maps an array with an iterating function that returns an Option and keeps only the Some values discarding the Nones.
|
||||
// The function receives both the index and the element.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the input array
|
||||
// - B: The type of elements in the output array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: Function that takes an index and element and returns an Option
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that transforms and filters an array
|
||||
//
|
||||
//go:inline
|
||||
func FilterMapWithIndex[A, B any](f func(int, A) Option[B]) Operator[A, B] {
|
||||
return G.FilterMapWithIndex[[]A, []B](f)
|
||||
}
|
||||
|
||||
// ChainOptionK 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.
|
||||
// ChainOptionK 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.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the input array
|
||||
// - B: The type of elements in the output array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: Function that maps elements to Option of arrays
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that transforms, filters, and flattens an array
|
||||
//
|
||||
//go:inline
|
||||
func ChainOptionK[A, B any](f option.Kleisli[A, []B]) Operator[A, B] {
|
||||
@@ -171,6 +376,20 @@ func ChainOptionK[A, B any](f option.Kleisli[A, []B]) Operator[A, B] {
|
||||
}
|
||||
|
||||
// FilterMapRef filters an array using a predicate on pointers and maps the matching elements using a function on pointers.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the input array
|
||||
// - B: The type of elements in the output array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - pred: Predicate function that takes a pointer to an element
|
||||
// - f: Function that transforms a pointer to an element
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that filters and transforms an array
|
||||
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)
|
||||
@@ -185,11 +404,48 @@ func reduceRef[A, B any](fa []A, f func(B, *A) B, initial B) B {
|
||||
return current
|
||||
}
|
||||
|
||||
// MonadReduce folds an array from left to right, applying a function to accumulate a result.
|
||||
// This is the monadic version that takes the array as the first parameter.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
// - B: The type of the accumulated result
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - fa: The input array
|
||||
// - f: Function that combines the accumulator with each element
|
||||
// - initial: The initial accumulator value
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - The final accumulated result
|
||||
//
|
||||
//go:inline
|
||||
func MonadReduce[A, B any](fa []A, f func(B, A) B, initial B) B {
|
||||
return G.MonadReduce(fa, f, initial)
|
||||
}
|
||||
|
||||
// MonadReduceWithIndex folds an array from left to right with access to the index,
|
||||
// applying a function to accumulate a result.
|
||||
// This is the monadic version that takes the array as the first parameter.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
// - B: The type of the accumulated result
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - fa: The input array
|
||||
// - f: Function that combines the index, accumulator, and element
|
||||
// - initial: The initial accumulator value
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - The final accumulated result
|
||||
//
|
||||
//go:inline
|
||||
func MonadReduceWithIndex[A, B any](fa []A, f func(int, B, A) B, initial B) B {
|
||||
return G.MonadReduceWithIndex(fa, f, initial)
|
||||
@@ -232,6 +488,20 @@ func ReduceRightWithIndex[A, B any](f func(int, A, B) B, initial B) func([]A) B
|
||||
|
||||
// ReduceRef folds an array from left to right using pointers to elements,
|
||||
// applying a function to accumulate a result.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
// - B: The type of the accumulated result
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: Function that combines the accumulator with a pointer to each element
|
||||
// - initial: The initial accumulator value
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that reduces an array to a single value
|
||||
func ReduceRef[A, B any](f func(B, *A) B, initial B) func([]A) B {
|
||||
return func(as []A) B {
|
||||
return reduceRef(as, f, initial)
|
||||
@@ -257,12 +527,36 @@ func Append[A any](as []A, a A) []A {
|
||||
|
||||
// IsEmpty checks if an array has no elements.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - as: The array to check
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - true if the array is empty, false otherwise
|
||||
//
|
||||
//go:inline
|
||||
func IsEmpty[A any](as []A) bool {
|
||||
return G.IsEmpty(as)
|
||||
}
|
||||
|
||||
// IsNonEmpty checks if an array has at least one element.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - as: The array to check
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - true if the array has at least one element, false otherwise
|
||||
func IsNonEmpty[A any](as []A) bool {
|
||||
return len(as) > 0
|
||||
}
|
||||
@@ -372,6 +666,23 @@ func Last[A any](as []A) Option[A] {
|
||||
}
|
||||
|
||||
// PrependAll inserts a separator before each element of an array.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - middle: The separator to insert before each element
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that transforms an array by prepending the separator to each element
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// result := array.PrependAll(0)([]int{1, 2, 3})
|
||||
// // result: []int{0, 1, 0, 2, 0, 3}
|
||||
func PrependAll[A any](middle A) Operator[A, A] {
|
||||
return func(as []A) []A {
|
||||
count := len(as)
|
||||
@@ -389,9 +700,22 @@ func PrependAll[A any](middle A) Operator[A, A] {
|
||||
|
||||
// Intersperse inserts a separator between each element of an array.
|
||||
//
|
||||
// Example:
|
||||
// # Type Parameters
|
||||
//
|
||||
// result := array.Intersperse(0)([]int{1, 2, 3}) // [1, 0, 2, 0, 3]
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - middle: The separator to insert between elements
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that transforms an array by inserting the separator between elements
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// result := array.Intersperse(0)([]int{1, 2, 3})
|
||||
// // result: []int{1, 0, 2, 0, 3}
|
||||
func Intersperse[A any](middle A) Operator[A, A] {
|
||||
prepend := PrependAll(middle)
|
||||
return func(as []A) []A {
|
||||
@@ -403,6 +727,18 @@ func Intersperse[A any](middle A) Operator[A, A] {
|
||||
}
|
||||
|
||||
// Intercalate inserts a separator between elements and concatenates them using a Monoid.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - m: The Monoid to use for concatenation
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A curried function that takes a separator and returns a function that reduces an array
|
||||
func Intercalate[A any](m M.Monoid[A]) func(A) func([]A) A {
|
||||
return func(middle A) func([]A) A {
|
||||
return Match(m.Empty, F.Flow2(Intersperse(middle), ConcatAll(m)))
|
||||
@@ -411,9 +747,22 @@ func Intercalate[A any](m M.Monoid[A]) func(A) func([]A) A {
|
||||
|
||||
// Flatten converts a nested array into a flat array by concatenating all inner arrays.
|
||||
//
|
||||
// Example:
|
||||
// # Type Parameters
|
||||
//
|
||||
// result := array.Flatten([][]int{{1, 2}, {3, 4}, {5}}) // [1, 2, 3, 4, 5]
|
||||
// - A: The type of elements in the inner arrays
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - mma: A nested array (array of arrays)
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A flat array containing all elements from all inner arrays
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// result := array.Flatten([][]int{{1, 2}, {3, 4}, {5}})
|
||||
// // result: []int{1, 2, 3, 4, 5}
|
||||
//
|
||||
//go:inline
|
||||
func Flatten[A any](mma [][]A) []A {
|
||||
@@ -421,6 +770,25 @@ func Flatten[A any](mma [][]A) []A {
|
||||
}
|
||||
|
||||
// Slice extracts a subarray from index low (inclusive) to high (exclusive).
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - low: The starting index (inclusive)
|
||||
// - high: The ending index (exclusive)
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that extracts a subarray
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// middle := array.Slice[int](2, 5)
|
||||
// result := middle([]int{0, 1, 2, 3, 4, 5, 6})
|
||||
// // result: []int{2, 3, 4}
|
||||
func Slice[A any](low, high int) Operator[A, A] {
|
||||
return array.Slice[[]A](low, high)
|
||||
}
|
||||
@@ -428,6 +796,24 @@ func Slice[A any](low, high int) Operator[A, A] {
|
||||
// Lookup returns the element at the specified index, wrapped in an Option.
|
||||
// Returns None if the index is out of bounds.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - idx: The index to look up
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that retrieves an element at the given index, wrapped in an Option
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// getSecond := array.Lookup[int](1)
|
||||
// result := getSecond([]int{10, 20, 30})
|
||||
// // result: option.Some(20)
|
||||
//
|
||||
//go:inline
|
||||
func Lookup[A any](idx int) func([]A) Option[A] {
|
||||
return G.Lookup[[]A](idx)
|
||||
@@ -436,6 +822,18 @@ func Lookup[A any](idx int) func([]A) Option[A] {
|
||||
// UpsertAt returns a function that inserts or updates an element at a specific index.
|
||||
// If the index is out of bounds, the element is appended.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - a: The element to insert or update
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that takes an index and returns a function that upserts at that index
|
||||
//
|
||||
//go:inline
|
||||
func UpsertAt[A any](a A) Operator[A, A] {
|
||||
return G.UpsertAt[[]A](a)
|
||||
@@ -443,6 +841,18 @@ func UpsertAt[A any](a A) Operator[A, A] {
|
||||
|
||||
// Size returns the number of elements in an array.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - as: The array to measure
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - The number of elements in the array
|
||||
//
|
||||
//go:inline
|
||||
func Size[A any](as []A) int {
|
||||
return G.Size(as)
|
||||
@@ -457,58 +867,178 @@ func MonadPartition[A any](as []A, pred func(A) bool) pair.Pair[[]A, []A] {
|
||||
return G.MonadPartition(as, pred)
|
||||
}
|
||||
|
||||
// Partition creates two new arrays out of one, the left result contains the elements
|
||||
// for which the predicate returns false, the right one those for which the predicate returns true
|
||||
// Partition creates two new arrays out of one. The left result contains the elements
|
||||
// for which the predicate returns false, the right one those for which the predicate returns true.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - pred: Predicate function to test each element
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that partitions an array into a pair of arrays
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// isEven := array.Partition(func(x int) bool { return x%2 == 0 })
|
||||
// result := isEven([]int{1, 2, 3, 4, 5, 6})
|
||||
// // result: pair.Pair{Left: []int{1, 3, 5}, Right: []int{2, 4, 6}}
|
||||
//
|
||||
//go:inline
|
||||
func Partition[A any](pred func(A) bool) func([]A) pair.Pair[[]A, []A] {
|
||||
return G.Partition[[]A](pred)
|
||||
}
|
||||
|
||||
// IsNil checks if the array is set to nil
|
||||
// IsNil checks if the array is set to nil.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - as: The array to check
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - true if the array is nil, false otherwise
|
||||
func IsNil[A any](as []A) bool {
|
||||
return array.IsNil(as)
|
||||
}
|
||||
|
||||
// IsNonNil checks if the array is set to nil
|
||||
// IsNonNil checks if the array is not nil.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - as: The array to check
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - true if the array is not nil, false otherwise
|
||||
func IsNonNil[A any](as []A) bool {
|
||||
return array.IsNonNil(as)
|
||||
}
|
||||
|
||||
// ConstNil returns a nil array
|
||||
// ConstNil returns a nil array.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A nil array of type A
|
||||
func ConstNil[A any]() []A {
|
||||
return array.ConstNil[[]A]()
|
||||
}
|
||||
|
||||
// SliceRight extracts a subarray from the specified start index to the end.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - start: The starting index (inclusive)
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that extracts a subarray from start to end
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// fromThird := array.SliceRight[int](2)
|
||||
// result := fromThird([]int{0, 1, 2, 3, 4, 5})
|
||||
// // result: []int{2, 3, 4, 5}
|
||||
//
|
||||
//go:inline
|
||||
func SliceRight[A any](start int) Operator[A, A] {
|
||||
return G.SliceRight[[]A](start)
|
||||
}
|
||||
|
||||
// Copy creates a shallow copy of the array
|
||||
// Copy creates a shallow copy of the array.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - b: The array to copy
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A new array with the same elements
|
||||
//
|
||||
//go:inline
|
||||
func Copy[A any](b []A) []A {
|
||||
return G.Copy(b)
|
||||
}
|
||||
|
||||
// Clone creates a deep copy of the array using the provided endomorphism to clone the values
|
||||
// Clone creates a deep copy of the array using the provided endomorphism to clone the values.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: Function to clone each element
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that creates a deep copy of an array
|
||||
//
|
||||
//go:inline
|
||||
func Clone[A any](f func(A) A) Operator[A, A] {
|
||||
return G.Clone[[]A](f)
|
||||
}
|
||||
|
||||
// FoldMap maps and folds an array. Map the Array passing each value to the iterating function. Then fold the results using the provided Monoid.
|
||||
// FoldMap maps and folds an array. Maps each value using the iterating function,
|
||||
// then folds the results using the provided Monoid.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the input array
|
||||
// - B: The type of elements after mapping
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - m: The Monoid to use for folding
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A curried function that takes a mapping function and returns a function that folds an array
|
||||
//
|
||||
//go:inline
|
||||
func FoldMap[A, B any](m M.Monoid[B]) func(func(A) B) func([]A) B {
|
||||
return G.FoldMap[[]A](m)
|
||||
}
|
||||
|
||||
// FoldMapWithIndex maps and folds an array. Map the Array passing each value to the iterating function. Then fold the results using the provided Monoid.
|
||||
// FoldMapWithIndex maps and folds an array with access to indices. Maps each value using the iterating function,
|
||||
// then folds the results using the provided Monoid.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the input array
|
||||
// - B: The type of elements after mapping
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - m: The Monoid to use for folding
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A curried function that takes a mapping function and returns a function that folds an array
|
||||
//
|
||||
//go:inline
|
||||
func FoldMapWithIndex[A, B any](m M.Monoid[B]) func(func(int, A) B) func([]A) B {
|
||||
@@ -517,12 +1047,46 @@ func FoldMapWithIndex[A, B any](m M.Monoid[B]) func(func(int, A) B) func([]A) B
|
||||
|
||||
// Fold folds the array using the provided Monoid.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - m: The Monoid to use for folding
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that folds an array to a single value
|
||||
//
|
||||
//go:inline
|
||||
func Fold[A any](m M.Monoid[A]) func([]A) A {
|
||||
return G.Fold[[]A](m)
|
||||
}
|
||||
|
||||
// Push adds an element to the end of an array (alias for Append).
|
||||
// Push adds an element to the end of an array (curried version of Append).
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - a: The element to add
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that appends the element to an array
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// addFive := array.Push(5)
|
||||
// result := addFive([]int{1, 2, 3})
|
||||
// // result: []int{1, 2, 3, 5}
|
||||
//
|
||||
// # See Also
|
||||
//
|
||||
// - Append: Non-curried version
|
||||
//
|
||||
//go:inline
|
||||
func Push[A any](a A) Operator[A, A] {
|
||||
@@ -660,6 +1224,20 @@ func Concat[A any](suffix []A) Operator[A, A] {
|
||||
// MonadFlap applies a value to an array of functions, producing an array of results.
|
||||
// This is the monadic version that takes both parameters.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - B: The type of results
|
||||
// - A: The type of the input value
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - fab: Array of functions to apply
|
||||
// - a: The value to apply to each function
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - An array of results from applying the value to each function
|
||||
//
|
||||
//go:inline
|
||||
func MonadFlap[B, A any](fab []func(A) B, a A) []B {
|
||||
return G.MonadFlap[func(A) B, []func(A) B, []B](fab, a)
|
||||
@@ -668,6 +1246,30 @@ func MonadFlap[B, A any](fab []func(A) B, a A) []B {
|
||||
// Flap applies a value to an array of functions, producing an array of results.
|
||||
// This is the curried version.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - B: The type of results
|
||||
// - A: The type of the input value
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - a: The value to apply to each function
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that applies the value to an array of functions
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// fns := []func(int) int{
|
||||
// func(x int) int { return x * 2 },
|
||||
// func(x int) int { return x + 10 },
|
||||
// func(x int) int { return x * x },
|
||||
// }
|
||||
// applyFive := array.Flap[int](5)
|
||||
// result := applyFive(fns)
|
||||
// // result: []int{10, 15, 25}
|
||||
//
|
||||
//go:inline
|
||||
func Flap[B, A any](a A) Operator[func(A) B, B] {
|
||||
return G.Flap[func(A) B, []func(A) B, []B](a)
|
||||
@@ -675,6 +1277,24 @@ func Flap[B, A any](a A) Operator[func(A) B, B] {
|
||||
|
||||
// Prepend adds an element to the beginning of an array, returning a new array.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - A: The type of elements in the array
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - head: The element to add at the beginning
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - A function that prepends the element to an array
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// addZero := array.Prepend(0)
|
||||
// result := addZero([]int{1, 2, 3})
|
||||
// // result: []int{0, 1, 2, 3}
|
||||
//
|
||||
//go:inline
|
||||
func Prepend[A any](head A) Operator[A, A] {
|
||||
return G.Prepend[Operator[A, A]](head)
|
||||
|
||||
@@ -198,11 +198,228 @@ func TestFilterMap(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFoldMap(t *testing.T) {
|
||||
src := From("a", "b", "c")
|
||||
t.Run("FoldMap with 0 items", func(t *testing.T) {
|
||||
empty := []int{}
|
||||
sumMonoid := N.MonoidSum[int]()
|
||||
foldMap := FoldMap[int](sumMonoid)(N.Mul(2))
|
||||
result := foldMap(empty)
|
||||
assert.Equal(t, 0, result, "FoldMap should return monoid empty for 0 items")
|
||||
})
|
||||
|
||||
fold := FoldMap[string](S.Monoid)(strings.ToUpper)
|
||||
t.Run("FoldMap with 1 item", func(t *testing.T) {
|
||||
single := From(5)
|
||||
sumMonoid := N.MonoidSum[int]()
|
||||
foldMap := FoldMap[int](sumMonoid)(N.Mul(2))
|
||||
result := foldMap(single)
|
||||
assert.Equal(t, 10, result, "FoldMap should map and return single item")
|
||||
})
|
||||
|
||||
assert.Equal(t, "ABC", fold(src))
|
||||
t.Run("FoldMap with 2 items", func(t *testing.T) {
|
||||
two := From(3, 4)
|
||||
sumMonoid := N.MonoidSum[int]()
|
||||
foldMap := FoldMap[int](sumMonoid)(N.Mul(2))
|
||||
result := foldMap(two)
|
||||
assert.Equal(t, 14, result, "FoldMap should map and fold 2 items: (3*2) + (4*2) = 14")
|
||||
})
|
||||
|
||||
t.Run("FoldMap with many items", func(t *testing.T) {
|
||||
many := From(1, 2, 3, 4, 5)
|
||||
sumMonoid := N.MonoidSum[int]()
|
||||
foldMap := FoldMap[int](sumMonoid)(N.Mul(2))
|
||||
result := foldMap(many)
|
||||
assert.Equal(t, 30, result, "FoldMap should map and fold many items: (1*2) + (2*2) + (3*2) + (4*2) + (5*2) = 30")
|
||||
})
|
||||
|
||||
t.Run("FoldMap with string concatenation - 0 items", func(t *testing.T) {
|
||||
empty := []string{}
|
||||
fold := FoldMap[string](S.Monoid)(strings.ToUpper)
|
||||
result := fold(empty)
|
||||
assert.Equal(t, "", result, "FoldMap should return empty string for 0 items")
|
||||
})
|
||||
|
||||
t.Run("FoldMap with string concatenation - 1 item", func(t *testing.T) {
|
||||
single := From("a")
|
||||
fold := FoldMap[string](S.Monoid)(strings.ToUpper)
|
||||
result := fold(single)
|
||||
assert.Equal(t, "A", result, "FoldMap should map single string")
|
||||
})
|
||||
|
||||
t.Run("FoldMap with string concatenation - 2 items", func(t *testing.T) {
|
||||
two := From("a", "b")
|
||||
fold := FoldMap[string](S.Monoid)(strings.ToUpper)
|
||||
result := fold(two)
|
||||
assert.Equal(t, "AB", result, "FoldMap should map and concatenate 2 strings")
|
||||
})
|
||||
|
||||
t.Run("FoldMap with string concatenation - many items", func(t *testing.T) {
|
||||
many := From("a", "b", "c", "d", "e")
|
||||
fold := FoldMap[string](S.Monoid)(strings.ToUpper)
|
||||
result := fold(many)
|
||||
assert.Equal(t, "ABCDE", result, "FoldMap should map and concatenate many strings")
|
||||
})
|
||||
}
|
||||
|
||||
func TestFold(t *testing.T) {
|
||||
t.Run("Fold with 0 items", func(t *testing.T) {
|
||||
empty := []int{}
|
||||
sumMonoid := N.MonoidSum[int]()
|
||||
fold := Fold[int](sumMonoid)
|
||||
result := fold(empty)
|
||||
assert.Equal(t, 0, result, "Fold should return monoid empty for 0 items")
|
||||
})
|
||||
|
||||
t.Run("Fold with 1 item", func(t *testing.T) {
|
||||
single := From(42)
|
||||
sumMonoid := N.MonoidSum[int]()
|
||||
fold := Fold[int](sumMonoid)
|
||||
result := fold(single)
|
||||
assert.Equal(t, 42, result, "Fold should return single item")
|
||||
})
|
||||
|
||||
t.Run("Fold with 2 items", func(t *testing.T) {
|
||||
two := From(10, 20)
|
||||
sumMonoid := N.MonoidSum[int]()
|
||||
fold := Fold[int](sumMonoid)
|
||||
result := fold(two)
|
||||
assert.Equal(t, 30, result, "Fold should combine 2 items: 10 + 20 = 30")
|
||||
})
|
||||
|
||||
t.Run("Fold with many items", func(t *testing.T) {
|
||||
many := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
sumMonoid := N.MonoidSum[int]()
|
||||
fold := Fold[int](sumMonoid)
|
||||
result := fold(many)
|
||||
assert.Equal(t, 55, result, "Fold should combine many items: 1+2+3+4+5+6+7+8+9+10 = 55")
|
||||
})
|
||||
|
||||
t.Run("Fold with string concatenation - 0 items", func(t *testing.T) {
|
||||
empty := []string{}
|
||||
fold := Fold[string](S.Monoid)
|
||||
result := fold(empty)
|
||||
assert.Equal(t, "", result, "Fold should return empty string for 0 items")
|
||||
})
|
||||
|
||||
t.Run("Fold with string concatenation - 1 item", func(t *testing.T) {
|
||||
single := From("hello")
|
||||
fold := Fold[string](S.Monoid)
|
||||
result := fold(single)
|
||||
assert.Equal(t, "hello", result, "Fold should return single string")
|
||||
})
|
||||
|
||||
t.Run("Fold with string concatenation - 2 items", func(t *testing.T) {
|
||||
two := From("hello", "world")
|
||||
fold := Fold[string](S.Monoid)
|
||||
result := fold(two)
|
||||
assert.Equal(t, "helloworld", result, "Fold should concatenate 2 strings")
|
||||
})
|
||||
|
||||
t.Run("Fold with string concatenation - many items", func(t *testing.T) {
|
||||
many := From("a", "b", "c", "d", "e", "f")
|
||||
fold := Fold[string](S.Monoid)
|
||||
result := fold(many)
|
||||
assert.Equal(t, "abcdef", result, "Fold should concatenate many strings")
|
||||
})
|
||||
|
||||
t.Run("Fold with product monoid - 0 items", func(t *testing.T) {
|
||||
empty := []int{}
|
||||
productMonoid := N.MonoidProduct[int]()
|
||||
fold := Fold[int](productMonoid)
|
||||
result := fold(empty)
|
||||
assert.Equal(t, 1, result, "Fold should return monoid empty (1) for product with 0 items")
|
||||
})
|
||||
|
||||
t.Run("Fold with product monoid - 1 item", func(t *testing.T) {
|
||||
single := From(7)
|
||||
productMonoid := N.MonoidProduct[int]()
|
||||
fold := Fold[int](productMonoid)
|
||||
result := fold(single)
|
||||
assert.Equal(t, 7, result, "Fold should return single item for product")
|
||||
})
|
||||
|
||||
t.Run("Fold with product monoid - 2 items", func(t *testing.T) {
|
||||
two := From(3, 4)
|
||||
productMonoid := N.MonoidProduct[int]()
|
||||
fold := Fold[int](productMonoid)
|
||||
result := fold(two)
|
||||
assert.Equal(t, 12, result, "Fold should multiply 2 items: 3 * 4 = 12")
|
||||
})
|
||||
|
||||
t.Run("Fold with product monoid - many items", func(t *testing.T) {
|
||||
many := From(2, 3, 4, 5)
|
||||
productMonoid := N.MonoidProduct[int]()
|
||||
fold := Fold[int](productMonoid)
|
||||
result := fold(many)
|
||||
assert.Equal(t, 120, result, "Fold should multiply many items: 2*3*4*5 = 120")
|
||||
})
|
||||
}
|
||||
func TestFoldMapWithIndex(t *testing.T) {
|
||||
t.Run("FoldMapWithIndex with 0 items", func(t *testing.T) {
|
||||
empty := []int{}
|
||||
sumMonoid := N.MonoidSum[int]()
|
||||
foldMap := FoldMapWithIndex[int](sumMonoid)(func(i, x int) int { return i + x })
|
||||
result := foldMap(empty)
|
||||
assert.Equal(t, 0, result, "FoldMapWithIndex should return monoid empty for 0 items")
|
||||
})
|
||||
|
||||
t.Run("FoldMapWithIndex with 1 item", func(t *testing.T) {
|
||||
single := From(10)
|
||||
sumMonoid := N.MonoidSum[int]()
|
||||
foldMap := FoldMapWithIndex[int](sumMonoid)(func(i, x int) int { return i + x })
|
||||
result := foldMap(single)
|
||||
assert.Equal(t, 10, result, "FoldMapWithIndex should map with index: 0 + 10 = 10")
|
||||
})
|
||||
|
||||
t.Run("FoldMapWithIndex with 2 items", func(t *testing.T) {
|
||||
two := From(10, 20)
|
||||
sumMonoid := N.MonoidSum[int]()
|
||||
foldMap := FoldMapWithIndex[int](sumMonoid)(func(i, x int) int { return i + x })
|
||||
result := foldMap(two)
|
||||
assert.Equal(t, 31, result, "FoldMapWithIndex should map with indices: (0+10) + (1+20) = 31")
|
||||
})
|
||||
|
||||
t.Run("FoldMapWithIndex with many items", func(t *testing.T) {
|
||||
many := From(5, 10, 15, 20)
|
||||
sumMonoid := N.MonoidSum[int]()
|
||||
foldMap := FoldMapWithIndex[int](sumMonoid)(func(i, x int) int { return i * x })
|
||||
result := foldMap(many)
|
||||
assert.Equal(t, 100, result, "FoldMapWithIndex should map with indices: (0*5) + (1*10) + (2*15) + (3*20) = 100")
|
||||
})
|
||||
|
||||
t.Run("FoldMapWithIndex with string concatenation - 0 items", func(t *testing.T) {
|
||||
empty := []string{}
|
||||
foldMap := FoldMapWithIndex[string](S.Monoid)(func(i int, s string) string {
|
||||
return fmt.Sprintf("%d:%s", i, s)
|
||||
})
|
||||
result := foldMap(empty)
|
||||
assert.Equal(t, "", result, "FoldMapWithIndex should return empty string for 0 items")
|
||||
})
|
||||
|
||||
t.Run("FoldMapWithIndex with string concatenation - 1 item", func(t *testing.T) {
|
||||
single := From("a")
|
||||
foldMap := FoldMapWithIndex[string](S.Monoid)(func(i int, s string) string {
|
||||
return fmt.Sprintf("%d:%s", i, s)
|
||||
})
|
||||
result := foldMap(single)
|
||||
assert.Equal(t, "0:a", result, "FoldMapWithIndex should format single item with index")
|
||||
})
|
||||
|
||||
t.Run("FoldMapWithIndex with string concatenation - 2 items", func(t *testing.T) {
|
||||
two := From("a", "b")
|
||||
foldMap := FoldMapWithIndex[string](S.Monoid)(func(i int, s string) string {
|
||||
return fmt.Sprintf("%d:%s,", i, s)
|
||||
})
|
||||
result := foldMap(two)
|
||||
assert.Equal(t, "0:a,1:b,", result, "FoldMapWithIndex should format 2 items with indices")
|
||||
})
|
||||
|
||||
t.Run("FoldMapWithIndex with string concatenation - many items", func(t *testing.T) {
|
||||
many := From("a", "b", "c", "d")
|
||||
foldMap := FoldMapWithIndex[string](S.Monoid)(func(i int, s string) string {
|
||||
return fmt.Sprintf("[%d]%s", i, s)
|
||||
})
|
||||
result := foldMap(many)
|
||||
assert.Equal(t, "[0]a[1]b[2]c[3]d", result, "FoldMapWithIndex should format many items with indices")
|
||||
})
|
||||
}
|
||||
|
||||
func ExampleFoldMap() {
|
||||
|
||||
@@ -323,34 +323,49 @@ func Clone[AS ~[]A, A any](f func(A) A) func(as AS) AS {
|
||||
}
|
||||
|
||||
func FoldMap[AS ~[]A, A, B any](m M.Monoid[B]) func(func(A) B) func(AS) B {
|
||||
empty := m.Empty()
|
||||
concat := m.Concat
|
||||
return func(f func(A) B) func(AS) B {
|
||||
return func(as AS) B {
|
||||
return array.Reduce(as, func(cur B, a A) B {
|
||||
return concat(cur, f(a))
|
||||
}, empty)
|
||||
switch len(as) {
|
||||
case 0:
|
||||
return m.Empty()
|
||||
case 1:
|
||||
return f(as[0])
|
||||
case 2:
|
||||
return concat(f(as[0]), f(as[1]))
|
||||
default:
|
||||
return array.Reduce(as[1:], func(cur B, a A) B {
|
||||
return concat(cur, f(a))
|
||||
}, f(as[0]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FoldMapWithIndex[AS ~[]A, A, B any](m M.Monoid[B]) func(func(int, A) B) func(AS) B {
|
||||
empty := m.Empty()
|
||||
concat := m.Concat
|
||||
return func(f func(int, A) B) func(AS) B {
|
||||
return func(as AS) B {
|
||||
return array.ReduceWithIndex(as, func(idx int, cur B, a A) B {
|
||||
return concat(cur, f(idx, a))
|
||||
}, empty)
|
||||
}, m.Empty())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Fold[AS ~[]A, A any](m M.Monoid[A]) func(AS) A {
|
||||
empty := m.Empty()
|
||||
concat := m.Concat
|
||||
return func(as AS) A {
|
||||
return array.Reduce(as, concat, empty)
|
||||
switch len(as) {
|
||||
case 0:
|
||||
return m.Empty()
|
||||
case 1:
|
||||
return as[0]
|
||||
case 2:
|
||||
return concat(as[0], as[1])
|
||||
default:
|
||||
return array.Reduce(as[1:], concat, as[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ 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)
|
||||
return array.MonadSequence(fof, m.Empty, m.Concat, ma)
|
||||
}
|
||||
|
||||
// Sequence takes an array where elements are HKT<A> (higher kinded type) and,
|
||||
@@ -67,7 +67,7 @@ func Sequence[HKTA, HKTRA any](
|
||||
fof func(HKTA) HKTRA,
|
||||
m M.Monoid[HKTRA],
|
||||
) func([]HKTA) HKTRA {
|
||||
return array.Sequence[[]HKTA](fof, m.Empty(), m.Concat)
|
||||
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.
|
||||
|
||||
@@ -13,28 +13,218 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
Package constraints defines a set of useful type constraints for generic programming in Go.
|
||||
|
||||
# Overview
|
||||
|
||||
This package provides type constraints that can be used with Go generics to restrict
|
||||
type parameters to specific categories of types. These constraints are similar to those
|
||||
in Go's standard constraints package but are defined here for consistency within the
|
||||
fp-go project.
|
||||
|
||||
# Type Constraints
|
||||
|
||||
Ordered - Types that support comparison operators:
|
||||
|
||||
type Ordered interface {
|
||||
Integer | Float | ~string
|
||||
}
|
||||
|
||||
Used for types that can be compared using <, <=, >, >= operators.
|
||||
|
||||
Integer - All integer types (signed and unsigned):
|
||||
|
||||
type Integer interface {
|
||||
Signed | Unsigned
|
||||
}
|
||||
|
||||
Signed - Signed integer types:
|
||||
|
||||
type Signed interface {
|
||||
~int | ~int8 | ~int16 | ~int32 | ~int64
|
||||
}
|
||||
|
||||
Unsigned - Unsigned integer types:
|
||||
|
||||
type Unsigned interface {
|
||||
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
|
||||
}
|
||||
|
||||
Float - Floating-point types:
|
||||
|
||||
type Float interface {
|
||||
~float32 | ~float64
|
||||
}
|
||||
|
||||
Complex - Complex number types:
|
||||
|
||||
type Complex interface {
|
||||
~complex64 | ~complex128
|
||||
}
|
||||
|
||||
# Usage Examples
|
||||
|
||||
Using Ordered constraint for comparison:
|
||||
|
||||
import C "github.com/IBM/fp-go/v2/constraints"
|
||||
|
||||
func Min[T C.Ordered](a, b T) T {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
result := Min(5, 3) // 3
|
||||
result := Min(3.14, 2.71) // 2.71
|
||||
result := Min("apple", "banana") // "apple"
|
||||
|
||||
Using Integer constraint:
|
||||
|
||||
func Abs[T C.Integer](n T) T {
|
||||
if n < 0 {
|
||||
return -n
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
result := Abs(-42) // 42
|
||||
result := Abs(uint(10)) // 10
|
||||
|
||||
Using Float constraint:
|
||||
|
||||
func Average[T C.Float](a, b T) T {
|
||||
return (a + b) / 2
|
||||
}
|
||||
|
||||
result := Average(3.14, 2.86) // 3.0
|
||||
|
||||
Using Complex constraint:
|
||||
|
||||
func Magnitude[T C.Complex](c T) float64 {
|
||||
r, i := real(c), imag(c)
|
||||
return math.Sqrt(r*r + i*i)
|
||||
}
|
||||
|
||||
c := complex(3, 4)
|
||||
result := Magnitude(c) // 5.0
|
||||
|
||||
# Combining Constraints
|
||||
|
||||
Constraints can be combined to create more specific type restrictions:
|
||||
|
||||
type Number interface {
|
||||
C.Integer | C.Float | C.Complex
|
||||
}
|
||||
|
||||
func Add[T Number](a, b T) T {
|
||||
return a + b
|
||||
}
|
||||
|
||||
# Tilde Operator
|
||||
|
||||
The ~ operator in type constraints means "underlying type". For example, ~int
|
||||
matches not only int but also any type whose underlying type is int:
|
||||
|
||||
type MyInt int
|
||||
|
||||
func Double[T C.Integer](n T) T {
|
||||
return n * 2
|
||||
}
|
||||
|
||||
var x MyInt = 5
|
||||
result := Double(x) // Works because MyInt's underlying type is int
|
||||
|
||||
# Related Packages
|
||||
|
||||
- number: Provides algebraic structures and utilities for numeric types
|
||||
- ord: Provides ordering operations using these constraints
|
||||
- eq: Provides equality operations for comparable types
|
||||
*/
|
||||
package constraints
|
||||
|
||||
// Ordered is a constraint that permits any ordered type: any type that supports
|
||||
// the operators < <= >= >. Ordered types include integers, floats, and strings.
|
||||
//
|
||||
// This constraint is commonly used for comparison operations, sorting, and
|
||||
// finding minimum/maximum values.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// func Max[T Ordered](a, b T) T {
|
||||
// if a > b {
|
||||
// return a
|
||||
// }
|
||||
// return b
|
||||
// }
|
||||
type Ordered interface {
|
||||
Integer | Float | ~string
|
||||
}
|
||||
|
||||
// Signed is a constraint that permits any signed integer type.
|
||||
// This includes int, int8, int16, int32, and int64, as well as any
|
||||
// types whose underlying type is one of these.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// func Negate[T Signed](n T) T {
|
||||
// return -n
|
||||
// }
|
||||
type Signed interface {
|
||||
~int | ~int8 | ~int16 | ~int32 | ~int64
|
||||
}
|
||||
|
||||
// Unsigned is a constraint that permits any unsigned integer type.
|
||||
// This includes uint, uint8, uint16, uint32, uint64, and uintptr, as well
|
||||
// as any types whose underlying type is one of these.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// func IsEven[T Unsigned](n T) bool {
|
||||
// return n%2 == 0
|
||||
// }
|
||||
type Unsigned interface {
|
||||
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
|
||||
}
|
||||
|
||||
// Integer is a constraint that permits any integer type, both signed and unsigned.
|
||||
// This is a union of the Signed and Unsigned constraints.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// func Abs[T Integer](n T) T {
|
||||
// if n < 0 {
|
||||
// return -n
|
||||
// }
|
||||
// return n
|
||||
// }
|
||||
type Integer interface {
|
||||
Signed | Unsigned
|
||||
}
|
||||
|
||||
// Float is a constraint that permits any floating-point type.
|
||||
// This includes float32 and float64, as well as any types whose
|
||||
// underlying type is one of these.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// func Round[T Float](f T) T {
|
||||
// return T(math.Round(float64(f)))
|
||||
// }
|
||||
type Float interface {
|
||||
~float32 | ~float64
|
||||
}
|
||||
|
||||
// Complex is a constraint that permits any complex numeric type.
|
||||
// This includes complex64 and complex128, as well as any types whose
|
||||
// underlying type is one of these.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// func Conjugate[T Complex](c T) T {
|
||||
// return complex(real(c), -imag(c))
|
||||
// }
|
||||
type Complex interface {
|
||||
~complex64 | ~complex128
|
||||
}
|
||||
|
||||
@@ -204,6 +204,102 @@ func ChainFirst[C, A, B any](f Kleisli[C, A, B]) Operator[C, A, A] {
|
||||
return readerreaderioresult.ChainFirst(f)
|
||||
}
|
||||
|
||||
// ChainFirstThunkK chains an effect with a function that returns a Thunk,
|
||||
// but discards the result and returns the original value.
|
||||
// This is useful for performing side effects (like logging or IO operations) that don't
|
||||
// need the effect's context, without changing the value flowing through the computation.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - C: The context type required by the effect
|
||||
// - A: The value type (preserved)
|
||||
// - B: The type produced by the Thunk (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: A function that takes A and returns Thunk[B] for side effects
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - Operator[C, A, A]: A function that executes the Thunk but preserves the original value
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// logToFile := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
// return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
// return func() result.Result[any] {
|
||||
// // Perform IO operation that doesn't need effect context
|
||||
// fmt.Printf("Logging: %d\n", n)
|
||||
// return result.Of[any](nil)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// eff := effect.Of[MyContext](42)
|
||||
// logged := effect.ChainFirstThunkK[MyContext](logToFile)(eff)
|
||||
// // Prints "Logging: 42" but still produces 42
|
||||
//
|
||||
// # See Also
|
||||
//
|
||||
// - ChainThunkK: Chains with a Thunk and uses its result
|
||||
// - TapThunkK: Alias for ChainFirstThunkK
|
||||
// - ChainFirstIOK: Similar but for IO operations
|
||||
//
|
||||
//go:inline
|
||||
func ChainFirstThunkK[C, A, B any](f thunk.Kleisli[A, B]) Operator[C, A, A] {
|
||||
return fromreader.ChainFirstReaderK(
|
||||
ChainFirst[C, A, B],
|
||||
FromThunk[C, B],
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
// TapThunkK is an alias for ChainFirstThunkK.
|
||||
// It chains an effect with a function that returns a Thunk for side effects,
|
||||
// but preserves the original value. This is useful for logging, debugging, or
|
||||
// performing IO operations that don't need the effect's context.
|
||||
//
|
||||
// # Type Parameters
|
||||
//
|
||||
// - C: The context type required by the effect
|
||||
// - A: The value type (preserved)
|
||||
// - B: The type produced by the Thunk (discarded)
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - f: A function that takes A and returns Thunk[B] for side effects
|
||||
//
|
||||
// # Returns
|
||||
//
|
||||
// - Operator[C, A, A]: A function that executes the Thunk but preserves the original value
|
||||
//
|
||||
// # Example
|
||||
//
|
||||
// performSideEffect := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
// return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
// return func() result.Result[any] {
|
||||
// // Perform context-independent IO operation
|
||||
// log.Printf("Processing value: %d", n)
|
||||
// return result.Of[any](nil)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// eff := effect.Of[MyContext](42)
|
||||
// tapped := effect.TapThunkK[MyContext](performSideEffect)(eff)
|
||||
// // Logs "Processing value: 42" but still produces 42
|
||||
//
|
||||
// # See Also
|
||||
//
|
||||
// - ChainFirstThunkK: The underlying implementation
|
||||
// - TapIOK: Similar but for IO operations
|
||||
// - Tap: Similar but for full effects
|
||||
//
|
||||
//go:inline
|
||||
func TapThunkK[C, A, B any](f thunk.Kleisli[A, B]) Operator[C, A, A] {
|
||||
return ChainFirstThunkK[C](f)
|
||||
}
|
||||
|
||||
// ChainIOK chains an effect with a function that returns an IO action.
|
||||
// This is useful for integrating IO-based computations (synchronous side effects)
|
||||
// into effect chains. The IO action is automatically lifted into the Effect context.
|
||||
@@ -652,6 +748,8 @@ func Read[A, C any](c C) func(Effect[C, A]) Thunk[A] {
|
||||
//
|
||||
// # See Also
|
||||
//
|
||||
// See Also:
|
||||
//
|
||||
// - Ask: Returns the entire context as the value
|
||||
// - Map: Transforms the value after extraction
|
||||
//
|
||||
|
||||
@@ -678,6 +678,587 @@ func TestChainThunkK_Integration(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestChainFirstThunkK_Success(t *testing.T) {
|
||||
t.Run("executes thunk but preserves original value", func(t *testing.T) {
|
||||
sideEffectExecuted := false
|
||||
|
||||
sideEffect := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
sideEffectExecuted = true
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe1(
|
||||
Of[TestConfig](42),
|
||||
ChainFirstThunkK[TestConfig](sideEffect),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Of(42), outcome)
|
||||
assert.True(t, sideEffectExecuted)
|
||||
})
|
||||
|
||||
t.Run("chains multiple side effects", func(t *testing.T) {
|
||||
log := []string{}
|
||||
|
||||
logValue := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
log = append(log, fmt.Sprintf("log: %d", n))
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe2(
|
||||
Of[TestConfig](10),
|
||||
ChainFirstThunkK[TestConfig](logValue),
|
||||
ChainFirstThunkK[TestConfig](logValue),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Of(10), outcome)
|
||||
assert.Equal(t, 2, len(log))
|
||||
assert.Equal(t, "log: 10", log[0])
|
||||
assert.Equal(t, "log: 10", log[1])
|
||||
})
|
||||
|
||||
t.Run("side effect can access runtime context", func(t *testing.T) {
|
||||
var capturedCtx context.Context
|
||||
|
||||
captureContext := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
capturedCtx = ctx
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
computation := F.Pipe1(
|
||||
Of[TestConfig](42),
|
||||
ChainFirstThunkK[TestConfig](captureContext),
|
||||
)
|
||||
outcome := computation(testConfig)(ctx)()
|
||||
|
||||
assert.Equal(t, result.Of(42), outcome)
|
||||
assert.Equal(t, ctx, capturedCtx)
|
||||
})
|
||||
|
||||
t.Run("side effect result is discarded", func(t *testing.T) {
|
||||
returnDifferentValue := func(n int) readerioresult.ReaderIOResult[string] {
|
||||
return func(ctx context.Context) io.IO[result.Result[string]] {
|
||||
return func() result.Result[string] {
|
||||
return result.Of("different value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe1(
|
||||
Of[TestConfig](42),
|
||||
ChainFirstThunkK[TestConfig](returnDifferentValue),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Of(42), outcome)
|
||||
})
|
||||
}
|
||||
|
||||
func TestChainFirstThunkK_Failure(t *testing.T) {
|
||||
t.Run("propagates error from previous effect", func(t *testing.T) {
|
||||
testErr := fmt.Errorf("previous error")
|
||||
sideEffectExecuted := false
|
||||
|
||||
sideEffect := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
sideEffectExecuted = true
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe1(
|
||||
Fail[TestConfig, int](testErr),
|
||||
ChainFirstThunkK[TestConfig](sideEffect),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Left[int](testErr), outcome)
|
||||
assert.False(t, sideEffectExecuted)
|
||||
})
|
||||
|
||||
t.Run("propagates error from thunk side effect", func(t *testing.T) {
|
||||
testErr := fmt.Errorf("side effect error")
|
||||
|
||||
failingSideEffect := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
return result.Left[any](testErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe1(
|
||||
Of[TestConfig](42),
|
||||
ChainFirstThunkK[TestConfig](failingSideEffect),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Left[int](testErr), outcome)
|
||||
})
|
||||
|
||||
t.Run("stops execution on first error", func(t *testing.T) {
|
||||
testErr := fmt.Errorf("first error")
|
||||
secondEffectExecuted := false
|
||||
|
||||
failingEffect := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
return result.Left[any](testErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
secondEffect := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
secondEffectExecuted = true
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe2(
|
||||
Of[TestConfig](42),
|
||||
ChainFirstThunkK[TestConfig](failingEffect),
|
||||
ChainFirstThunkK[TestConfig](secondEffect),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Left[int](testErr), outcome)
|
||||
assert.False(t, secondEffectExecuted)
|
||||
})
|
||||
}
|
||||
|
||||
func TestChainFirstThunkK_EdgeCases(t *testing.T) {
|
||||
t.Run("handles zero value", func(t *testing.T) {
|
||||
callCount := 0
|
||||
|
||||
countCalls := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
callCount++
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe1(
|
||||
Of[TestConfig](0),
|
||||
ChainFirstThunkK[TestConfig](countCalls),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Of(0), outcome)
|
||||
assert.Equal(t, 1, callCount)
|
||||
})
|
||||
|
||||
t.Run("handles empty string", func(t *testing.T) {
|
||||
var capturedValue string
|
||||
|
||||
captureValue := func(s string) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
capturedValue = s
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe1(
|
||||
Of[TestConfig](""),
|
||||
ChainFirstThunkK[TestConfig](captureValue),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Of(""), outcome)
|
||||
assert.Equal(t, "", capturedValue)
|
||||
})
|
||||
|
||||
t.Run("handles nil pointer", func(t *testing.T) {
|
||||
var capturedPtr *int
|
||||
|
||||
capturePtr := func(ptr *int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
capturedPtr = ptr
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe1(
|
||||
Of[TestConfig]((*int)(nil)),
|
||||
ChainFirstThunkK[TestConfig](capturePtr),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Of((*int)(nil)), outcome)
|
||||
assert.Nil(t, capturedPtr)
|
||||
})
|
||||
}
|
||||
|
||||
func TestChainFirstThunkK_Integration(t *testing.T) {
|
||||
t.Run("composes with Map and Chain", func(t *testing.T) {
|
||||
log := []string{}
|
||||
|
||||
logValue := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
log = append(log, fmt.Sprintf("value: %d", n))
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe3(
|
||||
Of[TestConfig](5),
|
||||
Map[TestConfig](func(x int) int { return x * 2 }),
|
||||
ChainFirstThunkK[TestConfig](logValue),
|
||||
Map[TestConfig](func(x int) int { return x + 3 }),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Of(13), outcome) // (5 * 2) + 3
|
||||
assert.Equal(t, 1, len(log))
|
||||
assert.Equal(t, "value: 10", log[0])
|
||||
})
|
||||
|
||||
t.Run("composes with ChainThunkK", func(t *testing.T) {
|
||||
log := []string{}
|
||||
|
||||
logSideEffect := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
log = append(log, fmt.Sprintf("side-effect: %d", n))
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transformValue := func(n int) readerioresult.ReaderIOResult[string] {
|
||||
return func(ctx context.Context) io.IO[result.Result[string]] {
|
||||
return func() result.Result[string] {
|
||||
log = append(log, fmt.Sprintf("transform: %d", n))
|
||||
return result.Of(fmt.Sprintf("Result: %d", n))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe2(
|
||||
Of[TestConfig](42),
|
||||
ChainFirstThunkK[TestConfig](logSideEffect),
|
||||
ChainThunkK[TestConfig](transformValue),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Of("Result: 42"), outcome)
|
||||
assert.Equal(t, 2, len(log))
|
||||
assert.Equal(t, "side-effect: 42", log[0])
|
||||
assert.Equal(t, "transform: 42", log[1])
|
||||
})
|
||||
|
||||
t.Run("composes with ChainReaderK and ChainReaderIOK", func(t *testing.T) {
|
||||
log := []string{}
|
||||
|
||||
addMultiplier := func(n int) reader.Reader[TestConfig, int] {
|
||||
return func(cfg TestConfig) int {
|
||||
return n + cfg.Multiplier
|
||||
}
|
||||
}
|
||||
|
||||
logReaderIO := func(n int) readerio.ReaderIO[TestConfig, int] {
|
||||
return func(cfg TestConfig) io.IO[int] {
|
||||
return func() int {
|
||||
log = append(log, fmt.Sprintf("reader-io: %d", n))
|
||||
return n * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logThunk := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
log = append(log, fmt.Sprintf("thunk: %d", n))
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe3(
|
||||
Of[TestConfig](5),
|
||||
ChainReaderK(addMultiplier),
|
||||
ChainReaderIOK(logReaderIO),
|
||||
ChainFirstThunkK[TestConfig](logThunk),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Of(16), outcome) // (5 + 3) * 2
|
||||
assert.Equal(t, 2, len(log))
|
||||
assert.Equal(t, "reader-io: 8", log[0])
|
||||
assert.Equal(t, "thunk: 16", log[1])
|
||||
})
|
||||
}
|
||||
|
||||
func TestTapThunkK_Success(t *testing.T) {
|
||||
t.Run("is alias for ChainFirstThunkK", func(t *testing.T) {
|
||||
log := []string{}
|
||||
|
||||
logValue := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
log = append(log, fmt.Sprintf("tapped: %d", n))
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe1(
|
||||
Of[TestConfig](42),
|
||||
TapThunkK[TestConfig](logValue),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Of(42), outcome)
|
||||
assert.Equal(t, 1, len(log))
|
||||
assert.Equal(t, "tapped: 42", log[0])
|
||||
})
|
||||
|
||||
t.Run("useful for logging without changing value", func(t *testing.T) {
|
||||
log := []string{}
|
||||
|
||||
logStep := func(step string) func(int) readerioresult.ReaderIOResult[any] {
|
||||
return func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
log = append(log, fmt.Sprintf("%s: %d", step, n))
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe4(
|
||||
Of[TestConfig](10),
|
||||
TapThunkK[TestConfig](logStep("start")),
|
||||
Map[TestConfig](func(x int) int { return x * 2 }),
|
||||
TapThunkK[TestConfig](logStep("after-map")),
|
||||
Map[TestConfig](func(x int) int { return x + 5 }),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Of(25), outcome) // (10 * 2) + 5
|
||||
assert.Equal(t, 2, len(log))
|
||||
assert.Equal(t, "start: 10", log[0])
|
||||
assert.Equal(t, "after-map: 20", log[1])
|
||||
})
|
||||
|
||||
t.Run("can perform IO operations", func(t *testing.T) {
|
||||
var ioExecuted bool
|
||||
|
||||
performIO := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
// Simulate IO operation
|
||||
ioExecuted = true
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe1(
|
||||
Of[TestConfig](42),
|
||||
TapThunkK[TestConfig](performIO),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Of(42), outcome)
|
||||
assert.True(t, ioExecuted)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTapThunkK_Failure(t *testing.T) {
|
||||
t.Run("propagates error from previous effect", func(t *testing.T) {
|
||||
testErr := fmt.Errorf("previous error")
|
||||
tapExecuted := false
|
||||
|
||||
tapValue := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
tapExecuted = true
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe1(
|
||||
Fail[TestConfig, int](testErr),
|
||||
TapThunkK[TestConfig](tapValue),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Left[int](testErr), outcome)
|
||||
assert.False(t, tapExecuted)
|
||||
})
|
||||
|
||||
t.Run("propagates error from tap operation", func(t *testing.T) {
|
||||
testErr := fmt.Errorf("tap error")
|
||||
|
||||
failingTap := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
return result.Left[any](testErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe1(
|
||||
Of[TestConfig](42),
|
||||
TapThunkK[TestConfig](failingTap),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Left[int](testErr), outcome)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTapThunkK_EdgeCases(t *testing.T) {
|
||||
t.Run("handles multiple taps in sequence", func(t *testing.T) {
|
||||
log := []string{}
|
||||
|
||||
tap1 := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
log = append(log, "tap1")
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tap2 := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
log = append(log, "tap2")
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tap3 := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
log = append(log, "tap3")
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe3(
|
||||
Of[TestConfig](42),
|
||||
TapThunkK[TestConfig](tap1),
|
||||
TapThunkK[TestConfig](tap2),
|
||||
TapThunkK[TestConfig](tap3),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Of(42), outcome)
|
||||
assert.Equal(t, []string{"tap1", "tap2", "tap3"}, log)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTapThunkK_Integration(t *testing.T) {
|
||||
t.Run("real-world logging scenario", func(t *testing.T) {
|
||||
log := []string{}
|
||||
|
||||
logStart := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
log = append(log, fmt.Sprintf("Starting computation with: %d", n))
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logIntermediate := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
log = append(log, fmt.Sprintf("Intermediate result: %d", n))
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logFinal := func(s string) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
log = append(log, fmt.Sprintf("Final result: %s", s))
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe5(
|
||||
Of[TestConfig](10),
|
||||
TapThunkK[TestConfig](logStart),
|
||||
Map[TestConfig](func(x int) int { return x * 3 }),
|
||||
TapThunkK[TestConfig](logIntermediate),
|
||||
Map[TestConfig](func(x int) string { return fmt.Sprintf("Value: %d", x) }),
|
||||
TapThunkK[TestConfig](logFinal),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Of("Value: 30"), outcome)
|
||||
assert.Equal(t, 3, len(log))
|
||||
assert.Equal(t, "Starting computation with: 10", log[0])
|
||||
assert.Equal(t, "Intermediate result: 30", log[1])
|
||||
assert.Equal(t, "Final result: Value: 30", log[2])
|
||||
})
|
||||
|
||||
t.Run("composes with FromThunk", func(t *testing.T) {
|
||||
log := []string{}
|
||||
|
||||
thunk := func(ctx context.Context) io.IO[result.Result[int]] {
|
||||
return func() result.Result[int] {
|
||||
return result.Of(100)
|
||||
}
|
||||
}
|
||||
|
||||
logValue := func(n int) readerioresult.ReaderIOResult[any] {
|
||||
return func(ctx context.Context) io.IO[result.Result[any]] {
|
||||
return func() result.Result[any] {
|
||||
log = append(log, fmt.Sprintf("value: %d", n))
|
||||
return result.Of[any](nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computation := F.Pipe1(
|
||||
FromThunk[TestConfig](thunk),
|
||||
TapThunkK[TestConfig](logValue),
|
||||
)
|
||||
outcome := computation(testConfig)(context.Background())()
|
||||
|
||||
assert.Equal(t, result.Of(100), outcome)
|
||||
assert.Equal(t, 1, len(log))
|
||||
assert.Equal(t, "value: 100", log[0])
|
||||
})
|
||||
}
|
||||
|
||||
func TestAsks_Success(t *testing.T) {
|
||||
t.Run("extracts a field from context", func(t *testing.T) {
|
||||
type Config struct {
|
||||
@@ -685,7 +1266,7 @@ func TestAsks_Success(t *testing.T) {
|
||||
Port int
|
||||
}
|
||||
|
||||
getHost := Asks[Config](func(cfg Config) string {
|
||||
getHost := Asks(func(cfg Config) string {
|
||||
return cfg.Host
|
||||
})
|
||||
|
||||
@@ -701,7 +1282,7 @@ func TestAsks_Success(t *testing.T) {
|
||||
Port int
|
||||
}
|
||||
|
||||
getURL := Asks[Config](func(cfg Config) string {
|
||||
getURL := Asks(func(cfg Config) string {
|
||||
return fmt.Sprintf("http://%s:%d", cfg.Host, cfg.Port)
|
||||
})
|
||||
|
||||
@@ -712,7 +1293,7 @@ func TestAsks_Success(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("extracts numeric field", func(t *testing.T) {
|
||||
getPort := Asks[TestConfig](func(cfg TestConfig) int {
|
||||
getPort := Asks(func(cfg TestConfig) int {
|
||||
return cfg.Multiplier
|
||||
})
|
||||
|
||||
@@ -728,7 +1309,7 @@ func TestAsks_Success(t *testing.T) {
|
||||
Height int
|
||||
}
|
||||
|
||||
getArea := Asks[Config](func(cfg Config) int {
|
||||
getArea := Asks(func(cfg Config) int {
|
||||
return cfg.Width * cfg.Height
|
||||
})
|
||||
|
||||
@@ -739,7 +1320,7 @@ func TestAsks_Success(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("transforms string field", func(t *testing.T) {
|
||||
getUpperPrefix := Asks[TestConfig](func(cfg TestConfig) string {
|
||||
getUpperPrefix := Asks(func(cfg TestConfig) string {
|
||||
return fmt.Sprintf("[%s]", cfg.Prefix)
|
||||
})
|
||||
|
||||
@@ -756,7 +1337,7 @@ func TestAsks_EdgeCases(t *testing.T) {
|
||||
Value int
|
||||
}
|
||||
|
||||
getValue := Asks[Config](func(cfg Config) int {
|
||||
getValue := Asks(func(cfg Config) int {
|
||||
return cfg.Value
|
||||
})
|
||||
|
||||
@@ -771,7 +1352,7 @@ func TestAsks_EdgeCases(t *testing.T) {
|
||||
Name string
|
||||
}
|
||||
|
||||
getName := Asks[Config](func(cfg Config) string {
|
||||
getName := Asks(func(cfg Config) string {
|
||||
return cfg.Name
|
||||
})
|
||||
|
||||
@@ -786,7 +1367,7 @@ func TestAsks_EdgeCases(t *testing.T) {
|
||||
Data *string
|
||||
}
|
||||
|
||||
hasData := Asks[Config](func(cfg Config) bool {
|
||||
hasData := Asks(func(cfg Config) bool {
|
||||
return cfg.Data != nil
|
||||
})
|
||||
|
||||
@@ -805,7 +1386,7 @@ func TestAsks_EdgeCases(t *testing.T) {
|
||||
DB Database
|
||||
}
|
||||
|
||||
getDBHost := Asks[Config](func(cfg Config) string {
|
||||
getDBHost := Asks(func(cfg Config) string {
|
||||
return cfg.DB.Host
|
||||
})
|
||||
|
||||
@@ -825,7 +1406,7 @@ func TestAsks_Integration(t *testing.T) {
|
||||
}
|
||||
|
||||
computation := F.Pipe1(
|
||||
Asks[Config](func(cfg Config) int {
|
||||
Asks(func(cfg Config) int {
|
||||
return cfg.Value
|
||||
}),
|
||||
Map[Config](func(x int) int { return x * 2 }),
|
||||
@@ -843,7 +1424,7 @@ func TestAsks_Integration(t *testing.T) {
|
||||
}
|
||||
|
||||
computation := F.Pipe1(
|
||||
Asks[Config](func(cfg Config) int {
|
||||
Asks(func(cfg Config) int {
|
||||
return cfg.Multiplier
|
||||
}),
|
||||
Chain(func(mult int) Effect[Config, int] {
|
||||
@@ -859,7 +1440,7 @@ func TestAsks_Integration(t *testing.T) {
|
||||
|
||||
t.Run("composes with ChainReaderK", func(t *testing.T) {
|
||||
computation := F.Pipe1(
|
||||
Asks[TestConfig](func(cfg TestConfig) int {
|
||||
Asks(func(cfg TestConfig) int {
|
||||
return cfg.Multiplier
|
||||
}),
|
||||
ChainReaderK(func(mult int) reader.Reader[TestConfig, int] {
|
||||
@@ -879,7 +1460,7 @@ func TestAsks_Integration(t *testing.T) {
|
||||
log := []string{}
|
||||
|
||||
computation := F.Pipe1(
|
||||
Asks[TestConfig](func(cfg TestConfig) string {
|
||||
Asks(func(cfg TestConfig) string {
|
||||
return cfg.Prefix
|
||||
}),
|
||||
ChainReaderIOK(func(prefix string) readerio.ReaderIO[TestConfig, string] {
|
||||
@@ -906,11 +1487,11 @@ func TestAsks_Integration(t *testing.T) {
|
||||
}
|
||||
|
||||
computation := F.Pipe2(
|
||||
Asks[Config](func(cfg Config) string {
|
||||
Asks(func(cfg Config) string {
|
||||
return cfg.First
|
||||
}),
|
||||
Chain(func(_ string) Effect[Config, string] {
|
||||
return Asks[Config](func(cfg Config) string {
|
||||
return Asks(func(cfg Config) string {
|
||||
return cfg.Second
|
||||
})
|
||||
}),
|
||||
@@ -933,7 +1514,7 @@ func TestAsks_Integration(t *testing.T) {
|
||||
computation := F.Pipe1(
|
||||
Ask[Config](),
|
||||
Chain(func(cfg Config) Effect[Config, int] {
|
||||
return Asks[Config](func(c Config) int {
|
||||
return Asks(func(c Config) int {
|
||||
return c.Value * 2
|
||||
})
|
||||
}),
|
||||
@@ -953,7 +1534,7 @@ func TestAsks_Comparison(t *testing.T) {
|
||||
}
|
||||
|
||||
// Using Asks
|
||||
asksVersion := Asks[Config](func(cfg Config) int {
|
||||
asksVersion := Asks(func(cfg Config) int {
|
||||
return cfg.Port
|
||||
})
|
||||
|
||||
@@ -983,7 +1564,7 @@ func TestAsks_Comparison(t *testing.T) {
|
||||
}
|
||||
|
||||
// Asks is more direct for field extraction
|
||||
getHost := Asks[Config](func(cfg Config) string {
|
||||
getHost := Asks(func(cfg Config) string {
|
||||
return cfg.Host
|
||||
})
|
||||
|
||||
@@ -1003,7 +1584,7 @@ func TestAsks_RealWorldScenarios(t *testing.T) {
|
||||
User string
|
||||
}
|
||||
|
||||
getConnectionString := Asks[DatabaseConfig](func(cfg DatabaseConfig) string {
|
||||
getConnectionString := Asks(func(cfg DatabaseConfig) string {
|
||||
return fmt.Sprintf("postgres://%s@%s:%d/%s",
|
||||
cfg.User, cfg.Host, cfg.Port, cfg.Database)
|
||||
})
|
||||
@@ -1027,7 +1608,7 @@ func TestAsks_RealWorldScenarios(t *testing.T) {
|
||||
BasePath string
|
||||
}
|
||||
|
||||
getEndpoint := Asks[APIConfig](func(cfg APIConfig) string {
|
||||
getEndpoint := Asks(func(cfg APIConfig) string {
|
||||
return fmt.Sprintf("%s://%s:%d%s",
|
||||
cfg.Protocol, cfg.Host, cfg.Port, cfg.BasePath)
|
||||
})
|
||||
@@ -1049,7 +1630,7 @@ func TestAsks_RealWorldScenarios(t *testing.T) {
|
||||
MaxRetries int
|
||||
}
|
||||
|
||||
isValid := Asks[Config](func(cfg Config) bool {
|
||||
isValid := Asks(func(cfg Config) bool {
|
||||
return cfg.Timeout > 0 && cfg.MaxRetries >= 0
|
||||
})
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
// This file was generated by robots at
|
||||
// 2025-03-09 23:52:50.7205396 +0100 CET m=+0.001580301
|
||||
|
||||
package function
|
||||
|
||||
// Pipe0 takes an initial value t0 and successively applies 0 functions where the input of a function is the return value of the previous function
|
||||
@@ -93,7 +97,6 @@ func Unsliced1[F ~func([]T) R, T, R any](f F) func(T) R {
|
||||
// The final return value is the result of the last function application
|
||||
//go:inline
|
||||
func Pipe2[F1 ~func(T0) T1, F2 ~func(T1) T2, T0, T1, T2 any](t0 T0, f1 F1, f2 F2) T2 {
|
||||
//go:inline
|
||||
return f2(f1(t0))
|
||||
}
|
||||
|
||||
@@ -161,7 +164,6 @@ func Unsliced2[F ~func([]T) R, T, R any](f F) func(T, T) R {
|
||||
// The final return value is the result of the last function application
|
||||
//go:inline
|
||||
func Pipe3[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, T0, T1, T2, T3 any](t0 T0, f1 F1, f2 F2, f3 F3) T3 {
|
||||
//go:inline
|
||||
return f3(f2(f1(t0)))
|
||||
}
|
||||
|
||||
@@ -227,8 +229,7 @@ func Unsliced3[F ~func([]T) R, T, R any](f F) func(T, T, T) R {
|
||||
// The final return value is the result of the last function application
|
||||
//go:inline
|
||||
func Pipe4[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, T0, T1, T2, T3, T4 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4) T4 {
|
||||
//go:inline
|
||||
return Pipe2(Pipe2(t0, f1, f2), f3, f4)
|
||||
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
|
||||
@@ -236,7 +237,9 @@ func Pipe4[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, T
|
||||
//go:inline
|
||||
func Flow4[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, T0, T1, T2, T3, T4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(T0) T4 {
|
||||
//go:inline
|
||||
return Flow2(Flow2(f1, f2), Flow2(f3, f4))
|
||||
return func(t0 T0) T4 {
|
||||
return Pipe4(t0, f1, f2, f3, f4)
|
||||
}
|
||||
}
|
||||
|
||||
// Nullary4 creates a parameter less function from a parameter less function and 3 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions
|
||||
@@ -293,8 +296,7 @@ func Unsliced4[F ~func([]T) R, T, R any](f F) func(T, T, T, T) R {
|
||||
// The final return value is the result of the last function application
|
||||
//go:inline
|
||||
func Pipe5[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, T0, T1, T2, T3, T4, T5 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) T5 {
|
||||
//go:inline
|
||||
return Pipe3(Pipe2(t0, f1, f2), f3, f4, f5)
|
||||
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
|
||||
@@ -302,7 +304,9 @@ func Pipe5[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F
|
||||
//go:inline
|
||||
func Flow5[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, T0, T1, T2, T3, T4, T5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(T0) T5 {
|
||||
//go:inline
|
||||
return Flow2(Flow2(f1, f2), Flow3(f3, f4, f5))
|
||||
return func(t0 T0) T5 {
|
||||
return Pipe5(t0, f1, f2, f3, f4, f5)
|
||||
}
|
||||
}
|
||||
|
||||
// Nullary5 creates a parameter less function from a parameter less function and 4 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions
|
||||
@@ -361,8 +365,7 @@ func Unsliced5[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T) R {
|
||||
// The final return value is the result of the last function application
|
||||
//go:inline
|
||||
func Pipe6[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, T0, T1, T2, T3, T4, T5, T6 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) T6 {
|
||||
//go:inline
|
||||
return Pipe3(Pipe3(t0, f1, f2, f3), f4, f5, f6)
|
||||
return f6(f5(f4(f3(f2(f1(t0))))))
|
||||
}
|
||||
|
||||
// Flow6 creates a function that takes an initial value t0 and successively applies 6 functions where the input of a function is the return value of the previous function
|
||||
@@ -370,7 +373,9 @@ func Pipe6[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F
|
||||
//go:inline
|
||||
func Flow6[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, T0, T1, T2, T3, T4, T5, T6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(T0) T6 {
|
||||
//go:inline
|
||||
return Flow2(Flow3(f1, f2, f3), Flow3(f4, f5, f6))
|
||||
return func(t0 T0) T6 {
|
||||
return Pipe6(t0, f1, f2, f3, f4, f5, f6)
|
||||
}
|
||||
}
|
||||
|
||||
// Nullary6 creates a parameter less function from a parameter less function and 5 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions
|
||||
@@ -431,8 +436,7 @@ func Unsliced6[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T) R {
|
||||
// The final return value is the result of the last function application
|
||||
//go:inline
|
||||
func Pipe7[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, T0, T1, T2, T3, T4, T5, T6, T7 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) T7 {
|
||||
//go:inline
|
||||
return Pipe3(Pipe4(t0, f1, f2, f3, f4), f5, f6, f7)
|
||||
return f7(f6(f5(f4(f3(f2(f1(t0)))))))
|
||||
}
|
||||
|
||||
// Flow7 creates a function that takes an initial value t0 and successively applies 7 functions where the input of a function is the return value of the previous function
|
||||
@@ -440,7 +444,9 @@ func Pipe7[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F
|
||||
//go:inline
|
||||
func Flow7[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, T0, T1, T2, T3, T4, T5, T6, T7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(T0) T7 {
|
||||
//go:inline
|
||||
return Flow2(Flow3(f1, f2, f3), Flow4(f4, f5, f6, f7))
|
||||
return func(t0 T0) T7 {
|
||||
return Pipe7(t0, f1, f2, f3, f4, f5, f6, f7)
|
||||
}
|
||||
}
|
||||
|
||||
// Nullary7 creates a parameter less function from a parameter less function and 6 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions
|
||||
@@ -503,8 +509,7 @@ func Unsliced7[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T) R {
|
||||
// The final return value is the result of the last function application
|
||||
//go:inline
|
||||
func Pipe8[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, T0, T1, T2, T3, T4, T5, T6, T7, T8 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) T8 {
|
||||
//go:inline
|
||||
return Pipe4(Pipe4(t0, f1, f2, f3, f4), f5, f6, f7, f8)
|
||||
return f8(f7(f6(f5(f4(f3(f2(f1(t0))))))))
|
||||
}
|
||||
|
||||
// Flow8 creates a function that takes an initial value t0 and successively applies 8 functions where the input of a function is the return value of the previous function
|
||||
@@ -512,7 +517,9 @@ func Pipe8[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F
|
||||
//go:inline
|
||||
func Flow8[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, T0, T1, T2, T3, T4, T5, T6, T7, T8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(T0) T8 {
|
||||
//go:inline
|
||||
return Flow2(Flow4(f1, f2, f3, f4), Flow4(f5, f6, f7, f8))
|
||||
return func(t0 T0) T8 {
|
||||
return Pipe8(t0, f1, f2, f3, f4, f5, f6, f7, f8)
|
||||
}
|
||||
}
|
||||
|
||||
// Nullary8 creates a parameter less function from a parameter less function and 7 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions
|
||||
@@ -577,8 +584,7 @@ func Unsliced8[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T, T) R {
|
||||
// The final return value is the result of the last function application
|
||||
//go:inline
|
||||
func Pipe9[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) T9 {
|
||||
//go:inline
|
||||
return Pipe4(Pipe5(t0, f1, f2, f3, f4, f5), f6, f7, f8, f9)
|
||||
return f9(f8(f7(f6(f5(f4(f3(f2(f1(t0)))))))))
|
||||
}
|
||||
|
||||
// Flow9 creates a function that takes an initial value t0 and successively applies 9 functions where the input of a function is the return value of the previous function
|
||||
@@ -586,7 +592,9 @@ func Pipe9[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F
|
||||
//go:inline
|
||||
func Flow9[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(T0) T9 {
|
||||
//go:inline
|
||||
return Flow2(Flow4(f1, f2, f3, f4), Flow5(f5, f6, f7, f8, f9))
|
||||
return func(t0 T0) T9 {
|
||||
return Pipe9(t0, f1, f2, f3, f4, f5, f6, f7, f8, f9)
|
||||
}
|
||||
}
|
||||
|
||||
// Nullary9 creates a parameter less function from a parameter less function and 8 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions
|
||||
@@ -653,8 +661,7 @@ func Unsliced9[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T, T, T) R
|
||||
// The final return value is the result of the last function application
|
||||
//go:inline
|
||||
func Pipe10[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) T10 {
|
||||
//go:inline
|
||||
return Pipe5(Pipe5(t0, f1, f2, f3, f4, f5), f6, f7, f8, f9, f10)
|
||||
return f10(f9(f8(f7(f6(f5(f4(f3(f2(f1(t0))))))))))
|
||||
}
|
||||
|
||||
// Flow10 creates a function that takes an initial value t0 and successively applies 10 functions where the input of a function is the return value of the previous function
|
||||
@@ -662,7 +669,9 @@ func Pipe10[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
|
||||
//go:inline
|
||||
func Flow10[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(T0) T10 {
|
||||
//go:inline
|
||||
return Flow2(Flow5(f1, f2, f3, f4, f5), Flow5(f6, f7, f8, f9, f10))
|
||||
return func(t0 T0) T10 {
|
||||
return Pipe10(t0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10)
|
||||
}
|
||||
}
|
||||
|
||||
// Nullary10 creates a parameter less function from a parameter less function and 9 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions
|
||||
@@ -739,7 +748,9 @@ func Pipe11[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4,
|
||||
//go:inline
|
||||
func Flow11[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11) func(T0) T11 {
|
||||
//go:inline
|
||||
return Flow2(Flow5(f1, f2, f3, f4, f5), Flow6(f6, f7, f8, f9, f10, f11))
|
||||
return func(t0 T0) T11 {
|
||||
return Pipe11(t0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11)
|
||||
}
|
||||
}
|
||||
|
||||
// Nullary11 creates a parameter less function from a parameter less function and 10 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions
|
||||
|
||||
@@ -4,7 +4,7 @@ go 1.24
|
||||
|
||||
require (
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/urfave/cli/v3 v3.7.0
|
||||
github.com/urfave/cli/v3 v3.8.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@@ -4,10 +4,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/urfave/cli/v3 v3.6.2 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8=
|
||||
github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/urfave/cli/v3 v3.7.0 h1:AGSnbUyjtLiM+WJUb4dzXKldl/gL+F8OwmRDtVr6g2U=
|
||||
github.com/urfave/cli/v3 v3.7.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI=
|
||||
github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
@@ -46,7 +46,7 @@ import (
|
||||
// - Multiple elements: recursively divides and conquers
|
||||
func MonadSequenceSegment[HKTB, HKTRB any](
|
||||
fof func(HKTB) HKTRB,
|
||||
empty HKTRB,
|
||||
empty func() HKTRB,
|
||||
concat func(HKTRB, HKTRB) HKTRB,
|
||||
fbs []HKTB,
|
||||
start, end int,
|
||||
@@ -54,7 +54,7 @@ func MonadSequenceSegment[HKTB, HKTRB any](
|
||||
|
||||
switch end - start {
|
||||
case 0:
|
||||
return empty
|
||||
return empty()
|
||||
case 1:
|
||||
return fof(fbs[start])
|
||||
default:
|
||||
@@ -254,7 +254,7 @@ HKTAB = HKT<func(A)B>
|
||||
*/
|
||||
func MonadSequence[GA ~[]HKTA, HKTA, HKTRA any](
|
||||
fof func(HKTA) HKTRA,
|
||||
empty HKTRA,
|
||||
empty func() HKTRA,
|
||||
concat func(HKTRA, HKTRA) HKTRA,
|
||||
|
||||
ta GA) HKTRA {
|
||||
@@ -263,7 +263,7 @@ func MonadSequence[GA ~[]HKTA, HKTA, HKTRA any](
|
||||
|
||||
func Sequence[GA ~[]HKTA, HKTA, HKTRA any](
|
||||
fof func(HKTA) HKTRA,
|
||||
empty HKTRA,
|
||||
empty func() HKTRA,
|
||||
concat func(HKTRA, HKTRA) HKTRA,
|
||||
) func(GA) HKTRA {
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ func MonadTraverse[GA ~func(yield func(A) bool), GB ~func(yield func(B) bool), A
|
||||
|
||||
fof := F.Bind2nd(fmap_b, Of[GB])
|
||||
|
||||
empty := fof_gb(Empty[GB]())
|
||||
empty := F.Nullary2(Empty[GB], fof_gb)
|
||||
|
||||
cb := F.Curry2(Concat[GB])
|
||||
concat_gb := F.Bind2nd(fmap_gb, cb)
|
||||
@@ -180,7 +180,7 @@ func MonadSequence[GA ~func(yield func(HKTA) bool), HKTA, HKTRA any](
|
||||
|
||||
// convert to an array
|
||||
hktb := ToArray[GA, []HKTA](ta)
|
||||
return INTA.MonadSequenceSegment(fof, m.Empty(), m.Concat, hktb, 0, len(hktb))
|
||||
return INTA.MonadSequenceSegment(fof, m.Empty, m.Concat, hktb, 0, len(hktb))
|
||||
}
|
||||
|
||||
// MonadTraverseWithIndex traverses an iterator sequence with index tracking, applying an effectful
|
||||
@@ -223,7 +223,7 @@ func MonadTraverseWithIndex[GA ~func(yield func(A) bool), A, HKTB, HKTRB any](
|
||||
|
||||
// convert to an array
|
||||
hktb := MonadMapToArrayWithIndex[GA, []HKTB](ta, f)
|
||||
return INTA.MonadSequenceSegment(fof, m.Empty(), m.Concat, hktb, 0, len(hktb))
|
||||
return INTA.MonadSequenceSegment(fof, m.Empty, m.Concat, hktb, 0, len(hktb))
|
||||
}
|
||||
|
||||
// Sequence is the curried version of MonadSequence, returning a function that sequences an iterator of effects.
|
||||
|
||||
@@ -34,6 +34,13 @@ import (
|
||||
// 3. Filtering to keep only pairs where the boolean (tail) is true
|
||||
// 4. Extracting the original values (head) from the filtered pairs
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Data: --1--2--3--4--5-->
|
||||
// Selectors: --T--F--T--F--T-->
|
||||
// Compress
|
||||
// Output: --1-----3-----5-->
|
||||
//
|
||||
// RxJS Equivalent: Similar to combining [zip] with [filter] - https://rxjs.dev/api/operators/zip
|
||||
//
|
||||
// Type Parameters:
|
||||
|
||||
@@ -21,6 +21,12 @@ package iter
|
||||
// all elements repeatedly. When the end of the input sequence is reached, it starts over
|
||||
// from the beginning, continuing this pattern forever.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: --1--2--3|
|
||||
// Cycle
|
||||
// Output: --1--2--3--1--2--3--1--2--3--> (infinite)
|
||||
//
|
||||
// RxJS Equivalent: [repeat] - https://rxjs.dev/api/operators/repeat
|
||||
//
|
||||
// WARNING: This creates an INFINITE sequence for non-empty inputs. It must be used with
|
||||
|
||||
@@ -23,6 +23,16 @@ import "github.com/IBM/fp-go/v2/option"
|
||||
// contains at least one element, it returns Some(element). If the iterator is empty,
|
||||
// it returns None. The function consumes only the first element of the iterator.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: --1--2--3--4--5-->
|
||||
// First
|
||||
// Output: --Some(1)|
|
||||
//
|
||||
// Input: --|
|
||||
// First
|
||||
// Output: --None|
|
||||
//
|
||||
// RxJS Equivalent: [first] - https://rxjs.dev/api/operators/first
|
||||
//
|
||||
// Type Parameters:
|
||||
|
||||
@@ -82,6 +82,12 @@ func Of2[K, A any](k K, a A) Seq2[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.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: --1--2--3-->
|
||||
// Map(x => x * 2)
|
||||
// Output: --2--4--6-->
|
||||
//
|
||||
// RxJS Equivalent: [map] - https://rxjs.dev/api/operators/map
|
||||
//
|
||||
// Example:
|
||||
@@ -186,6 +192,12 @@ func MapWithKey[K, A, B any](f func(K, A) B) Operator2[K, A, B] {
|
||||
|
||||
// MonadFilter returns a sequence containing only elements that satisfy the predicate.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: --1--2--3--4--5-->
|
||||
// Filter(x => x % 2 == 0)
|
||||
// Output: -----2-----4----->
|
||||
//
|
||||
// RxJS Equivalent: [filter] - https://rxjs.dev/api/operators/filter
|
||||
//
|
||||
// Example:
|
||||
@@ -293,6 +305,12 @@ func FilterWithKey[K, A any](pred func(K, A) bool) Operator2[K, A, A] {
|
||||
// MonadFilterMap applies a function that returns an Option to each element,
|
||||
// keeping only the Some values and unwrapping them.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: --1--2--3--4--5-->
|
||||
// FilterMap(x => x % 2 == 0 ? Some(x * 10) : None)
|
||||
// Output: -----20----40---->
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// seq := From(1, 2, 3, 4, 5)
|
||||
@@ -430,6 +448,12 @@ func FilterMapWithKey[K, A, B any](f func(K, A) Option[B]) Operator2[K, A, B] {
|
||||
// MonadChain applies a function that returns a sequence to each element and flattens the results.
|
||||
// This is the monadic bind operation (flatMap).
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: --1-----2-----3---->
|
||||
// Chain(x => [x, x*10])
|
||||
// Output: --1-10--2-20--3-30->
|
||||
//
|
||||
// RxJS Equivalent: [mergeMap/flatMap] - https://rxjs.dev/api/operators/mergeMap
|
||||
//
|
||||
// Example:
|
||||
@@ -473,6 +497,12 @@ func FlatMap[A, B any](f func(A) Seq[B]) Operator[A, B] {
|
||||
|
||||
// Flatten flattens a sequence of sequences into a single sequence.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: --[1,2]--[3,4]--[5]-->
|
||||
// Flatten
|
||||
// Output: --1-2----3-4----5---->
|
||||
//
|
||||
// RxJS Equivalent: [mergeAll] - https://rxjs.dev/api/operators/mergeAll
|
||||
//
|
||||
// Example:
|
||||
@@ -489,6 +519,14 @@ func Flatten[A any](mma Seq[Seq[A]]) Seq[A] {
|
||||
// MonadAp applies a sequence of functions to a sequence of values.
|
||||
// This is the applicative apply operation.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Functions: --(*2)---(+10)-->
|
||||
// Values: --5------3------>
|
||||
// Ap
|
||||
// Output: --10-6---15-13-->
|
||||
// (each function applied to each value)
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// fns := From(N.Mul(2), N.Add(10))
|
||||
@@ -577,6 +615,13 @@ func Replicate[A any](n int, a A) Seq[A] {
|
||||
// MonadReduce reduces a sequence to a single value by applying a function to each element
|
||||
// and an accumulator, starting with an initial value.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: --1--2--3--4--5--|
|
||||
// Reduce((acc, x) => acc + x, 0)
|
||||
// Output: ------------------15|
|
||||
// (emits final result only)
|
||||
//
|
||||
// RxJS Equivalent: [reduce] - https://rxjs.dev/api/operators/reduce
|
||||
//
|
||||
// Example:
|
||||
@@ -811,6 +856,13 @@ func FoldMapWithKey[K, A, B any](m M.Monoid[B]) func(func(K, A) B) func(Seq2[K,
|
||||
// MonadFlap applies a fixed value to a sequence of functions.
|
||||
// This is the dual of MonadAp.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Functions: --(*2)---(+10)-->
|
||||
// Value: 5 (fixed)
|
||||
// Flap
|
||||
// Output: --10-----15----->
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// fns := From(N.Mul(2), N.Add(10))
|
||||
@@ -832,6 +884,12 @@ func Flap[B, A any](a A) Operator[func(A) B, B] {
|
||||
|
||||
// Prepend returns a function that adds an element to the beginning of a sequence.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: -----2--3--4-->
|
||||
// Prepend(1)
|
||||
// Output: --1--2--3--4-->
|
||||
//
|
||||
// RxJS Equivalent: [startWith] - https://rxjs.dev/api/operators/startWith
|
||||
//
|
||||
// Example:
|
||||
@@ -847,6 +905,12 @@ func Prepend[A any](head A) Operator[A, A] {
|
||||
|
||||
// Append returns a function that adds an element to the end of a sequence.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: --1--2--3-----|
|
||||
// Append(4)
|
||||
// Output: --1--2--3--4--|
|
||||
//
|
||||
// RxJS Equivalent: [endWith] - https://rxjs.dev/api/operators/endWith
|
||||
//
|
||||
// Example:
|
||||
@@ -863,6 +927,14 @@ func Append[A any](tail A) Operator[A, A] {
|
||||
// MonadZip combines two sequences into a sequence of pairs.
|
||||
// The resulting sequence stops when either input sequence is exhausted.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// SeqA: --1--2--3---->
|
||||
// SeqB: --a--b------->
|
||||
// Zip
|
||||
// Output: --(1,a)-(2,b)|
|
||||
// (stops when shorter sequence ends)
|
||||
//
|
||||
// RxJS Equivalent: [zip] - https://rxjs.dev/api/operators/zip
|
||||
//
|
||||
// Example:
|
||||
@@ -1079,3 +1151,61 @@ func FromSeqPair[A, B any](as Seq[Pair[A, B]]) Seq2[A, B] {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skip returns an operator that skips the first n elements of a sequence.
|
||||
//
|
||||
// This function creates a transformation that discards the first n elements from
|
||||
// the source sequence and yields all remaining elements. If n is less than or equal
|
||||
// to 0, all elements are yielded. If n is greater than or equal to the sequence length,
|
||||
// an empty sequence is returned.
|
||||
//
|
||||
// The operation is lazy and only consumes elements from the source sequence as needed.
|
||||
// The first n elements are consumed and discarded, then subsequent elements are yielded.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: --1--2--3--4--5--6--7--8-->
|
||||
// Skip(3)
|
||||
// Output: -----------4--5--6--7--8-->
|
||||
//
|
||||
// RxJS Equivalent: [skip] - https://rxjs.dev/api/operators/skip
|
||||
//
|
||||
// Type Parameters:
|
||||
// - U: The type of elements in the sequence
|
||||
//
|
||||
// Parameters:
|
||||
// - count: The number of elements to skip from the beginning of the sequence
|
||||
//
|
||||
// Returns:
|
||||
// - An Operator that transforms a Seq[U] by skipping the first count elements
|
||||
//
|
||||
// Example - Skip first 3 elements:
|
||||
//
|
||||
// seq := From(1, 2, 3, 4, 5)
|
||||
// result := Skip[int](3)(seq)
|
||||
// // yields: 4, 5
|
||||
//
|
||||
// Example - Skip more than available:
|
||||
//
|
||||
// seq := From(1, 2)
|
||||
// result := Skip[int](5)(seq)
|
||||
// // yields: nothing (empty sequence)
|
||||
//
|
||||
// Example - Skip zero or negative:
|
||||
//
|
||||
// seq := From(1, 2, 3)
|
||||
// result := Skip[int](0)(seq)
|
||||
// // yields: 1, 2, 3 (all elements)
|
||||
//
|
||||
// Example - Chaining with other operations:
|
||||
//
|
||||
// seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
// result := F.Pipe2(
|
||||
// seq,
|
||||
// Skip[int](3),
|
||||
// MonadFilter(seq, func(x int) bool { return x%2 == 0 }),
|
||||
// )
|
||||
// // yields: 4, 6, 8, 10 (skip first 3, then filter evens)
|
||||
func Skip[U any](count int) Operator[U, U] {
|
||||
return FilterWithIndex(func(idx int, _ U) bool { return idx >= count })
|
||||
}
|
||||
|
||||
@@ -612,3 +612,440 @@ func TestMapToArrayIdentity(t *testing.T) {
|
||||
result := mapper(seq)
|
||||
assert.Equal(t, []string{"a", "b", "c"}, result)
|
||||
}
|
||||
|
||||
// TestSkip tests basic Skip functionality
|
||||
func TestSkip(t *testing.T) {
|
||||
t.Run("skips first n elements from sequence", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
result := toSlice(Skip[int](3)(seq))
|
||||
assert.Equal(t, []int{4, 5}, result)
|
||||
})
|
||||
|
||||
t.Run("skips first element", func(t *testing.T) {
|
||||
seq := From(10, 20, 30)
|
||||
result := toSlice(Skip[int](1)(seq))
|
||||
assert.Equal(t, []int{20, 30}, result)
|
||||
})
|
||||
|
||||
t.Run("skips all elements when n equals length", func(t *testing.T) {
|
||||
seq := From(1, 2, 3)
|
||||
result := toSlice(Skip[int](3)(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("skips all elements when n exceeds length", func(t *testing.T) {
|
||||
seq := From(1, 2, 3)
|
||||
result := toSlice(Skip[int](10)(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("skips from string sequence", func(t *testing.T) {
|
||||
seq := From("a", "b", "c", "d", "e")
|
||||
result := toSlice(Skip[string](2)(seq))
|
||||
assert.Equal(t, []string{"c", "d", "e"}, result)
|
||||
})
|
||||
|
||||
t.Run("skips from single element sequence", func(t *testing.T) {
|
||||
seq := From(42)
|
||||
result := toSlice(Skip[int](1)(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("skips from large sequence", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
result := toSlice(Skip[int](7)(seq))
|
||||
assert.Equal(t, []int{8, 9, 10}, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSkipZeroOrNegative tests Skip with zero or negative values
|
||||
func TestSkipZeroOrNegative(t *testing.T) {
|
||||
t.Run("returns all elements when n is zero", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
result := toSlice(Skip[int](0)(seq))
|
||||
assert.Equal(t, []int{1, 2, 3, 4, 5}, result)
|
||||
})
|
||||
|
||||
t.Run("returns all elements when n is negative", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
result := toSlice(Skip[int](-1)(seq))
|
||||
assert.Equal(t, []int{1, 2, 3, 4, 5}, result)
|
||||
})
|
||||
|
||||
t.Run("returns all elements when n is large negative", func(t *testing.T) {
|
||||
seq := From("a", "b", "c")
|
||||
result := toSlice(Skip[string](-100)(seq))
|
||||
assert.Equal(t, []string{"a", "b", "c"}, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSkipEmpty tests Skip with empty sequences
|
||||
func TestSkipEmpty(t *testing.T) {
|
||||
t.Run("returns empty from empty integer sequence", func(t *testing.T) {
|
||||
seq := Empty[int]()
|
||||
result := toSlice(Skip[int](5)(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("returns empty from empty string sequence", func(t *testing.T) {
|
||||
seq := Empty[string]()
|
||||
result := toSlice(Skip[string](3)(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("returns empty when skipping zero from empty", func(t *testing.T) {
|
||||
seq := Empty[int]()
|
||||
result := toSlice(Skip[int](0)(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSkipWithComplexTypes tests Skip with complex data types
|
||||
func TestSkipWithComplexTypes(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
t.Run("skips structs", func(t *testing.T) {
|
||||
seq := From(
|
||||
Person{"Alice", 30},
|
||||
Person{"Bob", 25},
|
||||
Person{"Charlie", 35},
|
||||
Person{"David", 28},
|
||||
)
|
||||
result := toSlice(Skip[Person](2)(seq))
|
||||
expected := []Person{
|
||||
{"Charlie", 35},
|
||||
{"David", 28},
|
||||
}
|
||||
assert.Equal(t, expected, result)
|
||||
})
|
||||
|
||||
t.Run("skips pointers", func(t *testing.T) {
|
||||
p1 := &Person{"Alice", 30}
|
||||
p2 := &Person{"Bob", 25}
|
||||
p3 := &Person{"Charlie", 35}
|
||||
seq := From(p1, p2, p3)
|
||||
result := toSlice(Skip[*Person](1)(seq))
|
||||
assert.Equal(t, []*Person{p2, p3}, result)
|
||||
})
|
||||
|
||||
t.Run("skips slices", func(t *testing.T) {
|
||||
seq := From([]int{1, 2}, []int{3, 4}, []int{5, 6}, []int{7, 8})
|
||||
result := toSlice(Skip[[]int](2)(seq))
|
||||
expected := [][]int{{5, 6}, {7, 8}}
|
||||
assert.Equal(t, expected, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSkipWithChainedOperations tests Skip with other sequence operations
|
||||
func TestSkipWithChainedOperations(t *testing.T) {
|
||||
t.Run("skip after map", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
mapped := MonadMap(seq, N.Mul(2))
|
||||
result := toSlice(Skip[int](2)(mapped))
|
||||
assert.Equal(t, []int{6, 8, 10}, result)
|
||||
})
|
||||
|
||||
t.Run("skip after filter", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
filtered := MonadFilter(seq, func(x int) bool { return x%2 == 0 })
|
||||
result := toSlice(Skip[int](2)(filtered))
|
||||
assert.Equal(t, []int{6, 8, 10}, result)
|
||||
})
|
||||
|
||||
t.Run("map after skip", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
skipped := Skip[int](2)(seq)
|
||||
result := toSlice(MonadMap(skipped, N.Mul(10)))
|
||||
assert.Equal(t, []int{30, 40, 50}, result)
|
||||
})
|
||||
|
||||
t.Run("filter after skip", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8)
|
||||
skipped := Skip[int](2)(seq)
|
||||
result := toSlice(MonadFilter(skipped, func(x int) bool { return x%2 == 0 }))
|
||||
assert.Equal(t, []int{4, 6, 8}, result)
|
||||
})
|
||||
|
||||
t.Run("skip after chain", func(t *testing.T) {
|
||||
seq := From(1, 2, 3)
|
||||
chained := MonadChain(seq, func(x int) Seq[int] {
|
||||
return From(x, x*10)
|
||||
})
|
||||
result := toSlice(Skip[int](3)(chained))
|
||||
assert.Equal(t, []int{20, 3, 30}, result)
|
||||
})
|
||||
|
||||
t.Run("multiple skips", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
skipped1 := Skip[int](2)(seq)
|
||||
skipped2 := Skip[int](3)(skipped1)
|
||||
result := toSlice(skipped2)
|
||||
assert.Equal(t, []int{6, 7, 8, 9, 10}, result)
|
||||
})
|
||||
|
||||
t.Run("skip and take", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
skipped := Skip[int](3)(seq)
|
||||
taken := Take[int](3)(skipped)
|
||||
result := toSlice(taken)
|
||||
assert.Equal(t, []int{4, 5, 6}, result)
|
||||
})
|
||||
|
||||
t.Run("take and skip", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
taken := Take[int](7)(seq)
|
||||
skipped := Skip[int](2)(taken)
|
||||
result := toSlice(skipped)
|
||||
assert.Equal(t, []int{3, 4, 5, 6, 7}, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSkipWithReplicate tests Skip with Replicate
|
||||
func TestSkipWithReplicate(t *testing.T) {
|
||||
t.Run("skips from replicated sequence", func(t *testing.T) {
|
||||
seq := Replicate(10, 42)
|
||||
result := toSlice(Skip[int](7)(seq))
|
||||
assert.Equal(t, []int{42, 42, 42}, result)
|
||||
})
|
||||
|
||||
t.Run("skips all from short replicate", func(t *testing.T) {
|
||||
seq := Replicate(2, "hello")
|
||||
result := toSlice(Skip[string](5)(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("skips zero from replicate", func(t *testing.T) {
|
||||
seq := Replicate(3, 100)
|
||||
result := toSlice(Skip[int](0)(seq))
|
||||
assert.Equal(t, []int{100, 100, 100}, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSkipWithMakeBy tests Skip with MakeBy
|
||||
func TestSkipWithMakeBy(t *testing.T) {
|
||||
t.Run("skips from generated sequence", func(t *testing.T) {
|
||||
seq := MakeBy(10, func(i int) int { return i * i })
|
||||
result := toSlice(Skip[int](5)(seq))
|
||||
assert.Equal(t, []int{25, 36, 49, 64, 81}, result)
|
||||
})
|
||||
|
||||
t.Run("skips more than generated", func(t *testing.T) {
|
||||
seq := MakeBy(3, func(i int) int { return i + 1 })
|
||||
result := toSlice(Skip[int](10)(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSkipWithPrependAppend tests Skip with Prepend and Append
|
||||
func TestSkipWithPrependAppend(t *testing.T) {
|
||||
t.Run("skip from prepended sequence", func(t *testing.T) {
|
||||
seq := From(2, 3, 4, 5)
|
||||
prepended := Prepend(1)(seq)
|
||||
result := toSlice(Skip[int](2)(prepended))
|
||||
assert.Equal(t, []int{3, 4, 5}, result)
|
||||
})
|
||||
|
||||
t.Run("skip from appended sequence", func(t *testing.T) {
|
||||
seq := From(1, 2, 3)
|
||||
appended := Append(4)(seq)
|
||||
result := toSlice(Skip[int](2)(appended))
|
||||
assert.Equal(t, []int{3, 4}, result)
|
||||
})
|
||||
|
||||
t.Run("skip includes appended element", func(t *testing.T) {
|
||||
seq := From(1, 2, 3)
|
||||
appended := Append(4)(seq)
|
||||
result := toSlice(Skip[int](3)(appended))
|
||||
assert.Equal(t, []int{4}, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSkipWithFlatten tests Skip with Flatten
|
||||
func TestSkipWithFlatten(t *testing.T) {
|
||||
t.Run("skips from flattened sequence", func(t *testing.T) {
|
||||
nested := From(From(1, 2), From(3, 4), From(5, 6))
|
||||
flattened := Flatten(nested)
|
||||
result := toSlice(Skip[int](3)(flattened))
|
||||
assert.Equal(t, []int{4, 5, 6}, result)
|
||||
})
|
||||
|
||||
t.Run("skips from flattened with empty inner sequences", func(t *testing.T) {
|
||||
nested := From(From(1, 2), Empty[int](), From(3, 4))
|
||||
flattened := Flatten(nested)
|
||||
result := toSlice(Skip[int](2)(flattened))
|
||||
assert.Equal(t, []int{3, 4}, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSkipDoesNotConsumeSkippedElements tests that Skip is efficient
|
||||
func TestSkipDoesNotConsumeSkippedElements(t *testing.T) {
|
||||
t.Run("processes all elements including skipped", func(t *testing.T) {
|
||||
callCount := 0
|
||||
seq := MonadMap(From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), func(x int) int {
|
||||
callCount++
|
||||
return x * 2
|
||||
})
|
||||
|
||||
skipped := Skip[int](7)(seq)
|
||||
|
||||
result := []int{}
|
||||
for v := range skipped {
|
||||
result = append(result, v)
|
||||
}
|
||||
|
||||
assert.Equal(t, []int{16, 18, 20}, result)
|
||||
// Skip still needs to iterate through skipped elements to count them
|
||||
assert.Equal(t, 10, callCount, "should process all elements")
|
||||
})
|
||||
}
|
||||
|
||||
// TestSkipEdgeCases tests edge cases
|
||||
func TestSkipEdgeCases(t *testing.T) {
|
||||
t.Run("skip 0 from single element", func(t *testing.T) {
|
||||
seq := From(42)
|
||||
result := toSlice(Skip[int](0)(seq))
|
||||
assert.Equal(t, []int{42}, result)
|
||||
})
|
||||
|
||||
t.Run("skip 1 from single element", func(t *testing.T) {
|
||||
seq := From(42)
|
||||
result := toSlice(Skip[int](1)(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("skip large number from small sequence", func(t *testing.T) {
|
||||
seq := From(1, 2)
|
||||
result := toSlice(Skip[int](1000000)(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("skip with very large n", func(t *testing.T) {
|
||||
seq := From(1, 2, 3)
|
||||
result := toSlice(Skip[int](int(^uint(0) >> 1))(seq)) // max int
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("skip all but one", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
result := toSlice(Skip[int](4)(seq))
|
||||
assert.Equal(t, []int{5}, result)
|
||||
})
|
||||
}
|
||||
|
||||
// Benchmark tests for Skip
|
||||
func BenchmarkSkip(b *testing.B) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
skipped := Skip[int](5)(seq)
|
||||
for range skipped {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSkipLargeSequence(b *testing.B) {
|
||||
data := make([]int, 1000)
|
||||
for i := range data {
|
||||
data[i] = i
|
||||
}
|
||||
seq := From(data...)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
skipped := Skip[int](900)(seq)
|
||||
for range skipped {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSkipWithMap(b *testing.B) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
mapped := MonadMap(seq, N.Mul(2))
|
||||
skipped := Skip[int](5)(mapped)
|
||||
for range skipped {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSkipWithFilter(b *testing.B) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
filtered := MonadFilter(seq, func(x int) bool { return x%2 == 0 })
|
||||
skipped := Skip[int](2)(filtered)
|
||||
for range skipped {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Example tests for documentation
|
||||
func ExampleSkip() {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
skipped := Skip[int](3)(seq)
|
||||
|
||||
for v := range skipped {
|
||||
fmt.Printf("%d ", v)
|
||||
}
|
||||
// Output: 4 5
|
||||
}
|
||||
|
||||
func ExampleSkip_moreThanAvailable() {
|
||||
seq := From(1, 2, 3)
|
||||
skipped := Skip[int](10)(seq)
|
||||
|
||||
count := 0
|
||||
for range skipped {
|
||||
count++
|
||||
}
|
||||
fmt.Printf("Count: %d\n", count)
|
||||
// Output: Count: 0
|
||||
}
|
||||
|
||||
func ExampleSkip_zero() {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
skipped := Skip[int](0)(seq)
|
||||
|
||||
for v := range skipped {
|
||||
fmt.Printf("%d ", v)
|
||||
}
|
||||
// Output: 1 2 3 4 5
|
||||
}
|
||||
|
||||
func ExampleSkip_withFilter() {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
evens := MonadFilter(seq, func(x int) bool { return x%2 == 0 })
|
||||
skipped := Skip[int](2)(evens)
|
||||
|
||||
for v := range skipped {
|
||||
fmt.Printf("%d ", v)
|
||||
}
|
||||
// Output: 6 8 10
|
||||
}
|
||||
|
||||
func ExampleSkip_withMap() {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
doubled := MonadMap(seq, N.Mul(2))
|
||||
skipped := Skip[int](2)(doubled)
|
||||
|
||||
for v := range skipped {
|
||||
fmt.Printf("%d ", v)
|
||||
}
|
||||
// Output: 6 8 10
|
||||
}
|
||||
|
||||
func ExampleSkip_chained() {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
result := F.Pipe3(
|
||||
seq,
|
||||
Skip[int](3),
|
||||
Filter(func(x int) bool { return x%2 == 0 }),
|
||||
toSlice[int],
|
||||
)
|
||||
|
||||
fmt.Println(result)
|
||||
// Output: [4 6 8 10]
|
||||
}
|
||||
|
||||
@@ -10,6 +10,16 @@ import (
|
||||
// sequence. If the iterator contains at least one element, it returns Some(element).
|
||||
// If the iterator is empty, it returns None.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: --1--2--3--4--5--|
|
||||
// Last
|
||||
// Output: -----------------Some(5)|
|
||||
//
|
||||
// Input: --|
|
||||
// Last
|
||||
// Output: --None|
|
||||
//
|
||||
// RxJS Equivalent: [last] - https://rxjs.dev/api/operators/last
|
||||
//
|
||||
// Type Parameters:
|
||||
|
||||
@@ -28,6 +28,13 @@ import (
|
||||
//
|
||||
// This is the monadic form that takes the sequence as the first parameter.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: --1--2--3--4--5-->
|
||||
// ChainOptionK(x => x % 2 == 0 ? Some(x * 10) : None)
|
||||
// Output: -----20----40---->
|
||||
// (filters and transforms)
|
||||
//
|
||||
// RxJS Equivalent: [concatMap] combined with [filter] - https://rxjs.dev/api/operators/concatMap
|
||||
//
|
||||
// Type parameters:
|
||||
@@ -72,6 +79,13 @@ func MonadChainOptionK[A, B any](as Seq[A], f option.Kleisli[A, B]) Seq[B] {
|
||||
// This is the curried version of [MonadChainOptionK], useful for function composition
|
||||
// and creating reusable transformations.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: --1--2--3--4--5-->
|
||||
// ChainOptionK(x => x > 2 ? Some(x) : None)
|
||||
// Output: --------3--4--5-->
|
||||
// (filters out values <= 2)
|
||||
//
|
||||
// RxJS Equivalent: [concatMap] combined with [filter] - https://rxjs.dev/api/operators/concatMap
|
||||
//
|
||||
// Type parameters:
|
||||
|
||||
@@ -24,6 +24,13 @@ package iter
|
||||
//
|
||||
// The operation is lazy - intermediate values are computed only as they are consumed.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: --1--2--3--4--5-->
|
||||
// Scan((acc, x) => acc + x, 0)
|
||||
// Output: --1--3--6--10-15->
|
||||
// (running sum)
|
||||
//
|
||||
// RxJS Equivalent: [scan] - https://rxjs.dev/api/operators/scan
|
||||
//
|
||||
// Scan is useful for:
|
||||
|
||||
@@ -27,6 +27,12 @@ import F "github.com/IBM/fp-go/v2/function"
|
||||
// Once n elements have been yielded, iteration stops immediately without consuming
|
||||
// the remaining elements from the source.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: --1--2--3--4--5--6--7--8-->
|
||||
// Take(3)
|
||||
// Output: --1--2--3|
|
||||
//
|
||||
// RxJS Equivalent: [take] - https://rxjs.dev/api/operators/take
|
||||
//
|
||||
// Type Parameters:
|
||||
@@ -78,3 +84,158 @@ func Take[U any](n int) Operator[U, U] {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TakeWhile returns an operator that emits elements from a sequence while a predicate is satisfied.
|
||||
//
|
||||
// This function creates a transformation that yields elements from the source sequence
|
||||
// as long as each element satisfies the provided predicate. Once an element fails the
|
||||
// predicate test, the sequence terminates immediately, and no further elements are
|
||||
// emitted, even if subsequent elements would satisfy the predicate.
|
||||
//
|
||||
// The operation is lazy and only consumes elements from the source sequence as needed.
|
||||
// Once the predicate returns false, iteration stops immediately without consuming
|
||||
// the remaining elements from the source.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: --1--2--3--4--5--2--1-->
|
||||
// TakeWhile(x < 4)
|
||||
// Output: --1--2--3|
|
||||
// (stops at 4)
|
||||
//
|
||||
// RxJS Equivalent: [takeWhile] - https://rxjs.dev/api/operators/takeWhile
|
||||
//
|
||||
// Type Parameters:
|
||||
// - U: The type of elements in the sequence
|
||||
//
|
||||
// Parameters:
|
||||
// - p: A predicate function that tests each element. Returns true to continue, false to stop
|
||||
//
|
||||
// Returns:
|
||||
// - An Operator that transforms a Seq[U] by taking elements while the predicate is satisfied
|
||||
//
|
||||
// Example - Take while less than threshold:
|
||||
//
|
||||
// seq := From(1, 2, 3, 4, 5, 2, 1)
|
||||
// result := TakeWhile(func(x int) bool { return x < 4 })(seq)
|
||||
// // yields: 1, 2, 3 (stops at 4, doesn't continue to 2, 1)
|
||||
//
|
||||
// Example - Take while condition is met:
|
||||
//
|
||||
// seq := From("a", "b", "c", "1", "d", "e")
|
||||
// isLetter := func(s string) bool { return s >= "a" && s <= "z" }
|
||||
// result := TakeWhile(isLetter)(seq)
|
||||
// // yields: "a", "b", "c" (stops at "1")
|
||||
//
|
||||
// Example - Take all when predicate always true:
|
||||
//
|
||||
// seq := From(2, 4, 6, 8)
|
||||
// result := TakeWhile(func(x int) bool { return x%2 == 0 })(seq)
|
||||
// // yields: 2, 4, 6, 8 (all elements satisfy predicate)
|
||||
//
|
||||
// Example - Take none when first element fails:
|
||||
//
|
||||
// seq := From(5, 1, 2, 3)
|
||||
// result := TakeWhile(func(x int) bool { return x < 5 })(seq)
|
||||
// // yields: nothing (first element fails predicate)
|
||||
//
|
||||
// Example - Chaining with other operations:
|
||||
//
|
||||
// seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
// result := F.Pipe2(
|
||||
// seq,
|
||||
// MonadMap(seq, func(x int) int { return x * 2 }),
|
||||
// TakeWhile(func(x int) bool { return x < 10 }),
|
||||
// )
|
||||
// // yields: 2, 4, 6, 8 (stops when doubled value reaches 10)
|
||||
func TakeWhile[U any](p Predicate[U]) Operator[U, U] {
|
||||
return func(s Seq[U]) Seq[U] {
|
||||
return func(yield func(U) bool) {
|
||||
for u := range s {
|
||||
if !p(u) || !yield(u) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SkipWhile returns an operator that skips elements from a sequence while a predicate is satisfied.
|
||||
//
|
||||
// This function creates a transformation that discards elements from the source sequence
|
||||
// as long as each element satisfies the provided predicate. Once an element fails the
|
||||
// predicate test, that element and all subsequent elements are yielded, regardless of
|
||||
// whether they satisfy the predicate.
|
||||
//
|
||||
// The operation is lazy and only consumes elements from the source sequence as needed.
|
||||
// Once the predicate returns false, all remaining elements are yielded without further
|
||||
// predicate evaluation.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: --1--2--3--4--5--2--1-->
|
||||
// SkipWhile(x < 4)
|
||||
// Output: -----------4--5--2--1-->
|
||||
// (starts at 4, continues with all)
|
||||
//
|
||||
// RxJS Equivalent: [skipWhile] - https://rxjs.dev/api/operators/skipWhile
|
||||
//
|
||||
// Type Parameters:
|
||||
// - U: The type of elements in the sequence
|
||||
//
|
||||
// Parameters:
|
||||
// - p: A predicate function that tests each element. Returns true to skip, false to start yielding
|
||||
//
|
||||
// Returns:
|
||||
// - An Operator that transforms a Seq[U] by skipping elements while the predicate is satisfied
|
||||
//
|
||||
// Example - Skip while less than threshold:
|
||||
//
|
||||
// seq := From(1, 2, 3, 4, 5, 2, 1)
|
||||
// result := SkipWhile(func(x int) bool { return x < 4 })(seq)
|
||||
// // yields: 4, 5, 2, 1 (starts at 4, continues with all remaining)
|
||||
//
|
||||
// Example - Skip while condition is met:
|
||||
//
|
||||
// seq := From("a", "b", "c", "1", "d", "e")
|
||||
// isLetter := func(s string) bool { return s >= "a" && s <= "z" }
|
||||
// result := SkipWhile(isLetter)(seq)
|
||||
// // yields: "1", "d", "e" (starts at "1", continues with all remaining)
|
||||
//
|
||||
// Example - Skip none when first element fails:
|
||||
//
|
||||
// seq := From(5, 1, 2, 3)
|
||||
// result := SkipWhile(func(x int) bool { return x < 5 })(seq)
|
||||
// // yields: 5, 1, 2, 3 (first element fails predicate, all yielded)
|
||||
//
|
||||
// Example - Skip all when predicate always true:
|
||||
//
|
||||
// seq := From(2, 4, 6, 8)
|
||||
// result := SkipWhile(func(x int) bool { return x%2 == 0 })(seq)
|
||||
// // yields: nothing (all elements satisfy predicate)
|
||||
//
|
||||
// Example - Chaining with other operations:
|
||||
//
|
||||
// seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
// result := F.Pipe2(
|
||||
// seq,
|
||||
// SkipWhile(func(x int) bool { return x < 5 }),
|
||||
// MonadMap(seq, func(x int) int { return x * 2 }),
|
||||
// )
|
||||
// // yields: 10, 12, 14, 16, 18, 20 (skip until 5, then double remaining)
|
||||
func SkipWhile[U any](p Predicate[U]) Operator[U, U] {
|
||||
return func(s Seq[U]) Seq[U] {
|
||||
return func(yield func(U) bool) {
|
||||
skipping := true
|
||||
for u := range s {
|
||||
if skipping && p(u) {
|
||||
continue
|
||||
}
|
||||
skipping = false
|
||||
if !yield(u) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,3 +461,831 @@ func ExampleTake_chained() {
|
||||
}
|
||||
// Output: 4 5 6 7 8
|
||||
}
|
||||
|
||||
// TestSkipWhile tests basic SkipWhile functionality
|
||||
func TestSkipWhile(t *testing.T) {
|
||||
t.Run("skips while predicate is true", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 2, 1)
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x < 4 })(seq))
|
||||
assert.Equal(t, []int{4, 5, 2, 1}, result)
|
||||
})
|
||||
|
||||
t.Run("skips none when first element fails", func(t *testing.T) {
|
||||
seq := From(5, 1, 2, 3)
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x < 5 })(seq))
|
||||
assert.Equal(t, []int{5, 1, 2, 3}, result)
|
||||
})
|
||||
|
||||
t.Run("skips all when predicate always true", func(t *testing.T) {
|
||||
seq := From(2, 4, 6, 8)
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x%2 == 0 })(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("skips from string sequence", func(t *testing.T) {
|
||||
seq := From("a", "b", "c", "1", "d", "e")
|
||||
isLetter := func(s string) bool { return s >= "a" && s <= "z" }
|
||||
result := toSlice(SkipWhile(isLetter)(seq))
|
||||
assert.Equal(t, []string{"1", "d", "e"}, result)
|
||||
})
|
||||
|
||||
t.Run("continues after predicate fails", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 1, 2, 3)
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x < 4 })(seq))
|
||||
assert.Equal(t, []int{4, 1, 2, 3}, result)
|
||||
})
|
||||
|
||||
t.Run("skips single element", func(t *testing.T) {
|
||||
seq := From(1, 10, 2, 3)
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x < 10 })(seq))
|
||||
assert.Equal(t, []int{10, 2, 3}, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSkipWhileEmpty tests SkipWhile with empty sequences
|
||||
func TestSkipWhileEmpty(t *testing.T) {
|
||||
t.Run("returns empty from empty sequence", func(t *testing.T) {
|
||||
seq := Empty[int]()
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x > 0 })(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("returns empty when predicate always satisfied", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x < 10 })(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSkipWhileWithComplexTypes tests SkipWhile with complex data types
|
||||
func TestSkipWhileWithComplexTypes(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
t.Run("skips structs while condition met", func(t *testing.T) {
|
||||
seq := From(
|
||||
Person{"Alice", 25},
|
||||
Person{"Bob", 30},
|
||||
Person{"Charlie", 35},
|
||||
Person{"David", 28},
|
||||
)
|
||||
result := toSlice(SkipWhile(func(p Person) bool { return p.Age < 35 })(seq))
|
||||
expected := []Person{
|
||||
{"Charlie", 35},
|
||||
{"David", 28},
|
||||
}
|
||||
assert.Equal(t, expected, result)
|
||||
})
|
||||
|
||||
t.Run("skips pointers while condition met", func(t *testing.T) {
|
||||
p1 := &Person{"Alice", 25}
|
||||
p2 := &Person{"Bob", 30}
|
||||
p3 := &Person{"Charlie", 35}
|
||||
p4 := &Person{"David", 28}
|
||||
seq := From(p1, p2, p3, p4)
|
||||
result := toSlice(SkipWhile(func(p *Person) bool { return p.Age < 35 })(seq))
|
||||
assert.Equal(t, []*Person{p3, p4}, result)
|
||||
})
|
||||
|
||||
t.Run("skips slices while condition met", func(t *testing.T) {
|
||||
seq := From([]int{1}, []int{1, 2}, []int{1, 2, 3}, []int{1})
|
||||
result := toSlice(SkipWhile(func(s []int) bool { return len(s) < 3 })(seq))
|
||||
expected := [][]int{{1, 2, 3}, {1}}
|
||||
assert.Equal(t, expected, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSkipWhileWithChainedOperations tests SkipWhile with other sequence operations
|
||||
func TestSkipWhileWithChainedOperations(t *testing.T) {
|
||||
t.Run("skipWhile after map", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
mapped := MonadMap(seq, N.Mul(2))
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x < 8 })(mapped))
|
||||
assert.Equal(t, []int{8, 10}, result)
|
||||
})
|
||||
|
||||
t.Run("skipWhile after filter", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
filtered := MonadFilter(seq, func(x int) bool { return x%2 == 0 })
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x < 6 })(filtered))
|
||||
assert.Equal(t, []int{6, 8, 10}, result)
|
||||
})
|
||||
|
||||
t.Run("map after skipWhile", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
skipped := SkipWhile(func(x int) bool { return x < 4 })(seq)
|
||||
result := toSlice(MonadMap(skipped, N.Mul(10)))
|
||||
assert.Equal(t, []int{40, 50}, result)
|
||||
})
|
||||
|
||||
t.Run("filter after skipWhile", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8)
|
||||
skipped := SkipWhile(func(x int) bool { return x < 4 })(seq)
|
||||
result := toSlice(MonadFilter(skipped, func(x int) bool { return x%2 == 0 }))
|
||||
assert.Equal(t, []int{4, 6, 8}, result)
|
||||
})
|
||||
|
||||
t.Run("skipWhile after chain", func(t *testing.T) {
|
||||
seq := From(1, 2, 3)
|
||||
chained := MonadChain(seq, func(x int) Seq[int] {
|
||||
return From(x, x*10)
|
||||
})
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x < 20 })(chained))
|
||||
assert.Equal(t, []int{20, 3, 30}, result)
|
||||
})
|
||||
|
||||
t.Run("skip after skipWhile", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
skipped1 := SkipWhile(func(x int) bool { return x < 4 })(seq)
|
||||
skipped2 := Skip[int](2)(skipped1)
|
||||
result := toSlice(skipped2)
|
||||
assert.Equal(t, []int{6, 7, 8, 9, 10}, result)
|
||||
})
|
||||
|
||||
t.Run("skipWhile after skip", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
skipped := Skip[int](3)(seq)
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x < 7 })(skipped))
|
||||
assert.Equal(t, []int{7, 8, 9, 10}, result)
|
||||
})
|
||||
|
||||
t.Run("takeWhile after skipWhile", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
skipped := SkipWhile(func(x int) bool { return x < 4 })(seq)
|
||||
taken := TakeWhile(func(x int) bool { return x < 8 })(skipped)
|
||||
result := toSlice(taken)
|
||||
assert.Equal(t, []int{4, 5, 6, 7}, result)
|
||||
})
|
||||
|
||||
t.Run("skipWhile after takeWhile", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
taken := TakeWhile(func(x int) bool { return x < 8 })(seq)
|
||||
skipped := SkipWhile(func(x int) bool { return x < 4 })(taken)
|
||||
result := toSlice(skipped)
|
||||
assert.Equal(t, []int{4, 5, 6, 7}, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSkipWhileWithReplicate tests SkipWhile with Replicate
|
||||
func TestSkipWhileWithReplicate(t *testing.T) {
|
||||
t.Run("skips all from replicated sequence", func(t *testing.T) {
|
||||
seq := Replicate(10, 5)
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x == 5 })(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("skips none when predicate fails on replicate", func(t *testing.T) {
|
||||
seq := Replicate(5, 10)
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x < 10 })(seq))
|
||||
assert.Equal(t, []int{10, 10, 10, 10, 10}, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSkipWhileWithMakeBy tests SkipWhile with MakeBy
|
||||
func TestSkipWhileWithMakeBy(t *testing.T) {
|
||||
t.Run("skips from generated sequence", func(t *testing.T) {
|
||||
seq := MakeBy(10, func(i int) int { return i * i })
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x < 25 })(seq))
|
||||
assert.Equal(t, []int{25, 36, 49, 64, 81}, result)
|
||||
})
|
||||
|
||||
t.Run("skips all from generated sequence", func(t *testing.T) {
|
||||
seq := MakeBy(5, func(i int) int { return i + 1 })
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x < 100 })(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSkipWhileWithPrependAppend tests SkipWhile with Prepend and Append
|
||||
func TestSkipWhileWithPrependAppend(t *testing.T) {
|
||||
t.Run("skipWhile from prepended sequence", func(t *testing.T) {
|
||||
seq := From(2, 3, 4, 5)
|
||||
prepended := Prepend(1)(seq)
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x < 4 })(prepended))
|
||||
assert.Equal(t, []int{4, 5}, result)
|
||||
})
|
||||
|
||||
t.Run("skipWhile from appended sequence", func(t *testing.T) {
|
||||
seq := From(1, 2, 3)
|
||||
appended := Append(10)(seq)
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x < 10 })(appended))
|
||||
assert.Equal(t, []int{10}, result)
|
||||
})
|
||||
|
||||
t.Run("skipWhile includes appended element", func(t *testing.T) {
|
||||
seq := From(1, 2, 3)
|
||||
appended := Append(4)(seq)
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x < 3 })(appended))
|
||||
assert.Equal(t, []int{3, 4}, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSkipWhileWithFlatten tests SkipWhile with Flatten
|
||||
func TestSkipWhileWithFlatten(t *testing.T) {
|
||||
t.Run("skips from flattened sequence", func(t *testing.T) {
|
||||
nested := From(From(1, 2), From(3, 4), From(5, 6))
|
||||
flattened := Flatten(nested)
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x < 4 })(flattened))
|
||||
assert.Equal(t, []int{4, 5, 6}, result)
|
||||
})
|
||||
|
||||
t.Run("skips from flattened with empty inner sequences", func(t *testing.T) {
|
||||
nested := From(From(1, 2), Empty[int](), From(3, 4))
|
||||
flattened := Flatten(nested)
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x < 3 })(flattened))
|
||||
assert.Equal(t, []int{3, 4}, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSkipWhileDoesNotConsumeEntireSequence tests that SkipWhile is lazy
|
||||
func TestSkipWhileDoesNotConsumeEntireSequence(t *testing.T) {
|
||||
t.Run("only consumes needed elements", func(t *testing.T) {
|
||||
callCount := 0
|
||||
seq := MonadMap(From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), func(x int) int {
|
||||
callCount++
|
||||
return x * 2
|
||||
})
|
||||
|
||||
skipped := SkipWhile(func(x int) bool { return x < 8 })(seq)
|
||||
|
||||
result := []int{}
|
||||
for v := range skipped {
|
||||
result = append(result, v)
|
||||
}
|
||||
|
||||
assert.Equal(t, []int{8, 10, 12, 14, 16, 18, 20}, result)
|
||||
// Should process all elements since we iterate through all remaining
|
||||
assert.Equal(t, 10, callCount, "should process all elements")
|
||||
})
|
||||
|
||||
t.Run("stops early when consumer stops", func(t *testing.T) {
|
||||
callCount := 0
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
filtered := MonadFilter(seq, func(x int) bool {
|
||||
callCount++
|
||||
return x%2 == 0
|
||||
})
|
||||
|
||||
skipped := SkipWhile(func(x int) bool { return x < 6 })(filtered)
|
||||
|
||||
result := []int{}
|
||||
count := 0
|
||||
for v := range skipped {
|
||||
result = append(result, v)
|
||||
count++
|
||||
if count == 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(t, []int{6, 8}, result)
|
||||
// Should stop after getting 2 elements
|
||||
assert.LessOrEqual(t, callCount, 9, "should not consume all elements")
|
||||
})
|
||||
}
|
||||
|
||||
// TestSkipWhileEdgeCases tests edge cases
|
||||
func TestSkipWhileEdgeCases(t *testing.T) {
|
||||
t.Run("skipWhile with always false predicate", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
result := toSlice(SkipWhile(func(x int) bool { return false })(seq))
|
||||
assert.Equal(t, []int{1, 2, 3, 4, 5}, result)
|
||||
})
|
||||
|
||||
t.Run("skipWhile with always true predicate", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
result := toSlice(SkipWhile(func(x int) bool { return true })(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("skipWhile from single element that passes", func(t *testing.T) {
|
||||
seq := From(42)
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x > 0 })(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("skipWhile from single element that fails", func(t *testing.T) {
|
||||
seq := From(42)
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x < 0 })(seq))
|
||||
assert.Equal(t, []int{42}, result)
|
||||
})
|
||||
|
||||
t.Run("skipWhile with complex predicate", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
result := toSlice(SkipWhile(func(x int) bool {
|
||||
return x%2 == 1 || x < 5
|
||||
})(seq))
|
||||
assert.Equal(t, []int{6, 7, 8, 9, 10}, result)
|
||||
})
|
||||
|
||||
t.Run("skipWhile yields elements that satisfy predicate after first failure", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 10, 1, 2, 3)
|
||||
result := toSlice(SkipWhile(func(x int) bool { return x < 10 })(seq))
|
||||
assert.Equal(t, []int{10, 1, 2, 3}, result)
|
||||
})
|
||||
}
|
||||
|
||||
// Benchmark tests for SkipWhile
|
||||
func BenchmarkSkipWhile(b *testing.B) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
skipped := SkipWhile(func(x int) bool { return x < 6 })(seq)
|
||||
for range skipped {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSkipWhileLargeSequence(b *testing.B) {
|
||||
data := make([]int, 1000)
|
||||
for i := range data {
|
||||
data[i] = i
|
||||
}
|
||||
seq := From(data...)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
skipped := SkipWhile(func(x int) bool { return x < 100 })(seq)
|
||||
for range skipped {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSkipWhileWithMap(b *testing.B) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
mapped := MonadMap(seq, N.Mul(2))
|
||||
skipped := SkipWhile(func(x int) bool { return x < 12 })(mapped)
|
||||
for range skipped {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSkipWhileWithFilter(b *testing.B) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
filtered := MonadFilter(seq, func(x int) bool { return x%2 == 0 })
|
||||
skipped := SkipWhile(func(x int) bool { return x < 6 })(filtered)
|
||||
for range skipped {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Example tests for documentation
|
||||
func ExampleSkipWhile() {
|
||||
seq := From(1, 2, 3, 4, 5, 2, 1)
|
||||
skipped := SkipWhile(func(x int) bool { return x < 4 })(seq)
|
||||
|
||||
for v := range skipped {
|
||||
fmt.Printf("%d ", v)
|
||||
}
|
||||
// Output: 4 5 2 1
|
||||
}
|
||||
|
||||
func ExampleSkipWhile_allSatisfy() {
|
||||
seq := From(2, 4, 6, 8)
|
||||
skipped := SkipWhile(func(x int) bool { return x%2 == 0 })(seq)
|
||||
|
||||
count := 0
|
||||
for range skipped {
|
||||
count++
|
||||
}
|
||||
fmt.Printf("Count: %d\n", count)
|
||||
// Output: Count: 0
|
||||
}
|
||||
|
||||
func ExampleSkipWhile_firstFails() {
|
||||
seq := From(5, 1, 2, 3)
|
||||
skipped := SkipWhile(func(x int) bool { return x < 5 })(seq)
|
||||
|
||||
for v := range skipped {
|
||||
fmt.Printf("%d ", v)
|
||||
}
|
||||
// Output: 5 1 2 3
|
||||
}
|
||||
|
||||
func ExampleSkipWhile_withMap() {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
doubled := MonadMap(seq, N.Mul(2))
|
||||
skipped := SkipWhile(func(x int) bool { return x < 8 })(doubled)
|
||||
|
||||
for v := range skipped {
|
||||
fmt.Printf("%d ", v)
|
||||
}
|
||||
// Output: 8 10
|
||||
}
|
||||
|
||||
func ExampleSkipWhile_strings() {
|
||||
seq := From("a", "b", "c", "1", "d", "e")
|
||||
isLetter := func(s string) bool { return s >= "a" && s <= "z" }
|
||||
skipped := SkipWhile(isLetter)(seq)
|
||||
|
||||
for v := range skipped {
|
||||
fmt.Printf("%s ", v)
|
||||
}
|
||||
// Output: 1 d e
|
||||
}
|
||||
|
||||
// TestTakeWhile tests basic TakeWhile functionality
|
||||
func TestTakeWhile(t *testing.T) {
|
||||
t.Run("takes while predicate is true", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 2, 1)
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x < 4 })(seq))
|
||||
assert.Equal(t, []int{1, 2, 3}, result)
|
||||
})
|
||||
|
||||
t.Run("takes all when predicate always true", func(t *testing.T) {
|
||||
seq := From(2, 4, 6, 8)
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x%2 == 0 })(seq))
|
||||
assert.Equal(t, []int{2, 4, 6, 8}, result)
|
||||
})
|
||||
|
||||
t.Run("takes none when first element fails", func(t *testing.T) {
|
||||
seq := From(5, 1, 2, 3)
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x < 5 })(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("takes from string sequence", func(t *testing.T) {
|
||||
seq := From("a", "b", "c", "1", "d", "e")
|
||||
isLetter := func(s string) bool { return s >= "a" && s <= "z" }
|
||||
result := toSlice(TakeWhile(isLetter)(seq))
|
||||
assert.Equal(t, []string{"a", "b", "c"}, result)
|
||||
})
|
||||
|
||||
t.Run("takes single element", func(t *testing.T) {
|
||||
seq := From(1, 10, 2, 3)
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x < 10 })(seq))
|
||||
assert.Equal(t, []int{1}, result)
|
||||
})
|
||||
|
||||
t.Run("stops at first false predicate", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 1, 2, 3)
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x < 4 })(seq))
|
||||
assert.Equal(t, []int{1, 2, 3}, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestTakeWhileEmpty tests TakeWhile with empty sequences
|
||||
func TestTakeWhileEmpty(t *testing.T) {
|
||||
t.Run("returns empty from empty sequence", func(t *testing.T) {
|
||||
seq := Empty[int]()
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x > 0 })(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("returns empty when predicate never satisfied", func(t *testing.T) {
|
||||
seq := From(10, 20, 30)
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x < 5 })(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestTakeWhileWithComplexTypes tests TakeWhile with complex data types
|
||||
func TestTakeWhileWithComplexTypes(t *testing.T) {
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
|
||||
t.Run("takes structs while condition met", func(t *testing.T) {
|
||||
seq := From(
|
||||
Person{"Alice", 25},
|
||||
Person{"Bob", 30},
|
||||
Person{"Charlie", 35},
|
||||
Person{"David", 28},
|
||||
)
|
||||
result := toSlice(TakeWhile(func(p Person) bool { return p.Age < 35 })(seq))
|
||||
expected := []Person{
|
||||
{"Alice", 25},
|
||||
{"Bob", 30},
|
||||
}
|
||||
assert.Equal(t, expected, result)
|
||||
})
|
||||
|
||||
t.Run("takes pointers while condition met", func(t *testing.T) {
|
||||
p1 := &Person{"Alice", 25}
|
||||
p2 := &Person{"Bob", 30}
|
||||
p3 := &Person{"Charlie", 35}
|
||||
seq := From(p1, p2, p3)
|
||||
result := toSlice(TakeWhile(func(p *Person) bool { return p.Age < 35 })(seq))
|
||||
assert.Equal(t, []*Person{p1, p2}, result)
|
||||
})
|
||||
|
||||
t.Run("takes slices while condition met", func(t *testing.T) {
|
||||
seq := From([]int{1}, []int{1, 2}, []int{1, 2, 3}, []int{1})
|
||||
result := toSlice(TakeWhile(func(s []int) bool { return len(s) < 3 })(seq))
|
||||
expected := [][]int{{1}, {1, 2}}
|
||||
assert.Equal(t, expected, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestTakeWhileWithChainedOperations tests TakeWhile with other sequence operations
|
||||
func TestTakeWhileWithChainedOperations(t *testing.T) {
|
||||
t.Run("takeWhile after map", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
mapped := MonadMap(seq, N.Mul(2))
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x < 8 })(mapped))
|
||||
assert.Equal(t, []int{2, 4, 6}, result)
|
||||
})
|
||||
|
||||
t.Run("takeWhile after filter", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
filtered := MonadFilter(seq, func(x int) bool { return x%2 == 0 })
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x < 7 })(filtered))
|
||||
assert.Equal(t, []int{2, 4, 6}, result)
|
||||
})
|
||||
|
||||
t.Run("map after takeWhile", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
taken := TakeWhile(func(x int) bool { return x < 4 })(seq)
|
||||
result := toSlice(MonadMap(taken, N.Mul(10)))
|
||||
assert.Equal(t, []int{10, 20, 30}, result)
|
||||
})
|
||||
|
||||
t.Run("filter after takeWhile", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8)
|
||||
taken := TakeWhile(func(x int) bool { return x < 7 })(seq)
|
||||
result := toSlice(MonadFilter(taken, func(x int) bool { return x%2 == 0 }))
|
||||
assert.Equal(t, []int{2, 4, 6}, result)
|
||||
})
|
||||
|
||||
t.Run("takeWhile after chain", func(t *testing.T) {
|
||||
seq := From(1, 2, 3)
|
||||
chained := MonadChain(seq, func(x int) Seq[int] {
|
||||
return From(x, x*10)
|
||||
})
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x < 20 })(chained))
|
||||
assert.Equal(t, []int{1, 10, 2}, result)
|
||||
})
|
||||
|
||||
t.Run("take after takeWhile", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
taken1 := TakeWhile(func(x int) bool { return x < 8 })(seq)
|
||||
taken2 := Take[int](3)(taken1)
|
||||
result := toSlice(taken2)
|
||||
assert.Equal(t, []int{1, 2, 3}, result)
|
||||
})
|
||||
|
||||
t.Run("takeWhile after take", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
taken := Take[int](7)(seq)
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x < 5 })(taken))
|
||||
assert.Equal(t, []int{1, 2, 3, 4}, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestTakeWhileWithReplicate tests TakeWhile with Replicate
|
||||
func TestTakeWhileWithReplicate(t *testing.T) {
|
||||
t.Run("takes from replicated sequence", func(t *testing.T) {
|
||||
seq := Replicate(10, 5)
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x == 5 })(seq))
|
||||
assert.Equal(t, []int{5, 5, 5, 5, 5, 5, 5, 5, 5, 5}, result)
|
||||
})
|
||||
|
||||
t.Run("takes none when predicate fails on replicate", func(t *testing.T) {
|
||||
seq := Replicate(5, 10)
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x < 10 })(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestTakeWhileWithMakeBy tests TakeWhile with MakeBy
|
||||
func TestTakeWhileWithMakeBy(t *testing.T) {
|
||||
t.Run("takes from generated sequence", func(t *testing.T) {
|
||||
seq := MakeBy(10, func(i int) int { return i * i })
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x < 25 })(seq))
|
||||
assert.Equal(t, []int{0, 1, 4, 9, 16}, result)
|
||||
})
|
||||
|
||||
t.Run("takes all from generated sequence", func(t *testing.T) {
|
||||
seq := MakeBy(5, func(i int) int { return i + 1 })
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x < 100 })(seq))
|
||||
assert.Equal(t, []int{1, 2, 3, 4, 5}, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestTakeWhileWithPrependAppend tests TakeWhile with Prepend and Append
|
||||
func TestTakeWhileWithPrependAppend(t *testing.T) {
|
||||
t.Run("takeWhile from prepended sequence", func(t *testing.T) {
|
||||
seq := From(2, 3, 4, 5)
|
||||
prepended := Prepend(1)(seq)
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x < 4 })(prepended))
|
||||
assert.Equal(t, []int{1, 2, 3}, result)
|
||||
})
|
||||
|
||||
t.Run("takeWhile from appended sequence", func(t *testing.T) {
|
||||
seq := From(1, 2, 3)
|
||||
appended := Append(10)(seq)
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x < 10 })(appended))
|
||||
assert.Equal(t, []int{1, 2, 3}, result)
|
||||
})
|
||||
|
||||
t.Run("takeWhile includes appended element", func(t *testing.T) {
|
||||
seq := From(1, 2, 3)
|
||||
appended := Append(4)(seq)
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x < 5 })(appended))
|
||||
assert.Equal(t, []int{1, 2, 3, 4}, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestTakeWhileWithFlatten tests TakeWhile with Flatten
|
||||
func TestTakeWhileWithFlatten(t *testing.T) {
|
||||
t.Run("takes from flattened sequence", func(t *testing.T) {
|
||||
nested := From(From(1, 2), From(3, 4), From(5, 6))
|
||||
flattened := Flatten(nested)
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x < 5 })(flattened))
|
||||
assert.Equal(t, []int{1, 2, 3, 4}, result)
|
||||
})
|
||||
|
||||
t.Run("takes from flattened with empty inner sequences", func(t *testing.T) {
|
||||
nested := From(From(1, 2), Empty[int](), From(3, 4))
|
||||
flattened := Flatten(nested)
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x < 4 })(flattened))
|
||||
assert.Equal(t, []int{1, 2, 3}, result)
|
||||
})
|
||||
}
|
||||
|
||||
// TestTakeWhileDoesNotConsumeEntireSequence tests that TakeWhile is lazy
|
||||
func TestTakeWhileDoesNotConsumeEntireSequence(t *testing.T) {
|
||||
t.Run("only consumes needed elements", func(t *testing.T) {
|
||||
callCount := 0
|
||||
seq := MonadMap(From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), func(x int) int {
|
||||
callCount++
|
||||
return x * 2
|
||||
})
|
||||
|
||||
taken := TakeWhile(func(x int) bool { return x < 8 })(seq)
|
||||
|
||||
result := []int{}
|
||||
for v := range taken {
|
||||
result = append(result, v)
|
||||
}
|
||||
|
||||
assert.Equal(t, []int{2, 4, 6}, result)
|
||||
// Should stop after finding element that fails predicate
|
||||
assert.LessOrEqual(t, callCount, 5, "should not consume significantly more than needed")
|
||||
assert.GreaterOrEqual(t, callCount, 4, "should consume at least enough to find failure")
|
||||
})
|
||||
|
||||
t.Run("stops early with filter", func(t *testing.T) {
|
||||
callCount := 0
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
filtered := MonadFilter(seq, func(x int) bool {
|
||||
callCount++
|
||||
return x%2 == 0
|
||||
})
|
||||
|
||||
taken := TakeWhile(func(x int) bool { return x < 7 })(filtered)
|
||||
|
||||
result := []int{}
|
||||
for v := range taken {
|
||||
result = append(result, v)
|
||||
}
|
||||
|
||||
assert.Equal(t, []int{2, 4, 6}, result)
|
||||
// Should stop after finding even number >= 7
|
||||
assert.LessOrEqual(t, callCount, 9, "should not consume significantly more than needed")
|
||||
assert.GreaterOrEqual(t, callCount, 7, "should consume at least enough to find 8")
|
||||
})
|
||||
}
|
||||
|
||||
// TestTakeWhileEdgeCases tests edge cases
|
||||
func TestTakeWhileEdgeCases(t *testing.T) {
|
||||
t.Run("takeWhile with always false predicate", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
result := toSlice(TakeWhile(func(x int) bool { return false })(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("takeWhile with always true predicate", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
result := toSlice(TakeWhile(func(x int) bool { return true })(seq))
|
||||
assert.Equal(t, []int{1, 2, 3, 4, 5}, result)
|
||||
})
|
||||
|
||||
t.Run("takeWhile from single element that passes", func(t *testing.T) {
|
||||
seq := From(42)
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x > 0 })(seq))
|
||||
assert.Equal(t, []int{42}, result)
|
||||
})
|
||||
|
||||
t.Run("takeWhile from single element that fails", func(t *testing.T) {
|
||||
seq := From(42)
|
||||
result := toSlice(TakeWhile(func(x int) bool { return x < 0 })(seq))
|
||||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("takeWhile with complex predicate", func(t *testing.T) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
result := toSlice(TakeWhile(func(x int) bool {
|
||||
return x%2 == 1 || x < 5
|
||||
})(seq))
|
||||
assert.Equal(t, []int{1, 2, 3, 4, 5}, result)
|
||||
})
|
||||
}
|
||||
|
||||
// Benchmark tests for TakeWhile
|
||||
func BenchmarkTakeWhile(b *testing.B) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
taken := TakeWhile(func(x int) bool { return x < 6 })(seq)
|
||||
for range taken {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTakeWhileLargeSequence(b *testing.B) {
|
||||
data := make([]int, 1000)
|
||||
for i := range data {
|
||||
data[i] = i
|
||||
}
|
||||
seq := From(data...)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
taken := TakeWhile(func(x int) bool { return x < 100 })(seq)
|
||||
for range taken {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTakeWhileWithMap(b *testing.B) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
mapped := MonadMap(seq, N.Mul(2))
|
||||
taken := TakeWhile(func(x int) bool { return x < 12 })(mapped)
|
||||
for range taken {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTakeWhileWithFilter(b *testing.B) {
|
||||
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
filtered := MonadFilter(seq, func(x int) bool { return x%2 == 0 })
|
||||
taken := TakeWhile(func(x int) bool { return x < 7 })(filtered)
|
||||
for range taken {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Example tests for documentation
|
||||
func ExampleTakeWhile() {
|
||||
seq := From(1, 2, 3, 4, 5, 2, 1)
|
||||
taken := TakeWhile(func(x int) bool { return x < 4 })(seq)
|
||||
|
||||
for v := range taken {
|
||||
fmt.Printf("%d ", v)
|
||||
}
|
||||
// Output: 1 2 3
|
||||
}
|
||||
|
||||
func ExampleTakeWhile_allSatisfy() {
|
||||
seq := From(2, 4, 6, 8)
|
||||
taken := TakeWhile(func(x int) bool { return x%2 == 0 })(seq)
|
||||
|
||||
for v := range taken {
|
||||
fmt.Printf("%d ", v)
|
||||
}
|
||||
// Output: 2 4 6 8
|
||||
}
|
||||
|
||||
func ExampleTakeWhile_firstFails() {
|
||||
seq := From(5, 1, 2, 3)
|
||||
taken := TakeWhile(func(x int) bool { return x < 5 })(seq)
|
||||
|
||||
count := 0
|
||||
for range taken {
|
||||
count++
|
||||
}
|
||||
fmt.Printf("Count: %d\n", count)
|
||||
// Output: Count: 0
|
||||
}
|
||||
|
||||
func ExampleTakeWhile_withMap() {
|
||||
seq := From(1, 2, 3, 4, 5)
|
||||
doubled := MonadMap(seq, N.Mul(2))
|
||||
taken := TakeWhile(func(x int) bool { return x < 8 })(doubled)
|
||||
|
||||
for v := range taken {
|
||||
fmt.Printf("%d ", v)
|
||||
}
|
||||
// Output: 2 4 6
|
||||
}
|
||||
|
||||
func ExampleTakeWhile_strings() {
|
||||
seq := From("a", "b", "c", "1", "d", "e")
|
||||
isLetter := func(s string) bool { return s >= "a" && s <= "z" }
|
||||
taken := TakeWhile(isLetter)(seq)
|
||||
|
||||
for v := range taken {
|
||||
fmt.Printf("%s ", v)
|
||||
}
|
||||
// Output: a b c
|
||||
}
|
||||
|
||||
@@ -32,6 +32,13 @@ import (
|
||||
// the number of unique keys encountered. The operation is lazy - elements are processed
|
||||
// and filtered as they are consumed.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: --1--2--3--2--4--1--5-->
|
||||
// Uniq(identity)
|
||||
// Output: --1--2--3-----4-----5-->
|
||||
// (first occurrence only)
|
||||
//
|
||||
// RxJS Equivalent: [distinct] - https://rxjs.dev/api/operators/distinct
|
||||
//
|
||||
// Type Parameters:
|
||||
@@ -119,6 +126,13 @@ func Uniq[A any, K comparable](f func(A) K) Operator[A, A] {
|
||||
// The operation maintains a map of seen elements internally, so memory usage grows with
|
||||
// the number of unique elements. Only the first occurrence of each unique element is kept.
|
||||
//
|
||||
// Marble Diagram:
|
||||
//
|
||||
// Input: --1--2--3--2--4--1--5-->
|
||||
// StrictUniq
|
||||
// Output: --1--2--3-----4-----5-->
|
||||
// (first occurrence only)
|
||||
//
|
||||
// RxJS Equivalent: [distinct] - https://rxjs.dev/api/operators/distinct
|
||||
//
|
||||
// Type Parameters:
|
||||
|
||||
@@ -522,3 +522,199 @@ func MarshalJSON[T any](
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// FromNonZero creates a bidirectional codec for non-zero values of comparable types.
|
||||
// This codec validates that values are not equal to their zero value (e.g., 0 for int,
|
||||
// "" for string, false for bool, nil for pointers).
|
||||
//
|
||||
// The codec uses a refinement (prism) that:
|
||||
// - Decodes: Validates that the input is not the zero value of type T
|
||||
// - Encodes: Returns the value unchanged (identity function)
|
||||
// - Validates: Ensures the value is non-zero/non-default
|
||||
//
|
||||
// This is useful for enforcing that required fields have meaningful values rather than
|
||||
// their default zero values, which often represent "not set" or "missing" states.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - T: A comparable type (must support == and != operators)
|
||||
//
|
||||
// Returns:
|
||||
// - A Type[T, T, T] codec that validates non-zero values
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // Create a codec for non-zero integers
|
||||
// nonZeroInt := FromNonZero[int]()
|
||||
//
|
||||
// // Decode non-zero value succeeds
|
||||
// result := nonZeroInt.Decode(42)
|
||||
// // result is Right(42)
|
||||
//
|
||||
// // Decode zero value fails
|
||||
// result := nonZeroInt.Decode(0)
|
||||
// // result is Left(ValidationError{...})
|
||||
//
|
||||
// // Encode is identity
|
||||
// encoded := nonZeroInt.Encode(42)
|
||||
// // encoded is 42
|
||||
//
|
||||
// // Works with strings
|
||||
// nonEmptyStr := FromNonZero[string]()
|
||||
// result := nonEmptyStr.Decode("hello") // Right("hello")
|
||||
// result = nonEmptyStr.Decode("") // Left(ValidationError{...})
|
||||
//
|
||||
// // Works with pointers
|
||||
// nonNilPtr := FromNonZero[*int]()
|
||||
// value := 42
|
||||
// result := nonNilPtr.Decode(&value) // Right(&value)
|
||||
// result = nonNilPtr.Decode(nil) // Left(ValidationError{...})
|
||||
//
|
||||
// Common use cases:
|
||||
// - Validating required numeric fields are not zero
|
||||
// - Ensuring string fields are not empty
|
||||
// - Checking pointers are not nil
|
||||
// - Validating boolean flags are explicitly set to true
|
||||
// - Composing with other codecs for multi-stage validation
|
||||
//
|
||||
// See Also:
|
||||
// - NonEmptyString: Specialized version for strings with clearer intent
|
||||
// - FromRefinement: General function for creating codecs from prisms
|
||||
func FromNonZero[T comparable]() Type[T, T, T] {
|
||||
return FromRefinement(prism.FromNonZero[T]())
|
||||
}
|
||||
|
||||
// NonEmptyString creates a bidirectional codec for non-empty strings.
|
||||
// This codec validates that string values are not empty, providing a type-safe
|
||||
// way to work with strings that must contain at least one character.
|
||||
//
|
||||
// This is a specialized version of FromNonZero[string]() that makes the intent
|
||||
// clearer when working specifically with strings that must not be empty.
|
||||
//
|
||||
// The codec:
|
||||
// - Decodes: Validates that the input string is not empty ("")
|
||||
// - Encodes: Returns the string unchanged (identity function)
|
||||
// - Validates: Ensures the string has length > 0
|
||||
//
|
||||
// Note: This codec only checks for empty strings, not whitespace-only strings.
|
||||
// A string containing only spaces, tabs, or newlines will pass validation.
|
||||
//
|
||||
// Returns:
|
||||
// - A Type[string, string, string] codec that validates non-empty strings
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// nonEmpty := NonEmptyString()
|
||||
//
|
||||
// // Decode non-empty string succeeds
|
||||
// result := nonEmpty.Decode("hello")
|
||||
// // result is Right("hello")
|
||||
//
|
||||
// // Decode empty string fails
|
||||
// result := nonEmpty.Decode("")
|
||||
// // result is Left(ValidationError{...})
|
||||
//
|
||||
// // Whitespace-only strings pass validation
|
||||
// result := nonEmpty.Decode(" ")
|
||||
// // result is Right(" ")
|
||||
//
|
||||
// // Encode is identity
|
||||
// encoded := nonEmpty.Encode("world")
|
||||
// // encoded is "world"
|
||||
//
|
||||
// // Compose with other codecs for validation pipelines
|
||||
// intFromNonEmptyString := Pipe(IntFromString())(nonEmpty)
|
||||
// result := intFromNonEmptyString.Decode("42") // Right(42)
|
||||
// result = intFromNonEmptyString.Decode("") // Left(ValidationError{...})
|
||||
// result = intFromNonEmptyString.Decode("abc") // Left(ValidationError{...})
|
||||
//
|
||||
// Common use cases:
|
||||
// - Validating required string fields (usernames, names, IDs)
|
||||
// - Ensuring configuration values are provided
|
||||
// - Validating user input before processing
|
||||
// - Composing with parsing codecs to validate before parsing
|
||||
// - Building validation pipelines for string data
|
||||
//
|
||||
// See Also:
|
||||
// - FromNonZero: General version for any comparable type
|
||||
// - String: Basic string codec without validation
|
||||
// - IntFromString: Codec for parsing integers from strings
|
||||
func NonEmptyString() Type[string, string, string] {
|
||||
return F.Pipe1(
|
||||
FromRefinement(prism.NonEmptyString()),
|
||||
WithName[string, string, string]("NonEmptyString"),
|
||||
)
|
||||
}
|
||||
|
||||
// WithName creates an endomorphism that renames a codec without changing its behavior.
|
||||
// This function returns a higher-order function that takes a codec and returns a new codec
|
||||
// with the specified name, while preserving all validation, encoding, and type-checking logic.
|
||||
//
|
||||
// This is useful for:
|
||||
// - Providing more descriptive names for composed codecs
|
||||
// - Creating domain-specific codec names for better error messages
|
||||
// - Documenting the purpose of complex codec pipelines
|
||||
// - Improving debugging and logging output
|
||||
//
|
||||
// The renamed codec maintains the same:
|
||||
// - Type checking behavior (Is function)
|
||||
// - Validation logic (Validate function)
|
||||
// - Encoding behavior (Encode function)
|
||||
//
|
||||
// Only the name returned by the Name() method changes.
|
||||
//
|
||||
// Type Parameters:
|
||||
// - A: The target type (what we decode to and encode from)
|
||||
// - O: The output type (what we encode to)
|
||||
// - I: The input type (what we decode from)
|
||||
//
|
||||
// Parameters:
|
||||
// - name: The new name for the codec
|
||||
//
|
||||
// Returns:
|
||||
// - An Endomorphism[Type[A, O, I]] that renames the codec
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// // Create a codec with a generic name
|
||||
// positiveInt := Pipe[int, int, string, int](
|
||||
// FromRefinement(prism.FromPredicate(func(n int) bool { return n > 0 })),
|
||||
// )(IntFromString())
|
||||
// // positiveInt.Name() returns something like "Pipe(FromRefinement(...), IntFromString)"
|
||||
//
|
||||
// // Rename it for clarity
|
||||
// namedCodec := WithName[int, string, string]("PositiveIntFromString")(positiveInt)
|
||||
// // namedCodec.Name() returns "PositiveIntFromString"
|
||||
//
|
||||
// // Use in a pipeline with F.Pipe
|
||||
// userAgeCodec := F.Pipe1(
|
||||
// IntFromString(),
|
||||
// WithName[int, string, string]("UserAge"),
|
||||
// )
|
||||
//
|
||||
// // Validation errors will show the custom name
|
||||
// result := userAgeCodec.Decode("invalid")
|
||||
// // Error context will reference "UserAge" instead of "IntFromString"
|
||||
//
|
||||
// Common use cases:
|
||||
// - Naming composed codecs for better error messages
|
||||
// - Creating domain-specific codec names (e.g., "EmailAddress", "PhoneNumber")
|
||||
// - Documenting complex validation pipelines
|
||||
// - Improving debugging output in logs
|
||||
// - Making codec composition more readable
|
||||
//
|
||||
// Note: This function creates a new codec instance with the same behavior but a different
|
||||
// name. The original codec is not modified.
|
||||
//
|
||||
// See Also:
|
||||
// - MakeType: For creating codecs with custom names from scratch
|
||||
// - Pipe: For composing codecs (which generates automatic names)
|
||||
func WithName[A, O, I any](name string) Endomorphism[Type[A, O, I]] {
|
||||
return func(codec Type[A, O, I]) Type[A, O, I] {
|
||||
return MakeType(
|
||||
name,
|
||||
codec.Is,
|
||||
codec.Validate,
|
||||
codec.Encode,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/IBM/fp-go/v2/either"
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
"github.com/IBM/fp-go/v2/optics/codec/validation"
|
||||
"github.com/IBM/fp-go/v2/optics/prism"
|
||||
"github.com/IBM/fp-go/v2/option"
|
||||
@@ -688,6 +689,596 @@ func TestBoolFromString_Integration(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FromNonZero
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestFromNonZero_Decode_Success(t *testing.T) {
|
||||
t.Run("int - decodes non-zero value", func(t *testing.T) {
|
||||
c := FromNonZero[int]()
|
||||
result := c.Decode(42)
|
||||
assert.Equal(t, validation.Success(42), result)
|
||||
})
|
||||
|
||||
t.Run("int - decodes negative value", func(t *testing.T) {
|
||||
c := FromNonZero[int]()
|
||||
result := c.Decode(-5)
|
||||
assert.Equal(t, validation.Success(-5), result)
|
||||
})
|
||||
|
||||
t.Run("string - decodes non-empty string", func(t *testing.T) {
|
||||
c := FromNonZero[string]()
|
||||
result := c.Decode("hello")
|
||||
assert.Equal(t, validation.Success("hello"), result)
|
||||
})
|
||||
|
||||
t.Run("string - decodes whitespace string", func(t *testing.T) {
|
||||
c := FromNonZero[string]()
|
||||
result := c.Decode(" ")
|
||||
assert.Equal(t, validation.Success(" "), result)
|
||||
})
|
||||
|
||||
t.Run("bool - decodes true", func(t *testing.T) {
|
||||
c := FromNonZero[bool]()
|
||||
result := c.Decode(true)
|
||||
assert.Equal(t, validation.Success(true), result)
|
||||
})
|
||||
|
||||
t.Run("float64 - decodes non-zero value", func(t *testing.T) {
|
||||
c := FromNonZero[float64]()
|
||||
result := c.Decode(3.14)
|
||||
assert.Equal(t, validation.Success(3.14), result)
|
||||
})
|
||||
|
||||
t.Run("float64 - decodes negative value", func(t *testing.T) {
|
||||
c := FromNonZero[float64]()
|
||||
result := c.Decode(-2.5)
|
||||
assert.Equal(t, validation.Success(-2.5), result)
|
||||
})
|
||||
|
||||
t.Run("pointer - decodes non-nil pointer", func(t *testing.T) {
|
||||
c := FromNonZero[*int]()
|
||||
value := 42
|
||||
result := c.Decode(&value)
|
||||
assert.True(t, either.IsRight(result))
|
||||
ptr := either.MonadFold(result, func(validation.Errors) *int { return nil }, func(p *int) *int { return p })
|
||||
require.NotNil(t, ptr)
|
||||
assert.Equal(t, 42, *ptr)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFromNonZero_Decode_Failure(t *testing.T) {
|
||||
t.Run("int - fails on zero", func(t *testing.T) {
|
||||
c := FromNonZero[int]()
|
||||
result := c.Decode(0)
|
||||
assert.True(t, either.IsLeft(result))
|
||||
})
|
||||
|
||||
t.Run("string - fails on empty string", func(t *testing.T) {
|
||||
c := FromNonZero[string]()
|
||||
result := c.Decode("")
|
||||
assert.True(t, either.IsLeft(result))
|
||||
})
|
||||
|
||||
t.Run("bool - fails on false", func(t *testing.T) {
|
||||
c := FromNonZero[bool]()
|
||||
result := c.Decode(false)
|
||||
assert.True(t, either.IsLeft(result))
|
||||
})
|
||||
|
||||
t.Run("float64 - fails on zero", func(t *testing.T) {
|
||||
c := FromNonZero[float64]()
|
||||
result := c.Decode(0.0)
|
||||
assert.True(t, either.IsLeft(result))
|
||||
})
|
||||
|
||||
t.Run("pointer - fails on nil", func(t *testing.T) {
|
||||
c := FromNonZero[*int]()
|
||||
result := c.Decode(nil)
|
||||
assert.True(t, either.IsLeft(result))
|
||||
})
|
||||
}
|
||||
|
||||
func TestFromNonZero_Encode(t *testing.T) {
|
||||
t.Run("int - encodes value unchanged", func(t *testing.T) {
|
||||
c := FromNonZero[int]()
|
||||
assert.Equal(t, 42, c.Encode(42))
|
||||
})
|
||||
|
||||
t.Run("string - encodes value unchanged", func(t *testing.T) {
|
||||
c := FromNonZero[string]()
|
||||
assert.Equal(t, "hello", c.Encode("hello"))
|
||||
})
|
||||
|
||||
t.Run("bool - encodes value unchanged", func(t *testing.T) {
|
||||
c := FromNonZero[bool]()
|
||||
assert.Equal(t, true, c.Encode(true))
|
||||
})
|
||||
|
||||
t.Run("float64 - encodes value unchanged", func(t *testing.T) {
|
||||
c := FromNonZero[float64]()
|
||||
assert.Equal(t, 3.14, c.Encode(3.14))
|
||||
})
|
||||
|
||||
t.Run("pointer - encodes value unchanged", func(t *testing.T) {
|
||||
c := FromNonZero[*int]()
|
||||
value := 42
|
||||
ptr := &value
|
||||
assert.Equal(t, ptr, c.Encode(ptr))
|
||||
})
|
||||
|
||||
t.Run("round-trip: decode then encode", func(t *testing.T) {
|
||||
c := FromNonZero[int]()
|
||||
original := 42
|
||||
result := c.Decode(original)
|
||||
require.True(t, either.IsRight(result))
|
||||
decoded := either.MonadFold(result, func(validation.Errors) int { return 0 }, func(n int) int { return n })
|
||||
assert.Equal(t, original, c.Encode(decoded))
|
||||
})
|
||||
}
|
||||
|
||||
func TestFromNonZero_Name(t *testing.T) {
|
||||
t.Run("int codec name", func(t *testing.T) {
|
||||
c := FromNonZero[int]()
|
||||
assert.Contains(t, c.Name(), "FromRefinement")
|
||||
assert.Contains(t, c.Name(), "PrismFromNonZero")
|
||||
})
|
||||
|
||||
t.Run("string codec name", func(t *testing.T) {
|
||||
c := FromNonZero[string]()
|
||||
assert.Contains(t, c.Name(), "FromRefinement")
|
||||
assert.Contains(t, c.Name(), "PrismFromNonZero")
|
||||
})
|
||||
}
|
||||
|
||||
func TestFromNonZero_Integration(t *testing.T) {
|
||||
t.Run("validates multiple non-zero integers", func(t *testing.T) {
|
||||
c := FromNonZero[int]()
|
||||
values := []int{1, -1, 42, -100, 999}
|
||||
for _, v := range values {
|
||||
result := c.Decode(v)
|
||||
require.True(t, either.IsRight(result), "expected success for %d", v)
|
||||
decoded := either.MonadFold(result, func(validation.Errors) int { return 0 }, func(n int) int { return n })
|
||||
assert.Equal(t, v, decoded)
|
||||
assert.Equal(t, v, c.Encode(decoded))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("rejects zero values", func(t *testing.T) {
|
||||
c := FromNonZero[int]()
|
||||
result := c.Decode(0)
|
||||
assert.True(t, either.IsLeft(result))
|
||||
})
|
||||
|
||||
t.Run("works with custom comparable types", func(t *testing.T) {
|
||||
type UserID string
|
||||
c := FromNonZero[UserID]()
|
||||
|
||||
result := c.Decode(UserID("user123"))
|
||||
assert.Equal(t, validation.Success(UserID("user123")), result)
|
||||
|
||||
result = c.Decode(UserID(""))
|
||||
assert.True(t, either.IsLeft(result))
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// NonEmptyString
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestNonEmptyString_Decode_Success(t *testing.T) {
|
||||
t.Run("decodes non-empty string", func(t *testing.T) {
|
||||
c := NonEmptyString()
|
||||
result := c.Decode("hello")
|
||||
assert.Equal(t, validation.Success("hello"), result)
|
||||
})
|
||||
|
||||
t.Run("decodes single character", func(t *testing.T) {
|
||||
c := NonEmptyString()
|
||||
result := c.Decode("a")
|
||||
assert.Equal(t, validation.Success("a"), result)
|
||||
})
|
||||
|
||||
t.Run("decodes whitespace string", func(t *testing.T) {
|
||||
c := NonEmptyString()
|
||||
result := c.Decode(" ")
|
||||
assert.Equal(t, validation.Success(" "), result)
|
||||
})
|
||||
|
||||
t.Run("decodes string with newlines", func(t *testing.T) {
|
||||
c := NonEmptyString()
|
||||
result := c.Decode("\n\t")
|
||||
assert.Equal(t, validation.Success("\n\t"), result)
|
||||
})
|
||||
|
||||
t.Run("decodes unicode string", func(t *testing.T) {
|
||||
c := NonEmptyString()
|
||||
result := c.Decode("你好")
|
||||
assert.Equal(t, validation.Success("你好"), result)
|
||||
})
|
||||
|
||||
t.Run("decodes emoji string", func(t *testing.T) {
|
||||
c := NonEmptyString()
|
||||
result := c.Decode("🎉")
|
||||
assert.Equal(t, validation.Success("🎉"), result)
|
||||
})
|
||||
|
||||
t.Run("decodes multiline string", func(t *testing.T) {
|
||||
c := NonEmptyString()
|
||||
multiline := "line1\nline2\nline3"
|
||||
result := c.Decode(multiline)
|
||||
assert.Equal(t, validation.Success(multiline), result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNonEmptyString_Decode_Failure(t *testing.T) {
|
||||
t.Run("fails on empty string", func(t *testing.T) {
|
||||
c := NonEmptyString()
|
||||
result := c.Decode("")
|
||||
assert.True(t, either.IsLeft(result))
|
||||
})
|
||||
|
||||
t.Run("error contains context", func(t *testing.T) {
|
||||
c := NonEmptyString()
|
||||
result := c.Decode("")
|
||||
require.True(t, either.IsLeft(result))
|
||||
errors := either.MonadFold(result, func(e validation.Errors) validation.Errors { return e }, func(string) validation.Errors { return nil })
|
||||
require.NotEmpty(t, errors)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNonEmptyString_Encode(t *testing.T) {
|
||||
t.Run("encodes string unchanged", func(t *testing.T) {
|
||||
c := NonEmptyString()
|
||||
assert.Equal(t, "hello", c.Encode("hello"))
|
||||
})
|
||||
|
||||
t.Run("encodes unicode string unchanged", func(t *testing.T) {
|
||||
c := NonEmptyString()
|
||||
assert.Equal(t, "你好", c.Encode("你好"))
|
||||
})
|
||||
|
||||
t.Run("encodes whitespace string unchanged", func(t *testing.T) {
|
||||
c := NonEmptyString()
|
||||
assert.Equal(t, " ", c.Encode(" "))
|
||||
})
|
||||
|
||||
t.Run("round-trip: decode then encode", func(t *testing.T) {
|
||||
c := NonEmptyString()
|
||||
original := "test string"
|
||||
result := c.Decode(original)
|
||||
require.True(t, either.IsRight(result))
|
||||
decoded := either.MonadFold(result, func(validation.Errors) string { return "" }, func(s string) string { return s })
|
||||
assert.Equal(t, original, c.Encode(decoded))
|
||||
})
|
||||
}
|
||||
|
||||
func TestNonEmptyString_Name(t *testing.T) {
|
||||
c := NonEmptyString()
|
||||
assert.Equal(t, c.Name(), "NonEmptyString")
|
||||
}
|
||||
|
||||
func TestNonEmptyString_Integration(t *testing.T) {
|
||||
t.Run("validates multiple non-empty strings", func(t *testing.T) {
|
||||
c := NonEmptyString()
|
||||
strings := []string{"a", "hello", "world", "test123", " spaces ", "🎉"}
|
||||
for _, s := range strings {
|
||||
result := c.Decode(s)
|
||||
require.True(t, either.IsRight(result), "expected success for %q", s)
|
||||
decoded := either.MonadFold(result, func(validation.Errors) string { return "" }, func(str string) string { return str })
|
||||
assert.Equal(t, s, decoded)
|
||||
assert.Equal(t, s, c.Encode(decoded))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("rejects empty string", func(t *testing.T) {
|
||||
c := NonEmptyString()
|
||||
result := c.Decode("")
|
||||
assert.True(t, either.IsLeft(result))
|
||||
})
|
||||
|
||||
t.Run("compose with IntFromString", func(t *testing.T) {
|
||||
// Create a codec that only parses non-empty strings to integers
|
||||
nonEmptyThenInt := Pipe[string, string](IntFromString())(NonEmptyString())
|
||||
|
||||
// Valid non-empty string with integer
|
||||
result := nonEmptyThenInt.Decode("42")
|
||||
assert.Equal(t, validation.Success(42), result)
|
||||
|
||||
// Empty string fails at NonEmptyString stage
|
||||
result = nonEmptyThenInt.Decode("")
|
||||
assert.True(t, either.IsLeft(result))
|
||||
|
||||
// Non-empty but invalid integer fails at IntFromString stage
|
||||
result = nonEmptyThenInt.Decode("abc")
|
||||
assert.True(t, either.IsLeft(result))
|
||||
})
|
||||
|
||||
t.Run("use in validation pipeline", func(t *testing.T) {
|
||||
c := NonEmptyString()
|
||||
|
||||
// Simulate validating user input
|
||||
inputs := []struct {
|
||||
value string
|
||||
expected bool
|
||||
}{
|
||||
{"john_doe", true},
|
||||
{"", false},
|
||||
{"a", true},
|
||||
{"user@example.com", true},
|
||||
}
|
||||
|
||||
for _, input := range inputs {
|
||||
result := c.Decode(input.value)
|
||||
if input.expected {
|
||||
assert.True(t, either.IsRight(result), "expected success for %q", input.value)
|
||||
} else {
|
||||
assert.True(t, either.IsLeft(result), "expected failure for %q", input.value)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// WithName
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestWithName_BasicFunctionality(t *testing.T) {
|
||||
t.Run("renames codec without changing behavior", func(t *testing.T) {
|
||||
original := IntFromString()
|
||||
renamed := WithName[int, string, string]("CustomIntCodec")(original)
|
||||
|
||||
// Name should be changed
|
||||
assert.Equal(t, "CustomIntCodec", renamed.Name())
|
||||
assert.NotEqual(t, original.Name(), renamed.Name())
|
||||
|
||||
// Behavior should be unchanged
|
||||
result := renamed.Decode("42")
|
||||
assert.Equal(t, validation.Success(42), result)
|
||||
|
||||
encoded := renamed.Encode(42)
|
||||
assert.Equal(t, "42", encoded)
|
||||
})
|
||||
|
||||
t.Run("preserves validation logic", func(t *testing.T) {
|
||||
original := IntFromString()
|
||||
renamed := WithName[int, string, string]("MyInt")(original)
|
||||
|
||||
// Valid input should succeed
|
||||
result := renamed.Decode("123")
|
||||
assert.True(t, either.IsRight(result))
|
||||
|
||||
// Invalid input should fail
|
||||
result = renamed.Decode("not a number")
|
||||
assert.True(t, either.IsLeft(result))
|
||||
})
|
||||
|
||||
t.Run("preserves encoding logic", func(t *testing.T) {
|
||||
original := BoolFromString()
|
||||
renamed := WithName[bool, string, string]("CustomBool")(original)
|
||||
|
||||
assert.Equal(t, "true", renamed.Encode(true))
|
||||
assert.Equal(t, "false", renamed.Encode(false))
|
||||
})
|
||||
}
|
||||
|
||||
func TestWithName_WithComposedCodecs(t *testing.T) {
|
||||
t.Run("renames composed codec", func(t *testing.T) {
|
||||
// Create a composed codec
|
||||
composed := Pipe[string, string](IntFromString())(NonEmptyString())
|
||||
|
||||
// Rename it
|
||||
renamed := WithName[int, string, string]("NonEmptyIntString")(composed)
|
||||
|
||||
assert.Equal(t, "NonEmptyIntString", renamed.Name())
|
||||
|
||||
// Behavior should be preserved
|
||||
result := renamed.Decode("42")
|
||||
assert.Equal(t, validation.Success(42), result)
|
||||
|
||||
// Empty string should fail
|
||||
result = renamed.Decode("")
|
||||
assert.True(t, either.IsLeft(result))
|
||||
|
||||
// Non-numeric should fail
|
||||
result = renamed.Decode("abc")
|
||||
assert.True(t, either.IsLeft(result))
|
||||
})
|
||||
|
||||
t.Run("works in pipeline with F.Pipe", func(t *testing.T) {
|
||||
codec := F.Pipe1(
|
||||
IntFromString(),
|
||||
WithName[int, string, string]("UserAge"),
|
||||
)
|
||||
|
||||
assert.Equal(t, "UserAge", codec.Name())
|
||||
|
||||
result := codec.Decode("25")
|
||||
assert.Equal(t, validation.Success(25), result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWithName_PreservesTypeChecking(t *testing.T) {
|
||||
t.Run("preserves Is function", func(t *testing.T) {
|
||||
original := String()
|
||||
renamed := WithName[string, string, any]("CustomString")(original)
|
||||
|
||||
// Should accept string
|
||||
result := renamed.Is("hello")
|
||||
assert.True(t, either.IsRight(result))
|
||||
|
||||
// Should reject non-string
|
||||
result = renamed.Is(42)
|
||||
assert.True(t, either.IsLeft(result))
|
||||
})
|
||||
|
||||
t.Run("preserves complex type checking", func(t *testing.T) {
|
||||
original := Array(Int())
|
||||
renamed := WithName[[]int, []int, any]("IntArray")(original)
|
||||
|
||||
// Should accept []int
|
||||
result := renamed.Is([]int{1, 2, 3})
|
||||
assert.True(t, either.IsRight(result))
|
||||
|
||||
// Should reject []string
|
||||
result = renamed.Is([]string{"a", "b"})
|
||||
assert.True(t, either.IsLeft(result))
|
||||
})
|
||||
}
|
||||
|
||||
func TestWithName_RoundTrip(t *testing.T) {
|
||||
t.Run("maintains round-trip property", func(t *testing.T) {
|
||||
original := Int64FromString()
|
||||
renamed := WithName[int64, string, string]("CustomInt64")(original)
|
||||
|
||||
testValues := []string{"0", "42", "-100", "9223372036854775807"}
|
||||
for _, input := range testValues {
|
||||
result := renamed.Decode(input)
|
||||
require.True(t, either.IsRight(result), "expected success for %s", input)
|
||||
|
||||
decoded := either.MonadFold(result, func(validation.Errors) int64 { return 0 }, func(n int64) int64 { return n })
|
||||
encoded := renamed.Encode(decoded)
|
||||
assert.Equal(t, input, encoded)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestWithName_ErrorMessages(t *testing.T) {
|
||||
t.Run("custom name appears in validation context", func(t *testing.T) {
|
||||
codec := WithName[int, string, string]("PositiveInteger")(IntFromString())
|
||||
|
||||
result := codec.Decode("not a number")
|
||||
require.True(t, either.IsLeft(result))
|
||||
|
||||
// The error context should reference the custom name
|
||||
errors := either.MonadFold(result, func(e validation.Errors) validation.Errors { return e }, func(int) validation.Errors { return nil })
|
||||
require.NotEmpty(t, errors)
|
||||
|
||||
// Check that at least one error references our custom name
|
||||
found := false
|
||||
for _, err := range errors {
|
||||
if len(err.Context) > 0 {
|
||||
for _, ctx := range err.Context {
|
||||
if ctx.Type == "PositiveInteger" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.True(t, found, "expected custom name 'PositiveInteger' in error context")
|
||||
})
|
||||
}
|
||||
|
||||
func TestWithName_MultipleRenames(t *testing.T) {
|
||||
t.Run("can rename multiple times", func(t *testing.T) {
|
||||
codec := IntFromString()
|
||||
|
||||
renamed1 := WithName[int, string, string]("FirstName")(codec)
|
||||
assert.Equal(t, "FirstName", renamed1.Name())
|
||||
|
||||
renamed2 := WithName[int, string, string]("SecondName")(renamed1)
|
||||
assert.Equal(t, "SecondName", renamed2.Name())
|
||||
|
||||
// Behavior should still work
|
||||
result := renamed2.Decode("42")
|
||||
assert.Equal(t, validation.Success(42), result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWithName_WithDifferentTypes(t *testing.T) {
|
||||
t.Run("works with string codec", func(t *testing.T) {
|
||||
codec := WithName[string, string, string]("Username")(NonEmptyString())
|
||||
assert.Equal(t, "Username", codec.Name())
|
||||
|
||||
result := codec.Decode("john_doe")
|
||||
assert.Equal(t, validation.Success("john_doe"), result)
|
||||
})
|
||||
|
||||
t.Run("works with bool codec", func(t *testing.T) {
|
||||
codec := WithName[bool, string, string]("IsActive")(BoolFromString())
|
||||
assert.Equal(t, "IsActive", codec.Name())
|
||||
|
||||
result := codec.Decode("true")
|
||||
assert.Equal(t, validation.Success(true), result)
|
||||
})
|
||||
|
||||
t.Run("works with URL codec", func(t *testing.T) {
|
||||
codec := WithName[*url.URL, string, string]("WebsiteURL")(URL())
|
||||
assert.Equal(t, "WebsiteURL", codec.Name())
|
||||
|
||||
result := codec.Decode("https://example.com")
|
||||
assert.True(t, either.IsRight(result))
|
||||
})
|
||||
|
||||
t.Run("works with array codec", func(t *testing.T) {
|
||||
codec := WithName[[]int, []int, any]("Numbers")(Array(Int()))
|
||||
assert.Equal(t, "Numbers", codec.Name())
|
||||
|
||||
result := codec.Decode([]int{1, 2, 3})
|
||||
assert.Equal(t, validation.Success([]int{1, 2, 3}), result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWithName_AsDecoderEncoder(t *testing.T) {
|
||||
t.Run("AsDecoder returns decoder interface", func(t *testing.T) {
|
||||
codec := WithName[int, string, string]("MyInt")(IntFromString())
|
||||
decoder := codec.AsDecoder()
|
||||
|
||||
result := decoder.Decode("42")
|
||||
assert.Equal(t, validation.Success(42), result)
|
||||
})
|
||||
|
||||
t.Run("AsEncoder returns encoder interface", func(t *testing.T) {
|
||||
codec := WithName[int, string, string]("MyInt")(IntFromString())
|
||||
encoder := codec.AsEncoder()
|
||||
|
||||
encoded := encoder.Encode(42)
|
||||
assert.Equal(t, "42", encoded)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWithName_Integration(t *testing.T) {
|
||||
t.Run("domain-specific codec names", func(t *testing.T) {
|
||||
// Create domain-specific codecs with meaningful names
|
||||
emailCodec := WithName[string, string, string]("EmailAddress")(NonEmptyString())
|
||||
phoneCodec := WithName[string, string, string]("PhoneNumber")(NonEmptyString())
|
||||
ageCodec := WithName[int, string, string]("Age")(IntFromString())
|
||||
|
||||
// Test email
|
||||
result := emailCodec.Decode("user@example.com")
|
||||
assert.True(t, either.IsRight(result))
|
||||
assert.Equal(t, "EmailAddress", emailCodec.Name())
|
||||
|
||||
// Test phone
|
||||
result = phoneCodec.Decode("+1234567890")
|
||||
assert.True(t, either.IsRight(result))
|
||||
assert.Equal(t, "PhoneNumber", phoneCodec.Name())
|
||||
|
||||
// Test age
|
||||
ageResult := ageCodec.Decode("25")
|
||||
assert.True(t, either.IsRight(ageResult))
|
||||
assert.Equal(t, "Age", ageCodec.Name())
|
||||
})
|
||||
|
||||
t.Run("naming complex validation pipelines", func(t *testing.T) {
|
||||
// Create a complex codec and give it a clear name
|
||||
positiveIntCodec := F.Pipe2(
|
||||
NonEmptyString(),
|
||||
Pipe[string, string](IntFromString()),
|
||||
WithName[int, string, string]("PositiveIntegerFromString"),
|
||||
)
|
||||
|
||||
assert.Equal(t, "PositiveIntegerFromString", positiveIntCodec.Name())
|
||||
|
||||
result := positiveIntCodec.Decode("42")
|
||||
assert.True(t, either.IsRight(result))
|
||||
|
||||
result = positiveIntCodec.Decode("")
|
||||
assert.True(t, either.IsLeft(result))
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// MarshalJSON
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -773,7 +1364,7 @@ func TestIntFromString_PipeComposition(t *testing.T) {
|
||||
func(n int) int { return n },
|
||||
"PositiveInt",
|
||||
)
|
||||
positiveIntCodec := Pipe[string, string, int, int](
|
||||
positiveIntCodec := Pipe[string, string](
|
||||
FromRefinement(positiveIntPrism),
|
||||
)(IntFromString())
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package generic
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"sort"
|
||||
|
||||
F "github.com/IBM/fp-go/v2/function"
|
||||
@@ -301,13 +302,8 @@ func unionLast[M ~map[K]V, K comparable, V any](left, right M) M {
|
||||
|
||||
result := make(M, lenLeft+lenRight)
|
||||
|
||||
for k, v := range left {
|
||||
result[k] = v
|
||||
}
|
||||
|
||||
for k, v := range right {
|
||||
result[k] = v
|
||||
}
|
||||
maps.Copy(result, left)
|
||||
maps.Copy(result, right)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user