1
0
mirror of https://github.com/IBM/fp-go.git synced 2026-03-30 14:58:18 +02:00

Compare commits

...

10 Commits

Author SHA1 Message Date
Dr. Carsten Leue
747a1794e5 fix: add more iter operators
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-03-30 10:04:20 +02:00
renovate[bot]
c754cacf1f fix(deps): update module github.com/urfave/cli/v3 to v3.8.0 (#159)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-25 20:40:07 +00:00
Dr. Carsten Leue
d357b32847 fix: add TapThunkK
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-03-23 18:43:49 +01:00
Dr. Carsten Leue
a3af003e74 fix: undo Pipe and Flow changes, did not have the desired effect
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-03-20 23:20:07 +01:00
Dr. Carsten Leue
c81235827b fix: try to change the way Pipe and Flow are structured
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-03-20 12:05:25 +01:00
Dr. Carsten Leue
f35430cf18 fix: introduce async iterators
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-03-18 10:19:03 +01:00
Dr. Carsten Leue
d3ffc71808 fix: add ModifyF
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-03-17 15:23:10 +01:00
Dr. Carsten Leue
62844b7030 fix: add Filter and FilterMap
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-03-15 23:33:08 +01:00
Dr. Carsten Leue
99a0ddd4b6 fix: implement filter and filtermap
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-03-15 23:18:14 +01:00
Dr. Carsten Leue
02acbae8f6 fix: add lenses for Hostname and Port
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-03-15 22:49:11 +01:00
27 changed files with 5488 additions and 624 deletions

View File

@@ -18,6 +18,10 @@ package readerioresult
import (
"context"
"github.com/IBM/fp-go/v2/array"
"github.com/IBM/fp-go/v2/internal/witherable"
"github.com/IBM/fp-go/v2/iterator/iter"
"github.com/IBM/fp-go/v2/option"
RIOR "github.com/IBM/fp-go/v2/readerioresult"
)
@@ -49,3 +53,43 @@ import (
func FilterOrElse[A any](pred Predicate[A], onFalse func(A) error) Operator[A, A] {
return RIOR.FilterOrElse[context.Context](pred, onFalse)
}
//go:inline
func Filter[HKTA, A any](
filter func(Predicate[A]) Endomorphism[HKTA],
) func(Predicate[A]) Operator[HKTA, HKTA] {
return witherable.Filter(
Map,
filter,
)
}
//go:inline
func FilterArray[A any](p Predicate[A]) Operator[[]A, []A] {
return Filter(array.Filter[A])(p)
}
//go:inline
func FilterIter[A any](p Predicate[A]) Operator[Seq[A], Seq[A]] {
return Filter(iter.Filter[A])(p)
}
//go:inline
func FilterMap[HKTA, HKTB, A, B any](
filter func(option.Kleisli[A, B]) Reader[HKTA, HKTB],
) func(option.Kleisli[A, B]) Operator[HKTA, HKTB] {
return witherable.FilterMap(
Map,
filter,
)
}
//go:inline
func FilterMapArray[A, B any](p option.Kleisli[A, B]) Operator[[]A, []B] {
return FilterMap(array.FilterMap[A, B])(p)
}
//go:inline
func FilterMapIter[A, B any](p option.Kleisli[A, B]) Operator[Seq[A], Seq[B]] {
return FilterMap(iter.FilterMap[A, B])(p)
}

View File

@@ -17,6 +17,7 @@ package readerioresult
import (
"context"
"iter"
"github.com/IBM/fp-go/v2/consumer"
"github.com/IBM/fp-go/v2/context/ioresult"
@@ -220,4 +221,10 @@ type (
// The first element is the CancelFunc that should be called to release resources.
// The second element is the new Context that was created.
ContextCancel = Pair[context.CancelFunc, context.Context]
// Seq is an iterator over sequences of individual values.
// When called as seq(yield), seq calls yield(v) for each value v in the sequence,
// stopping early if yield returns false.
// See the [iter] package documentation for more details.
Seq[A any] = iter.Seq[A]
)

View File

@@ -0,0 +1,48 @@
package readerreaderioresult
import (
"github.com/IBM/fp-go/v2/array"
"github.com/IBM/fp-go/v2/internal/witherable"
"github.com/IBM/fp-go/v2/iterator/iter"
"github.com/IBM/fp-go/v2/option"
)
//go:inline
func Filter[C, HKTA, A any](
filter func(Predicate[A]) Endomorphism[HKTA],
) func(Predicate[A]) Operator[C, HKTA, HKTA] {
return witherable.Filter(
Map[C],
filter,
)
}
//go:inline
func FilterArray[C, A any](p Predicate[A]) Operator[C, []A, []A] {
return Filter[C](array.Filter[A])(p)
}
//go:inline
func FilterIter[C, A any](p Predicate[A]) Operator[C, Seq[A], Seq[A]] {
return Filter[C](iter.Filter[A])(p)
}
//go:inline
func FilterMap[C, HKTA, HKTB, A, B any](
filter func(option.Kleisli[A, B]) Reader[HKTA, HKTB],
) func(option.Kleisli[A, B]) Operator[C, HKTA, HKTB] {
return witherable.FilterMap(
Map[C],
filter,
)
}
//go:inline
func FilterMapArray[C, A, B any](p option.Kleisli[A, B]) Operator[C, []A, []B] {
return FilterMap[C](array.FilterMap[A, B])(p)
}
//go:inline
func FilterMapIter[C, A, B any](p option.Kleisli[A, B]) Operator[C, Seq[A], Seq[B]] {
return FilterMap[C](iter.FilterMap[A, B])(p)
}

View File

@@ -834,7 +834,7 @@ func Flap[R, B, A any](a A) Operator[R, func(A) B, B] {
// This is the monadic version that takes the computation as the first parameter.
//
//go:inline
func MonadMapLeft[R, A any](fa ReaderReaderIOResult[R, A], f Endmorphism[error]) ReaderReaderIOResult[R, A] {
func MonadMapLeft[R, A any](fa ReaderReaderIOResult[R, A], f Endomorphism[error]) ReaderReaderIOResult[R, A] {
return RRIOE.MonadMapLeft(fa, f)
}
@@ -843,7 +843,7 @@ func MonadMapLeft[R, A any](fa ReaderReaderIOResult[R, A], f Endmorphism[error])
// This is the curried version that returns an operator.
//
//go:inline
func MapLeft[R, A any](f Endmorphism[error]) Operator[R, A, A] {
func MapLeft[R, A any](f Endomorphism[error]) Operator[R, A, A] {
return RRIOE.MapLeft[R, context.Context, A](f)
}

View File

@@ -24,6 +24,7 @@ import (
"github.com/IBM/fp-go/v2/io"
"github.com/IBM/fp-go/v2/ioeither"
"github.com/IBM/fp-go/v2/ioresult"
"github.com/IBM/fp-go/v2/iterator/iter"
"github.com/IBM/fp-go/v2/lazy"
"github.com/IBM/fp-go/v2/optics/lens"
"github.com/IBM/fp-go/v2/optics/traversal/result"
@@ -146,9 +147,15 @@ type (
// It's an alias for predicate.Predicate[A].
Predicate[A any] = predicate.Predicate[A]
// Endmorphism represents a function from type A to type A.
// Endomorphism represents a function from type A to type A.
// It's an alias for endomorphism.Endomorphism[A].
Endmorphism[A any] = endomorphism.Endomorphism[A]
Endomorphism[A any] = endomorphism.Endomorphism[A]
// Seq is an iterator over sequences of individual values.
// When called as seq(yield), seq calls yield(v) for each value v in the sequence,
// stopping early if yield returns false.
// See the [iter] package documentation for more details.
Seq[A any] = iter.Seq[A]
Void = function.Void
)

View File

@@ -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.

View File

@@ -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
})

296
v2/effect/filter.go Normal file
View File

@@ -0,0 +1,296 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package effect
import (
"github.com/IBM/fp-go/v2/context/readerreaderioresult"
"github.com/IBM/fp-go/v2/option"
)
// Filter lifts a filtering operation on a higher-kinded type into an Effect operator.
// This is a generic function that works with any filterable data structure by taking
// a filter function and returning an operator that can be used in effect chains.
//
// # Type Parameters
//
// - C: The context type required by the effect
// - HKTA: The higher-kinded type being filtered (e.g., []A, Seq[A])
// - A: The element type being filtered
//
// # Parameters
//
// - filter: A function that takes a predicate and returns an endomorphism on HKTA
//
// # Returns
//
// - func(Predicate[A]) Operator[C, HKTA, HKTA]: A function that takes a predicate
// and returns an operator that filters effects containing HKTA values
//
// # Example Usage
//
// import A "github.com/IBM/fp-go/v2/array"
//
// // Create a custom filter operator for arrays
// filterOp := Filter[MyContext](A.Filter[int])
// isEven := func(n int) bool { return n%2 == 0 }
//
// pipeline := F.Pipe2(
// Succeed[MyContext]([]int{1, 2, 3, 4, 5}),
// filterOp(isEven),
// Map[MyContext](func(arr []int) int { return len(arr) }),
// )
// // Result: Effect that produces 2 (count of even numbers)
//
// # See Also
//
// - FilterArray: Specialized version for array filtering
// - FilterIter: Specialized version for iterator filtering
// - FilterMap: For filtering and mapping simultaneously
//
//go:inline
func Filter[C, HKTA, A any](
filter func(Predicate[A]) Endomorphism[HKTA],
) func(Predicate[A]) Operator[C, HKTA, HKTA] {
return readerreaderioresult.Filter[C](filter)
}
// FilterArray creates an operator that filters array elements within an Effect based on a predicate.
// Elements that satisfy the predicate are kept, while others are removed.
// This is a specialized version of Filter for arrays.
//
// # Type Parameters
//
// - C: The context type required by the effect
// - A: The element type in the array
//
// # Parameters
//
// - p: A predicate function that tests each element
//
// # Returns
//
// - Operator[C, []A, []A]: An operator that filters array elements in an effect
//
// # Example Usage
//
// isPositive := func(n int) bool { return n > 0 }
// filterPositive := FilterArray[MyContext](isPositive)
//
// pipeline := F.Pipe1(
// Succeed[MyContext]([]int{-2, -1, 0, 1, 2, 3}),
// filterPositive,
// )
// // Result: Effect that produces []int{1, 2, 3}
//
// # See Also
//
// - Filter: Generic version for any filterable type
// - FilterIter: For filtering iterators
// - FilterMapArray: For filtering and mapping arrays simultaneously
//
//go:inline
func FilterArray[C, A any](p Predicate[A]) Operator[C, []A, []A] {
return readerreaderioresult.FilterArray[C](p)
}
// FilterIter creates an operator that filters iterator elements within an Effect based on a predicate.
// Elements that satisfy the predicate are kept in the resulting iterator, while others are removed.
// This is a specialized version of Filter for iterators (Seq).
//
// # Type Parameters
//
// - C: The context type required by the effect
// - A: The element type in the iterator
//
// # Parameters
//
// - p: A predicate function that tests each element
//
// # Returns
//
// - Operator[C, Seq[A], Seq[A]]: An operator that filters iterator elements in an effect
//
// # Example Usage
//
// isEven := func(n int) bool { return n%2 == 0 }
// filterEven := FilterIter[MyContext](isEven)
//
// pipeline := F.Pipe1(
// Succeed[MyContext](slices.Values([]int{1, 2, 3, 4, 5, 6})),
// filterEven,
// )
// // Result: Effect that produces an iterator over [2, 4, 6]
//
// # See Also
//
// - Filter: Generic version for any filterable type
// - FilterArray: For filtering arrays
// - FilterMapIter: For filtering and mapping iterators simultaneously
//
//go:inline
func FilterIter[C, A any](p Predicate[A]) Operator[C, Seq[A], Seq[A]] {
return readerreaderioresult.FilterIter[C](p)
}
// FilterMap lifts a filter-map operation on a higher-kinded type into an Effect operator.
// This combines filtering and mapping in a single operation: elements are transformed
// using a function that returns Option, and only Some values are kept in the result.
//
// # Type Parameters
//
// - C: The context type required by the effect
// - HKTA: The input higher-kinded type (e.g., []A, Seq[A])
// - HKTB: The output higher-kinded type (e.g., []B, Seq[B])
// - A: The input element type
// - B: The output element type
//
// # Parameters
//
// - filter: A function that takes an option.Kleisli and returns a transformation from HKTA to HKTB
//
// # Returns
//
// - func(option.Kleisli[A, B]) Operator[C, HKTA, HKTB]: A function that takes a Kleisli arrow
// and returns an operator that filter-maps effects
//
// # Example Usage
//
// import A "github.com/IBM/fp-go/v2/array"
// import O "github.com/IBM/fp-go/v2/option"
//
// // Parse and filter positive integers
// parsePositive := func(s string) O.Option[int] {
// var n int
// if _, err := fmt.Sscanf(s, "%d", &n); err == nil && n > 0 {
// return O.Some(n)
// }
// return O.None[int]()
// }
//
// filterMapOp := FilterMap[MyContext](A.FilterMap[string, int])
// pipeline := F.Pipe1(
// Succeed[MyContext]([]string{"1", "-2", "3", "invalid", "5"}),
// filterMapOp(parsePositive),
// )
// // Result: Effect that produces []int{1, 3, 5}
//
// # See Also
//
// - FilterMapArray: Specialized version for arrays
// - FilterMapIter: Specialized version for iterators
// - Filter: For filtering without transformation
//
//go:inline
func FilterMap[C, HKTA, HKTB, A, B any](
filter func(option.Kleisli[A, B]) Reader[HKTA, HKTB],
) func(option.Kleisli[A, B]) Operator[C, HKTA, HKTB] {
return readerreaderioresult.FilterMap[C](filter)
}
// FilterMapArray creates an operator that filters and maps array elements within an Effect.
// Each element is transformed using a function that returns Option[B]. Elements that
// produce Some(b) are kept in the result array, while None values are filtered out.
//
// # Type Parameters
//
// - C: The context type required by the effect
// - A: The input element type
// - B: The output element type
//
// # Parameters
//
// - p: A Kleisli arrow from A to Option[B] that transforms and filters elements
//
// # Returns
//
// - Operator[C, []A, []B]: An operator that filter-maps array elements in an effect
//
// # Example Usage
//
// import O "github.com/IBM/fp-go/v2/option"
//
// // Double even numbers, filter out odd numbers
// doubleEven := func(n int) O.Option[int] {
// if n%2 == 0 {
// return O.Some(n * 2)
// }
// return O.None[int]()
// }
//
// pipeline := F.Pipe1(
// Succeed[MyContext]([]int{1, 2, 3, 4, 5}),
// FilterMapArray[MyContext](doubleEven),
// )
// // Result: Effect that produces []int{4, 8}
//
// # See Also
//
// - FilterMap: Generic version for any filterable type
// - FilterMapIter: For filter-mapping iterators
// - FilterArray: For filtering without transformation
//
//go:inline
func FilterMapArray[C, A, B any](p option.Kleisli[A, B]) Operator[C, []A, []B] {
return readerreaderioresult.FilterMapArray[C](p)
}
// FilterMapIter creates an operator that filters and maps iterator elements within an Effect.
// Each element is transformed using a function that returns Option[B]. Elements that
// produce Some(b) are kept in the resulting iterator, while None values are filtered out.
//
// # Type Parameters
//
// - C: The context type required by the effect
// - A: The input element type
// - B: The output element type
//
// # Parameters
//
// - p: A Kleisli arrow from A to Option[B] that transforms and filters elements
//
// # Returns
//
// - Operator[C, Seq[A], Seq[B]]: An operator that filter-maps iterator elements in an effect
//
// # Example Usage
//
// import O "github.com/IBM/fp-go/v2/option"
//
// // Parse strings to integers, keeping only valid ones
// parseInt := func(s string) O.Option[int] {
// var n int
// if _, err := fmt.Sscanf(s, "%d", &n); err == nil {
// return O.Some(n)
// }
// return O.None[int]()
// }
//
// pipeline := F.Pipe1(
// Succeed[MyContext](slices.Values([]string{"1", "2", "invalid", "3"})),
// FilterMapIter[MyContext](parseInt),
// )
// // Result: Effect that produces an iterator over [1, 2, 3]
//
// # See Also
//
// - FilterMap: Generic version for any filterable type
// - FilterMapArray: For filter-mapping arrays
// - FilterIter: For filtering without transformation
//
//go:inline
func FilterMapIter[C, A, B any](p option.Kleisli[A, B]) Operator[C, Seq[A], Seq[B]] {
return readerreaderioresult.FilterMapIter[C](p)
}

653
v2/effect/filter_test.go Normal file
View File

@@ -0,0 +1,653 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package effect
import (
"errors"
"fmt"
"slices"
"testing"
A "github.com/IBM/fp-go/v2/array"
F "github.com/IBM/fp-go/v2/function"
N "github.com/IBM/fp-go/v2/number"
O "github.com/IBM/fp-go/v2/option"
"github.com/stretchr/testify/assert"
)
type FilterTestConfig struct {
MaxValue int
MinValue int
}
// Helper to collect iterator results from an effect
func collectSeqEffect[C, A any](eff Effect[C, Seq[A]], cfg C) []A {
result, err := runEffect(eff, cfg)
if err != nil {
return nil
}
return slices.Collect(result)
}
func TestFilterArray_Success(t *testing.T) {
t.Run("filters array keeping matching elements", func(t *testing.T) {
// Arrange
isPositive := N.MoreThan(0)
filterOp := FilterArray[FilterTestConfig](isPositive)
input := Succeed[FilterTestConfig]([]int{1, -2, 3, -4, 5})
// Act
result, err := runEffect(filterOp(input), FilterTestConfig{})
// Assert
assert.NoError(t, err)
assert.Equal(t, []int{1, 3, 5}, result)
})
t.Run("returns empty array when no elements match", func(t *testing.T) {
// Arrange
isNegative := N.LessThan(0)
filterOp := FilterArray[FilterTestConfig](isNegative)
input := Succeed[FilterTestConfig]([]int{1, 2, 3})
// Act
result, err := runEffect(filterOp(input), FilterTestConfig{})
// Assert
assert.NoError(t, err)
assert.Equal(t, []int{}, result)
})
t.Run("returns all elements when all match", func(t *testing.T) {
// Arrange
alwaysTrue := func(n int) bool { return true }
filterOp := FilterArray[FilterTestConfig](alwaysTrue)
input := Succeed[FilterTestConfig]([]int{1, 2, 3})
// Act
result, err := runEffect(filterOp(input), FilterTestConfig{})
// Assert
assert.NoError(t, err)
assert.Equal(t, []int{1, 2, 3}, result)
})
}
func TestFilterIter_Success(t *testing.T) {
t.Run("filters iterator keeping matching elements", func(t *testing.T) {
// Arrange
isEven := func(n int) bool { return n%2 == 0 }
filterOp := FilterIter[FilterTestConfig](isEven)
input := Succeed[FilterTestConfig](slices.Values([]int{1, 2, 3, 4, 5, 6}))
// Act
collected := collectSeqEffect(filterOp(input), FilterTestConfig{})
// Assert
assert.Equal(t, []int{2, 4, 6}, collected)
})
t.Run("returns empty iterator when no elements match", func(t *testing.T) {
// Arrange
isNegative := N.LessThan(0)
filterOp := FilterIter[FilterTestConfig](isNegative)
input := Succeed[FilterTestConfig](slices.Values([]int{1, 2, 3}))
// Act
collected := collectSeqEffect(filterOp(input), FilterTestConfig{})
// Assert
assert.Empty(t, collected)
})
}
func TestFilterArray_WithContext(t *testing.T) {
t.Run("uses context for filtering", func(t *testing.T) {
// Arrange
cfg := FilterTestConfig{MaxValue: 100, MinValue: 0}
inRange := func(n int) bool { return n >= cfg.MinValue && n <= cfg.MaxValue }
filterOp := FilterArray[FilterTestConfig](inRange)
input := Succeed[FilterTestConfig]([]int{-10, 50, 150, 75})
// Act
result, err := runEffect(filterOp(input), cfg)
// Assert
assert.NoError(t, err)
assert.Equal(t, []int{50, 75}, result)
})
}
func TestFilterArray_EdgeCases(t *testing.T) {
t.Run("handles empty array", func(t *testing.T) {
// Arrange
isPositive := N.MoreThan(0)
filterOp := FilterArray[FilterTestConfig](isPositive)
input := Succeed[FilterTestConfig]([]int{})
// Act
result, err := runEffect(filterOp(input), FilterTestConfig{})
// Assert
assert.NoError(t, err)
assert.Equal(t, []int{}, result)
})
t.Run("preserves error from input", func(t *testing.T) {
// Arrange
isPositive := N.MoreThan(0)
filterOp := FilterArray[FilterTestConfig](isPositive)
inputErr := errors.New("input error")
input := Fail[FilterTestConfig, []int](inputErr)
// Act
_, err := runEffect(filterOp(input), FilterTestConfig{})
// Assert
assert.Error(t, err)
assert.Equal(t, inputErr, err)
})
}
func TestFilterIter_EdgeCases(t *testing.T) {
t.Run("handles empty iterator", func(t *testing.T) {
// Arrange
isPositive := N.MoreThan(0)
filterOp := FilterIter[FilterTestConfig](isPositive)
input := Succeed[FilterTestConfig](slices.Values([]int{}))
// Act
collected := collectSeqEffect(filterOp(input), FilterTestConfig{})
// Assert
assert.Empty(t, collected)
})
t.Run("preserves error from input", func(t *testing.T) {
// Arrange
isPositive := N.MoreThan(0)
filterOp := FilterIter[FilterTestConfig](isPositive)
inputErr := errors.New("input error")
input := Fail[FilterTestConfig, Seq[int]](inputErr)
// Act
_, err := runEffect(filterOp(input), FilterTestConfig{})
// Assert
assert.Error(t, err)
assert.Equal(t, inputErr, err)
})
}
func TestFilter_GenericFilter(t *testing.T) {
t.Run("works with custom filter function", func(t *testing.T) {
// Arrange
customFilter := func(p Predicate[int]) Endomorphism[[]int] {
return A.Filter(p)
}
filterOp := Filter[FilterTestConfig](customFilter)
isEven := func(n int) bool { return n%2 == 0 }
input := Succeed[FilterTestConfig]([]int{1, 2, 3, 4, 5})
// Act
result, err := runEffect(filterOp(isEven)(input), FilterTestConfig{})
// Assert
assert.NoError(t, err)
assert.Equal(t, []int{2, 4}, result)
})
}
func TestFilterMapArray_Success(t *testing.T) {
t.Run("filters and maps array elements", func(t *testing.T) {
// Arrange
parsePositive := func(n int) O.Option[string] {
if n > 0 {
return O.Some(fmt.Sprintf("positive:%d", n))
}
return O.None[string]()
}
filterMapOp := FilterMapArray[FilterTestConfig](parsePositive)
input := Succeed[FilterTestConfig]([]int{-1, 2, -3, 4, 5})
// Act
result, err := runEffect(filterMapOp(input), FilterTestConfig{})
// Assert
assert.NoError(t, err)
assert.Equal(t, []string{"positive:2", "positive:4", "positive:5"}, result)
})
t.Run("returns empty when no elements match", func(t *testing.T) {
// Arrange
neverMatch := func(n int) O.Option[int] {
return O.None[int]()
}
filterMapOp := FilterMapArray[FilterTestConfig](neverMatch)
input := Succeed[FilterTestConfig]([]int{1, 2, 3})
// Act
result, err := runEffect(filterMapOp(input), FilterTestConfig{})
// Assert
assert.NoError(t, err)
assert.Equal(t, []int{}, result)
})
t.Run("maps all elements when all match", func(t *testing.T) {
// Arrange
double := func(n int) O.Option[int] {
return O.Some(n * 2)
}
filterMapOp := FilterMapArray[FilterTestConfig](double)
input := Succeed[FilterTestConfig]([]int{1, 2, 3})
// Act
result, err := runEffect(filterMapOp(input), FilterTestConfig{})
// Assert
assert.NoError(t, err)
assert.Equal(t, []int{2, 4, 6}, result)
})
}
func TestFilterMapIter_Success(t *testing.T) {
t.Run("filters and maps iterator elements", func(t *testing.T) {
// Arrange
doubleEven := func(n int) O.Option[int] {
if n%2 == 0 {
return O.Some(n * 2)
}
return O.None[int]()
}
filterMapOp := FilterMapIter[FilterTestConfig](doubleEven)
input := Succeed[FilterTestConfig](slices.Values([]int{1, 2, 3, 4, 5}))
// Act
collected := collectSeqEffect(filterMapOp(input), FilterTestConfig{})
// Assert
assert.Equal(t, []int{4, 8}, collected)
})
}
func TestFilterMapArray_TypeConversion(t *testing.T) {
t.Run("converts int to string", func(t *testing.T) {
// Arrange
intToString := func(n int) O.Option[string] {
if n > 0 {
return O.Some(fmt.Sprintf("%d", n))
}
return O.None[string]()
}
filterMapOp := FilterMapArray[FilterTestConfig](intToString)
input := Succeed[FilterTestConfig]([]int{-1, 2, -3, 4})
// Act
result, err := runEffect(filterMapOp(input), FilterTestConfig{})
// Assert
assert.NoError(t, err)
assert.Equal(t, []string{"2", "4"}, result)
})
t.Run("converts string to int", func(t *testing.T) {
// Arrange
parseEven := func(s string) O.Option[int] {
var n int
if _, err := fmt.Sscanf(s, "%d", &n); err == nil && n%2 == 0 {
return O.Some(n)
}
return O.None[int]()
}
filterMapOp := FilterMapArray[FilterTestConfig](parseEven)
input := Succeed[FilterTestConfig]([]string{"1", "2", "3", "4", "invalid"})
// Act
result, err := runEffect(filterMapOp(input), FilterTestConfig{})
// Assert
assert.NoError(t, err)
assert.Equal(t, []int{2, 4}, result)
})
}
func TestFilterMapArray_EdgeCases(t *testing.T) {
t.Run("handles empty array", func(t *testing.T) {
// Arrange
double := func(n int) O.Option[int] {
return O.Some(n * 2)
}
filterMapOp := FilterMapArray[FilterTestConfig](double)
input := Succeed[FilterTestConfig]([]int{})
// Act
result, err := runEffect(filterMapOp(input), FilterTestConfig{})
// Assert
assert.NoError(t, err)
assert.Equal(t, []int{}, result)
})
t.Run("preserves error from input", func(t *testing.T) {
// Arrange
double := func(n int) O.Option[int] {
return O.Some(n * 2)
}
filterMapOp := FilterMapArray[FilterTestConfig](double)
inputErr := errors.New("input error")
input := Fail[FilterTestConfig, []int](inputErr)
// Act
_, err := runEffect(filterMapOp(input), FilterTestConfig{})
// Assert
assert.Error(t, err)
assert.Equal(t, inputErr, err)
})
}
func TestFilterMapIter_EdgeCases(t *testing.T) {
t.Run("handles empty iterator", func(t *testing.T) {
// Arrange
double := func(n int) O.Option[int] {
return O.Some(n * 2)
}
filterMapOp := FilterMapIter[FilterTestConfig](double)
input := Succeed[FilterTestConfig](slices.Values([]int{}))
// Act
collected := collectSeqEffect(filterMapOp(input), FilterTestConfig{})
// Assert
assert.Empty(t, collected)
})
}
func TestFilterMap_GenericFilterMap(t *testing.T) {
t.Run("works with custom filterMap function", func(t *testing.T) {
// Arrange
customFilterMap := func(f O.Kleisli[int, string]) Reader[[]int, []string] {
return A.FilterMap(f)
}
filterMapOp := FilterMap[FilterTestConfig](customFilterMap)
intToString := func(n int) O.Option[string] {
if n > 0 {
return O.Some(fmt.Sprintf("%d", n))
}
return O.None[string]()
}
input := Succeed[FilterTestConfig]([]int{-1, 2, -3, 4})
// Act
result, err := runEffect(filterMapOp(intToString)(input), FilterTestConfig{})
// Assert
assert.NoError(t, err)
assert.Equal(t, []string{"2", "4"}, result)
})
}
func TestFilter_Composition(t *testing.T) {
t.Run("chains multiple filters", func(t *testing.T) {
// Arrange
isPositive := N.MoreThan(0)
isEven := func(n int) bool { return n%2 == 0 }
filterPositive := FilterArray[FilterTestConfig](isPositive)
filterEven := FilterArray[FilterTestConfig](isEven)
input := Succeed[FilterTestConfig]([]int{-2, -1, 0, 1, 2, 3, 4, 5, 6})
// Act
result, err := runEffect(filterEven(filterPositive(input)), FilterTestConfig{})
// Assert
assert.NoError(t, err)
assert.Equal(t, []int{2, 4, 6}, result)
})
t.Run("chains filter and filterMap", func(t *testing.T) {
// Arrange
isPositive := N.MoreThan(0)
doubleEven := func(n int) O.Option[int] {
if n%2 == 0 {
return O.Some(n * 2)
}
return O.None[int]()
}
filterOp := FilterArray[FilterTestConfig](isPositive)
filterMapOp := FilterMapArray[FilterTestConfig](doubleEven)
input := Succeed[FilterTestConfig]([]int{-2, 1, 2, 3, 4, 5})
// Act
result, err := runEffect(filterMapOp(filterOp(input)), FilterTestConfig{})
// Assert
assert.NoError(t, err)
assert.Equal(t, []int{4, 8}, result)
})
}
func TestFilter_WithComplexTypes(t *testing.T) {
type User struct {
Name string
Age int
}
t.Run("filters structs", func(t *testing.T) {
// Arrange
isAdult := func(u User) bool { return u.Age >= 18 }
filterOp := FilterArray[FilterTestConfig](isAdult)
users := []User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 16},
{Name: "Charlie", Age: 30},
}
input := Succeed[FilterTestConfig](users)
// Act
result, err := runEffect(filterOp(input), FilterTestConfig{})
// Assert
assert.NoError(t, err)
expected := []User{
{Name: "Alice", Age: 25},
{Name: "Charlie", Age: 30},
}
assert.Equal(t, expected, result)
})
t.Run("filterMaps structs to different type", func(t *testing.T) {
// Arrange
extractAdultName := func(u User) O.Option[string] {
if u.Age >= 18 {
return O.Some(u.Name)
}
return O.None[string]()
}
filterMapOp := FilterMapArray[FilterTestConfig](extractAdultName)
users := []User{
{Name: "Alice", Age: 25},
{Name: "Bob", Age: 16},
{Name: "Charlie", Age: 30},
}
input := Succeed[FilterTestConfig](users)
// Act
result, err := runEffect(filterMapOp(input), FilterTestConfig{})
// Assert
assert.NoError(t, err)
assert.Equal(t, []string{"Alice", "Charlie"}, result)
})
}
func TestFilter_BoundaryConditions(t *testing.T) {
t.Run("filters with boundary predicate", func(t *testing.T) {
// Arrange
inRange := func(n int) bool { return n >= 0 && n <= 100 }
filterOp := FilterArray[FilterTestConfig](inRange)
input := Succeed[FilterTestConfig]([]int{-1, 0, 50, 100, 101})
// Act
result, err := runEffect(filterOp(input), FilterTestConfig{})
// Assert
assert.NoError(t, err)
assert.Equal(t, []int{0, 50, 100}, result)
})
t.Run("filterMap with boundary conditions", func(t *testing.T) {
// Arrange
clampToRange := func(n int) O.Option[int] {
if n >= 0 && n <= 100 {
return O.Some(n)
}
return O.None[int]()
}
filterMapOp := FilterMapArray[FilterTestConfig](clampToRange)
input := Succeed[FilterTestConfig]([]int{-1, 0, 50, 100, 101})
// Act
result, err := runEffect(filterMapOp(input), FilterTestConfig{})
// Assert
assert.NoError(t, err)
assert.Equal(t, []int{0, 50, 100}, result)
})
}
func TestFilter_WithIterators(t *testing.T) {
t.Run("filters large iterator efficiently", func(t *testing.T) {
// Arrange
isEven := func(n int) bool { return n%2 == 0 }
filterOp := FilterIter[FilterTestConfig](isEven)
// Create iterator for range 0-99
makeSeq := func(yield func(int) bool) {
for i := range 100 {
if !yield(i) {
return
}
}
}
input := Succeed[FilterTestConfig](Seq[int](makeSeq))
// Act
collected := collectSeqEffect(filterOp(input), FilterTestConfig{})
// Assert
assert.Equal(t, 50, len(collected))
assert.Equal(t, 0, collected[0])
assert.Equal(t, 98, collected[49])
})
t.Run("filterMap with iterator", func(t *testing.T) {
// Arrange
squareEven := func(n int) O.Option[int] {
if n%2 == 0 {
return O.Some(n * n)
}
return O.None[int]()
}
filterMapOp := FilterMapIter[FilterTestConfig](squareEven)
input := Succeed[FilterTestConfig](slices.Values([]int{1, 2, 3, 4, 5}))
// Act
collected := collectSeqEffect(filterMapOp(input), FilterTestConfig{})
// Assert
assert.Equal(t, []int{4, 16}, collected)
})
}
func TestFilter_ErrorPropagation(t *testing.T) {
t.Run("filter propagates Left through chain", func(t *testing.T) {
// Arrange
isPositive := N.MoreThan(0)
filterOp := FilterArray[FilterTestConfig](isPositive)
originalErr := errors.New("original error")
// Create an effect that fails
failedEffect := F.Pipe1(
Succeed[FilterTestConfig]([]int{1, 2, 3}),
Chain(func([]int) Effect[FilterTestConfig, []int] {
return Fail[FilterTestConfig, []int](originalErr)
}),
)
// Act
_, err := runEffect(filterOp(failedEffect), FilterTestConfig{})
// Assert
assert.Error(t, err)
assert.Equal(t, originalErr, err)
})
t.Run("filterMap propagates Left through chain", func(t *testing.T) {
// Arrange
double := func(n int) O.Option[int] {
return O.Some(n * 2)
}
filterMapOp := FilterMapArray[FilterTestConfig](double)
originalErr := errors.New("original error")
// Create an effect that fails
failedEffect := F.Pipe1(
Succeed[FilterTestConfig]([]int{1, 2, 3}),
Chain(func([]int) Effect[FilterTestConfig, []int] {
return Fail[FilterTestConfig, []int](originalErr)
}),
)
// Act
_, err := runEffect(filterMapOp(failedEffect), FilterTestConfig{})
// Assert
assert.Error(t, err)
assert.Equal(t, originalErr, err)
})
}
func TestFilter_Integration(t *testing.T) {
t.Run("complex filtering pipeline", func(t *testing.T) {
// Arrange: Filter positive numbers, then double evens, then filter > 5
isPositive := N.MoreThan(0)
doubleEven := func(n int) O.Option[int] {
if n%2 == 0 {
return O.Some(n * 2)
}
return O.None[int]()
}
isGreaterThan5 := N.MoreThan(5)
pipeline := F.Pipe3(
Succeed[FilterTestConfig]([]int{-2, -1, 0, 1, 2, 3, 4, 5, 6}),
FilterArray[FilterTestConfig](isPositive),
FilterMapArray[FilterTestConfig](doubleEven),
FilterArray[FilterTestConfig](isGreaterThan5),
)
// Act
result, err := runEffect(pipeline, FilterTestConfig{})
// Assert
assert.NoError(t, err)
// Positive: [1,2,3,4,5,6] -> DoubleEven: [4,8,12] -> >5: [8,12]
assert.Equal(t, []int{8, 12}, result)
})
}

View File

@@ -19,9 +19,11 @@ import (
"github.com/IBM/fp-go/v2/context/readerioresult"
"github.com/IBM/fp-go/v2/context/readerreaderioresult"
"github.com/IBM/fp-go/v2/either"
"github.com/IBM/fp-go/v2/endomorphism"
"github.com/IBM/fp-go/v2/io"
"github.com/IBM/fp-go/v2/ioeither"
"github.com/IBM/fp-go/v2/ioresult"
"github.com/IBM/fp-go/v2/iterator/iter"
"github.com/IBM/fp-go/v2/lazy"
"github.com/IBM/fp-go/v2/monoid"
"github.com/IBM/fp-go/v2/optics/lens"
@@ -89,4 +91,14 @@ type (
// Operator represents a function that transforms Effect[C, A] to Effect[C, B].
// It's used for lifting operations over effects.
Operator[C, A, B any] = readerreaderioresult.Operator[C, A, B]
// Endomorphism represents a function from type A to type A.
// It's an alias for endomorphism.Endomorphism[A].
Endomorphism[A any] = endomorphism.Endomorphism[A]
// Seq is an iterator over sequences of individual values.
// When called as seq(yield), seq calls yield(v) for each value v in the sequence,
// stopping early if yield returns false.
// See the [iter] package documentation for more details.
Seq[A any] = iter.Seq[A]
)

View File

@@ -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 (

View File

@@ -8,6 +8,8 @@ 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=

View File

@@ -0,0 +1,15 @@
package filterable
import (
"github.com/IBM/fp-go/v2/option"
"github.com/IBM/fp-go/v2/pair"
)
type (
Option[A any] = option.Option[A]
Separated[A, B any] = pair.Pair[A, B]
FilterType[A, HKTA any] = func(func(A) bool) func(HKTA) HKTA
FilterMapType[A, B, HKTA, HKTB any] = func(func(A) Option[B]) func(HKTA) HKTB
)

View File

@@ -0,0 +1,27 @@
package witherable
import (
"github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/filterable"
"github.com/IBM/fp-go/v2/internal/functor"
)
func Filter[A, HKT_G_A, HKT_F_HKT_G_A any](
fmap functor.MapType[HKT_G_A, HKT_G_A, HKT_F_HKT_G_A, HKT_F_HKT_G_A],
ffilter filterable.FilterType[A, HKT_G_A],
) func(func(A) bool) func(HKT_F_HKT_G_A) HKT_F_HKT_G_A {
return function.Flow2(
ffilter,
fmap,
)
}
func FilterMap[A, B, HKT_G_A, HKT_G_B, HKT_F_HKT_G_A, HKT_F_HKT_G_B any](
fmap functor.MapType[HKT_G_A, HKT_G_B, HKT_F_HKT_G_A, HKT_F_HKT_G_B],
ffilter filterable.FilterMapType[A, B, HKT_G_A, HKT_G_B],
) func(func(A) Option[B]) func(HKT_F_HKT_G_A) HKT_F_HKT_G_B {
return function.Flow2(
ffilter,
fmap,
)
}

View File

@@ -0,0 +1 @@
package witherable

View File

@@ -0,0 +1,7 @@
package witherable
import "github.com/IBM/fp-go/v2/option"
type (
Option[A any] = option.Option[A]
)

195
v2/iterator/iter/async.go Normal file
View File

@@ -0,0 +1,195 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package iter
import (
N "github.com/IBM/fp-go/v2/number"
)
// Async converts a synchronous sequence into an asynchronous buffered sequence.
// It spawns a goroutine to consume the input sequence and sends values through
// a buffered channel, allowing concurrent production and consumption of elements.
//
// The function provides backpressure control through the buffer size and properly
// handles early termination when the consumer stops iterating. This is useful for
// decoupling producers and consumers, enabling pipeline parallelism, or when you
// need to process sequences concurrently.
//
// # Type Parameters
//
// - T: The type of elements in the sequence
//
// # Parameters
//
// - input: The source sequence to be consumed asynchronously
// - bufSize: The buffer size for the channel. Negative values are treated as 0 (unbuffered).
// A larger buffer allows more elements to be produced ahead of consumption,
// but uses more memory. A buffer of 0 creates an unbuffered channel requiring
// synchronization between producer and consumer.
//
// # Returns
//
// - Seq[T]: A new sequence that yields elements from the input sequence asynchronously
//
// # Behavior
//
// - Spawns a goroutine that consumes the input sequence
// - Elements are sent through a buffered channel to the output sequence
// - Properly handles early termination: if the consumer stops iterating (yield returns false),
// the producer goroutine is signaled to stop via a done channel
// - Both the producer goroutine and the done channel are properly cleaned up
// - The channel is closed when the input sequence is exhausted or early termination occurs
//
// # Example Usage
//
// // Create an async sequence with a buffer of 10
// seq := From(1, 2, 3, 4, 5)
// async := Async(seq, 10)
//
// // Elements are produced concurrently
// for v := range async {
// fmt.Println(v) // Prints: 1, 2, 3, 4, 5
// }
//
// # Example with Early Termination
//
// seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// async := Async(seq, 5)
//
// // Stop after 3 elements - producer goroutine will be properly cleaned up
// count := 0
// for v := range async {
// fmt.Println(v)
// count++
// if count >= 3 {
// break
// }
// }
//
// # Example with Unbuffered Channel
//
// // bufSize of 0 creates an unbuffered channel
// seq := From(1, 2, 3)
// async := Async(seq, 0)
//
// // Producer and consumer are synchronized
// for v := range async {
// fmt.Println(v)
// }
//
// # See Also
//
// - From: Creates a sequence from values
// - Map: Transforms sequence elements
// - Filter: Filters sequence elements
func Async[T any](input Seq[T], bufSize int) Seq[T] {
return func(yield func(T) bool) {
ch := make(chan T, N.Max(bufSize, 0))
done := make(chan Void)
go func() {
defer close(ch)
for v := range input {
select {
case ch <- v:
case <-done:
return
}
}
}()
defer close(done)
for v := range ch {
if !yield(v) {
return
}
}
}
}
// Async2 converts a synchronous key-value sequence into an asynchronous buffered sequence.
// It spawns a goroutine to consume the input sequence and sends key-value pairs through
// a buffered channel, allowing concurrent production and consumption of elements.
//
// This function is the Seq2 variant of Async, providing the same asynchronous behavior
// for key-value sequences. It internally converts the Seq2 to a sequence of Pairs,
// applies Async, and converts back to Seq2.
//
// # Type Parameters
//
// - K: The type of keys in the sequence
// - V: The type of values in the sequence
//
// # Parameters
//
// - input: The source key-value sequence to be consumed asynchronously
// - bufSize: The buffer size for the channel. Negative values are treated as 0 (unbuffered).
// A larger buffer allows more elements to be produced ahead of consumption,
// but uses more memory. A buffer of 0 creates an unbuffered channel requiring
// synchronization between producer and consumer.
//
// # Returns
//
// - Seq2[K, V]: A new key-value sequence that yields elements from the input sequence asynchronously
//
// # Behavior
//
// - Spawns a goroutine that consumes the input key-value sequence
// - Key-value pairs are sent through a buffered channel to the output sequence
// - Properly handles early termination: if the consumer stops iterating (yield returns false),
// the producer goroutine is signaled to stop via a done channel
// - Both the producer goroutine and the done channel are properly cleaned up
// - The channel is closed when the input sequence is exhausted or early termination occurs
//
// # Example Usage
//
// // Create an async key-value sequence with a buffer of 10
// seq := MonadZip(From(1, 2, 3), From("a", "b", "c"))
// async := Async2(seq, 10)
//
// // Elements are produced concurrently
// for k, v := range async {
// fmt.Printf("%d: %s\n", k, v)
// }
// // Output:
// // 1: a
// // 2: b
// // 3: c
//
// # Example with Early Termination
//
// seq := MonadZip(From(1, 2, 3, 4, 5), From("a", "b", "c", "d", "e"))
// async := Async2(seq, 5)
//
// // Stop after 2 pairs - producer goroutine will be properly cleaned up
// count := 0
// for k, v := range async {
// fmt.Printf("%d: %s\n", k, v)
// count++
// if count >= 2 {
// break
// }
// }
//
// # See Also
//
// - Async: Asynchronous sequence for single-value sequences
// - ToSeqPair: Converts Seq2 to Seq of Pairs
// - FromSeqPair: Converts Seq of Pairs to Seq2
// - MonadZip: Creates key-value sequences from two sequences
func Async2[K, V any](input Seq2[K, V], bufSize int) Seq2[K, V] {
return FromSeqPair(Async(ToSeqPair(input), bufSize))
}

View File

@@ -0,0 +1,905 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package iter
import (
"fmt"
"sync/atomic"
"testing"
"time"
N "github.com/IBM/fp-go/v2/number"
"github.com/IBM/fp-go/v2/pair"
"github.com/stretchr/testify/assert"
)
// TestAsync_Success tests basic Async functionality
func TestAsync_Success(t *testing.T) {
t.Run("converts sequence to async with buffer", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
async := Async(seq, 10)
result := toSlice(async)
assert.Equal(t, []int{1, 2, 3, 4, 5}, result)
})
t.Run("preserves element order", func(t *testing.T) {
seq := From("a", "b", "c", "d", "e")
async := Async(seq, 5)
result := toSlice(async)
assert.Equal(t, []string{"a", "b", "c", "d", "e"}, result)
})
t.Run("works with single element", func(t *testing.T) {
seq := From(42)
async := Async(seq, 1)
result := toSlice(async)
assert.Equal(t, []int{42}, result)
})
t.Run("works with large sequence", func(t *testing.T) {
data := make([]int, 100)
for i := range data {
data[i] = i
}
seq := From(data...)
async := Async(seq, 20)
result := toSlice(async)
assert.Equal(t, data, result)
})
}
// TestAsync_BufferSizes tests different buffer sizes
func TestAsync_BufferSizes(t *testing.T) {
t.Run("unbuffered channel (bufSize 0)", func(t *testing.T) {
seq := From(1, 2, 3)
async := Async(seq, 0)
result := toSlice(async)
assert.Equal(t, []int{1, 2, 3}, result)
})
t.Run("small buffer", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
async := Async(seq, 2)
result := toSlice(async)
assert.Equal(t, []int{1, 2, 3, 4, 5}, result)
})
t.Run("large buffer", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
async := Async(seq, 100)
result := toSlice(async)
assert.Equal(t, []int{1, 2, 3, 4, 5}, result)
})
t.Run("negative buffer size treated as 0", func(t *testing.T) {
seq := From(1, 2, 3)
async := Async(seq, -5)
result := toSlice(async)
assert.Equal(t, []int{1, 2, 3}, result)
})
t.Run("buffer size equals sequence length", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
async := Async(seq, 5)
result := toSlice(async)
assert.Equal(t, []int{1, 2, 3, 4, 5}, result)
})
t.Run("buffer size larger than sequence", func(t *testing.T) {
seq := From(1, 2, 3)
async := Async(seq, 10)
result := toSlice(async)
assert.Equal(t, []int{1, 2, 3}, result)
})
}
// TestAsync_Empty tests Async with empty sequences
func TestAsync_Empty(t *testing.T) {
t.Run("empty integer sequence", func(t *testing.T) {
seq := Empty[int]()
async := Async(seq, 5)
result := toSlice(async)
assert.Empty(t, result)
})
t.Run("empty string sequence", func(t *testing.T) {
seq := Empty[string]()
async := Async(seq, 10)
result := toSlice(async)
assert.Empty(t, result)
})
t.Run("empty with zero buffer", func(t *testing.T) {
seq := Empty[int]()
async := Async(seq, 0)
result := toSlice(async)
assert.Empty(t, result)
})
}
// TestAsync_EarlyTermination tests that Async properly handles early termination
func TestAsync_EarlyTermination(t *testing.T) {
t.Run("stops producer when consumer breaks", func(t *testing.T) {
var producerCount atomic.Int32
// Create a sequence that tracks how many elements were produced
seq := func(yield func(int) bool) {
for i := range 100 {
producerCount.Add(1)
if !yield(i) {
return
}
}
}
async := Async(seq, 10)
// Consume only 5 elements
count := 0
for range async {
count++
if count >= 5 {
break
}
}
// Give goroutine time to clean up
time.Sleep(10 * time.Millisecond)
// Producer should have stopped shortly after consumer stopped
// It may produce a few extra due to buffering, but not all 100
produced := producerCount.Load()
assert.LessOrEqual(t, produced, int32(20), "producer should stop after consumer breaks")
assert.GreaterOrEqual(t, produced, int32(5), "producer should produce at least what was consumed")
})
t.Run("handles yield returning false", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
async := Async(seq, 5)
collected := []int{}
for v := range async {
collected = append(collected, v)
if v == 3 {
break
}
}
assert.Equal(t, []int{1, 2, 3}, collected)
})
t.Run("early termination with unbuffered channel", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
async := Async(seq, 0)
collected := []int{}
for v := range async {
collected = append(collected, v)
if v == 2 {
break
}
}
assert.Equal(t, []int{1, 2}, collected)
})
}
// TestAsync_WithComplexTypes tests Async with complex data types
func TestAsync_WithComplexTypes(t *testing.T) {
type Person struct {
Name string
Age int
}
t.Run("works with structs", func(t *testing.T) {
seq := From(
Person{"Alice", 30},
Person{"Bob", 25},
Person{"Charlie", 35},
)
async := Async(seq, 5)
result := toSlice(async)
expected := []Person{
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35},
}
assert.Equal(t, expected, result)
})
t.Run("works with pointers", func(t *testing.T) {
p1 := &Person{"Alice", 30}
p2 := &Person{"Bob", 25}
p3 := &Person{"Charlie", 35}
seq := From(p1, p2, p3)
async := Async(seq, 3)
result := toSlice(async)
assert.Equal(t, []*Person{p1, p2, p3}, result)
})
t.Run("works with slices", func(t *testing.T) {
seq := From([]int{1, 2}, []int{3, 4}, []int{5, 6})
async := Async(seq, 2)
result := toSlice(async)
expected := [][]int{{1, 2}, {3, 4}, {5, 6}}
assert.Equal(t, expected, result)
})
t.Run("works with maps", func(t *testing.T) {
m1 := map[string]int{"a": 1}
m2 := map[string]int{"b": 2}
m3 := map[string]int{"c": 3}
seq := From(m1, m2, m3)
async := Async(seq, 3)
result := toSlice(async)
assert.Equal(t, []map[string]int{m1, m2, m3}, result)
})
}
// TestAsync_WithChainedOperations tests Async with other sequence operations
func TestAsync_WithChainedOperations(t *testing.T) {
t.Run("async after map", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
mapped := MonadMap(seq, N.Mul(2))
async := Async(mapped, 5)
result := toSlice(async)
assert.Equal(t, []int{2, 4, 6, 8, 10}, result)
})
t.Run("map after async", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
async := Async(seq, 5)
mapped := MonadMap(async, N.Mul(2))
result := toSlice(mapped)
assert.Equal(t, []int{2, 4, 6, 8, 10}, result)
})
t.Run("async 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 })
async := Async(filtered, 5)
result := toSlice(async)
assert.Equal(t, []int{2, 4, 6, 8, 10}, result)
})
t.Run("filter after async", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
async := Async(seq, 5)
filtered := MonadFilter(async, func(x int) bool { return x%2 == 0 })
result := toSlice(filtered)
assert.Equal(t, []int{2, 4, 6, 8, 10}, result)
})
t.Run("async after chain", func(t *testing.T) {
seq := From(1, 2, 3)
chained := MonadChain(seq, func(x int) Seq[int] {
return From(x, x*10)
})
async := Async(chained, 10)
result := toSlice(async)
assert.Equal(t, []int{1, 10, 2, 20, 3, 30}, result)
})
t.Run("multiple async operations", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
async1 := Async(seq, 3)
async2 := Async(async1, 2)
result := toSlice(async2)
assert.Equal(t, []int{1, 2, 3, 4, 5}, result)
})
}
// TestAsync_Concurrency tests concurrent behavior
func TestAsync_Concurrency(t *testing.T) {
t.Run("allows concurrent production and consumption", func(t *testing.T) {
// Create a slow producer
seq := func(yield func(int) bool) {
for i := range 5 {
time.Sleep(5 * time.Millisecond)
if !yield(i) {
return
}
}
}
async := Async(seq, 10)
result := toSlice(async)
// Verify all elements are produced correctly
assert.Equal(t, []int{0, 1, 2, 3, 4}, result)
})
t.Run("handles concurrent consumption safely", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
async := Async(seq, 5)
// Consume with some processing time
var sum atomic.Int32
for v := range async {
sum.Add(int32(v))
time.Sleep(1 * time.Millisecond)
}
assert.Equal(t, int32(55), sum.Load())
})
}
// TestAsync_EdgeCases tests edge cases
func TestAsync_EdgeCases(t *testing.T) {
t.Run("very large buffer size", func(t *testing.T) {
seq := From(1, 2, 3)
async := Async(seq, 1000000)
result := toSlice(async)
assert.Equal(t, []int{1, 2, 3}, result)
})
t.Run("buffer size of 1", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
async := Async(seq, 1)
result := toSlice(async)
assert.Equal(t, []int{1, 2, 3, 4, 5}, result)
})
t.Run("works with replicate", func(t *testing.T) {
seq := Replicate(5, 42)
async := Async(seq, 3)
result := toSlice(async)
assert.Equal(t, []int{42, 42, 42, 42, 42}, result)
})
t.Run("works with makeBy", func(t *testing.T) {
seq := MakeBy(5, func(i int) int { return i * i })
async := Async(seq, 3)
result := toSlice(async)
assert.Equal(t, []int{0, 1, 4, 9, 16}, result)
})
}
// Benchmark tests
func BenchmarkAsync(b *testing.B) {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
b.ResetTimer()
for range b.N {
async := Async(seq, 5)
for range async {
}
}
}
func BenchmarkAsync_LargeSequence(b *testing.B) {
data := make([]int, 1000)
for i := range data {
data[i] = i
}
seq := From(data...)
b.ResetTimer()
for range b.N {
async := Async(seq, 100)
for range async {
}
}
}
func BenchmarkAsync_SmallBuffer(b *testing.B) {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
b.ResetTimer()
for range b.N {
async := Async(seq, 1)
for range async {
}
}
}
func BenchmarkAsync_LargeBuffer(b *testing.B) {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
b.ResetTimer()
for range b.N {
async := Async(seq, 100)
for range async {
}
}
}
func BenchmarkAsync_Unbuffered(b *testing.B) {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
b.ResetTimer()
for range b.N {
async := Async(seq, 0)
for range async {
}
}
}
func BenchmarkAsync_WithMap(b *testing.B) {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
b.ResetTimer()
for range b.N {
async := Async(seq, 5)
mapped := MonadMap(async, N.Mul(2))
for range mapped {
}
}
}
func BenchmarkAsync_WithFilter(b *testing.B) {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
b.ResetTimer()
for range b.N {
async := Async(seq, 5)
filtered := MonadFilter(async, func(x int) bool { return x%2 == 0 })
for range filtered {
}
}
}
// Example tests for documentation
func ExampleAsync() {
seq := From(1, 2, 3, 4, 5)
async := Async(seq, 10)
for v := range async {
fmt.Printf("%d ", v)
}
// Output: 1 2 3 4 5
}
func ExampleAsync_unbuffered() {
seq := From(1, 2, 3)
async := Async(seq, 0)
for v := range async {
fmt.Printf("%d ", v)
}
// Output: 1 2 3
}
func ExampleAsync_earlyTermination() {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
async := Async(seq, 5)
count := 0
for v := range async {
fmt.Printf("%d ", v)
count++
if count >= 3 {
break
}
}
// Output: 1 2 3
}
func ExampleAsync_withMap() {
seq := From(1, 2, 3, 4, 5)
async := Async(seq, 5)
doubled := MonadMap(async, N.Mul(2))
for v := range doubled {
fmt.Printf("%d ", v)
}
// Output: 2 4 6 8 10
}
func ExampleAsync_withFilter() {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
async := Async(seq, 5)
evens := MonadFilter(async, func(x int) bool { return x%2 == 0 })
for v := range evens {
fmt.Printf("%d ", v)
}
// Output: 2 4 6 8 10
}
// TestAsync2_Success tests basic Async2 functionality
func TestAsync2_Success(t *testing.T) {
t.Run("converts Seq2 to async with buffer", func(t *testing.T) {
seq := MonadZip(From(1, 2, 3), From("a", "b", "c"))
async := Async2(seq, 10)
result := toMap(async)
expected := map[int]string{1: "a", 2: "b", 3: "c"}
assert.Equal(t, expected, result)
})
t.Run("preserves key-value pairs order", func(t *testing.T) {
seq := MonadZip(From("x", "y", "z"), From(10, 20, 30))
async := Async2(seq, 5)
keys := []string{}
values := []int{}
for k, v := range async {
keys = append(keys, k)
values = append(values, v)
}
assert.Equal(t, []string{"x", "y", "z"}, keys)
assert.Equal(t, []int{10, 20, 30}, values)
})
t.Run("works with single pair", func(t *testing.T) {
seq := Of2("key", 42)
async := Async2(seq, 1)
result := toMap(async)
assert.Equal(t, map[string]int{"key": 42}, result)
})
t.Run("works with large Seq2", func(t *testing.T) {
keys := make([]int, 100)
values := make([]string, 100)
for i := range keys {
keys[i] = i
values[i] = fmt.Sprintf("val%d", i)
}
seq := MonadZip(From(keys...), From(values...))
async := Async2(seq, 20)
result := toMap(async)
assert.Equal(t, 100, len(result))
for i := range 100 {
assert.Equal(t, fmt.Sprintf("val%d", i), result[i])
}
})
}
// TestAsync2_BufferSizes tests different buffer sizes
func TestAsync2_BufferSizes(t *testing.T) {
t.Run("unbuffered channel (bufSize 0)", func(t *testing.T) {
seq := MonadZip(From(1, 2, 3), From("a", "b", "c"))
async := Async2(seq, 0)
result := toMap(async)
expected := map[int]string{1: "a", 2: "b", 3: "c"}
assert.Equal(t, expected, result)
})
t.Run("negative buffer size treated as 0", func(t *testing.T) {
seq := MonadZip(From(1, 2, 3), From("a", "b", "c"))
async := Async2(seq, -5)
result := toMap(async)
expected := map[int]string{1: "a", 2: "b", 3: "c"}
assert.Equal(t, expected, result)
})
t.Run("large buffer", func(t *testing.T) {
seq := MonadZip(From(1, 2, 3), From("a", "b", "c"))
async := Async2(seq, 100)
result := toMap(async)
expected := map[int]string{1: "a", 2: "b", 3: "c"}
assert.Equal(t, expected, result)
})
}
// TestAsync2_Empty tests Async2 with empty sequences
func TestAsync2_Empty(t *testing.T) {
t.Run("empty Seq2", func(t *testing.T) {
seq := MonadZip(Empty[int](), Empty[string]())
async := Async2(seq, 5)
result := toMap(async)
assert.Empty(t, result)
})
}
// TestAsync2_EarlyTermination tests that Async2 properly handles early termination
func TestAsync2_EarlyTermination(t *testing.T) {
t.Run("stops producer when consumer breaks", func(t *testing.T) {
seq := MonadZip(From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), From("a", "b", "c", "d", "e", "f", "g", "h", "i", "j"))
async := Async2(seq, 5)
count := 0
for range async {
count++
if count >= 3 {
break
}
}
assert.Equal(t, 3, count)
})
}
// TestAsync2_WithChainedOperations tests Async2 with other operations
func TestAsync2_WithChainedOperations(t *testing.T) {
t.Run("async2 after map", func(t *testing.T) {
seq := MonadZip(From(1, 2, 3), From(10, 20, 30))
mapped := MonadMapWithKey(seq, func(k, v int) int { return k + v })
async := Async2(mapped, 5)
result := toMap(async)
expected := map[int]int{1: 11, 2: 22, 3: 33}
assert.Equal(t, expected, result)
})
}
// TestToSeqPair_Success tests basic ToSeqPair functionality
func TestToSeqPair_Success(t *testing.T) {
t.Run("converts Seq2 to Seq of Pairs", func(t *testing.T) {
seq2 := MonadZip(From(1, 2, 3), From("a", "b", "c"))
pairs := ToSeqPair(seq2)
result := toSlice(pairs)
assert.Equal(t, 3, len(result))
assert.Equal(t, 1, pair.Head(result[0]))
assert.Equal(t, "a", pair.Tail(result[0]))
assert.Equal(t, 2, pair.Head(result[1]))
assert.Equal(t, "b", pair.Tail(result[1]))
assert.Equal(t, 3, pair.Head(result[2]))
assert.Equal(t, "c", pair.Tail(result[2]))
})
t.Run("preserves order", func(t *testing.T) {
seq2 := MonadZip(From("x", "y", "z"), From(10, 20, 30))
pairs := ToSeqPair(seq2)
result := toSlice(pairs)
assert.Equal(t, 3, len(result))
for i, p := range result {
expectedKey := string(rune('x' + i))
expectedVal := (i + 1) * 10
assert.Equal(t, expectedKey, pair.Head(p))
assert.Equal(t, expectedVal, pair.Tail(p))
}
})
t.Run("works with single pair", func(t *testing.T) {
seq2 := Of2("key", 42)
pairs := ToSeqPair(seq2)
result := toSlice(pairs)
assert.Equal(t, 1, len(result))
assert.Equal(t, "key", pair.Head(result[0]))
assert.Equal(t, 42, pair.Tail(result[0]))
})
}
// TestToSeqPair_Empty tests ToSeqPair with empty sequences
func TestToSeqPair_Empty(t *testing.T) {
t.Run("empty Seq2 produces empty Seq", func(t *testing.T) {
seq2 := MonadZip(Empty[int](), Empty[string]())
pairs := ToSeqPair(seq2)
result := toSlice(pairs)
assert.Empty(t, result)
})
}
// TestToSeqPair_WithComplexTypes tests ToSeqPair with complex types
func TestToSeqPair_WithComplexTypes(t *testing.T) {
type Person struct {
Name string
Age int
}
t.Run("works with struct values", func(t *testing.T) {
seq2 := MonadZip(
From(1, 2, 3),
From(Person{"Alice", 30}, Person{"Bob", 25}, Person{"Charlie", 35}),
)
pairs := ToSeqPair(seq2)
result := toSlice(pairs)
assert.Equal(t, 3, len(result))
assert.Equal(t, 1, pair.Head(result[0]))
assert.Equal(t, Person{"Alice", 30}, pair.Tail(result[0]))
})
}
// TestFromSeqPair_Success tests basic FromSeqPair functionality
func TestFromSeqPair_Success(t *testing.T) {
t.Run("converts Seq of Pairs to Seq2", func(t *testing.T) {
pairs := From(
pair.MakePair(1, "a"),
pair.MakePair(2, "b"),
pair.MakePair(3, "c"),
)
seq2 := FromSeqPair(pairs)
result := toMap(seq2)
expected := map[int]string{1: "a", 2: "b", 3: "c"}
assert.Equal(t, expected, result)
})
t.Run("preserves order", func(t *testing.T) {
pairs := From(
pair.MakePair("x", 10),
pair.MakePair("y", 20),
pair.MakePair("z", 30),
)
seq2 := FromSeqPair(pairs)
keys := []string{}
values := []int{}
for k, v := range seq2 {
keys = append(keys, k)
values = append(values, v)
}
assert.Equal(t, []string{"x", "y", "z"}, keys)
assert.Equal(t, []int{10, 20, 30}, values)
})
t.Run("works with single pair", func(t *testing.T) {
pairs := From(pair.MakePair("key", 42))
seq2 := FromSeqPair(pairs)
result := toMap(seq2)
assert.Equal(t, map[string]int{"key": 42}, result)
})
}
// TestFromSeqPair_Empty tests FromSeqPair with empty sequences
func TestFromSeqPair_Empty(t *testing.T) {
t.Run("empty Seq produces empty Seq2", func(t *testing.T) {
pairs := Empty[Pair[int, string]]()
seq2 := FromSeqPair(pairs)
result := toMap(seq2)
assert.Empty(t, result)
})
}
// TestFromSeqPair_WithComplexTypes tests FromSeqPair with complex types
func TestFromSeqPair_WithComplexTypes(t *testing.T) {
type Person struct {
Name string
Age int
}
t.Run("works with struct values", func(t *testing.T) {
pairs := From(
pair.MakePair(1, Person{"Alice", 30}),
pair.MakePair(2, Person{"Bob", 25}),
pair.MakePair(3, Person{"Charlie", 35}),
)
seq2 := FromSeqPair(pairs)
result := toMap(seq2)
expected := map[int]Person{
1: {"Alice", 30},
2: {"Bob", 25},
3: {"Charlie", 35},
}
assert.Equal(t, expected, result)
})
}
// TestRoundTrip tests that ToSeqPair and FromSeqPair are inverses
func TestRoundTrip(t *testing.T) {
t.Run("ToSeqPair then FromSeqPair", func(t *testing.T) {
original := MonadZip(From(1, 2, 3), From("a", "b", "c"))
pairs := ToSeqPair(original)
restored := FromSeqPair(pairs)
result := toMap(restored)
expected := map[int]string{1: "a", 2: "b", 3: "c"}
assert.Equal(t, expected, result)
})
t.Run("FromSeqPair then ToSeqPair", func(t *testing.T) {
original := From(
pair.MakePair(1, "a"),
pair.MakePair(2, "b"),
pair.MakePair(3, "c"),
)
seq2 := FromSeqPair(original)
restored := ToSeqPair(seq2)
result := toSlice(restored)
assert.Equal(t, 3, len(result))
assert.Equal(t, 1, pair.Head(result[0]))
assert.Equal(t, "a", pair.Tail(result[0]))
})
}
// Benchmark tests for Async2
func BenchmarkAsync2(b *testing.B) {
seq := MonadZip(From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), From("a", "b", "c", "d", "e", "f", "g", "h", "i", "j"))
b.ResetTimer()
for range b.N {
async := Async2(seq, 5)
for range async {
}
}
}
func BenchmarkAsync2_LargeSequence(b *testing.B) {
keys := make([]int, 1000)
values := make([]string, 1000)
for i := range keys {
keys[i] = i
values[i] = fmt.Sprintf("val%d", i)
}
seq := MonadZip(From(keys...), From(values...))
b.ResetTimer()
for range b.N {
async := Async2(seq, 100)
for range async {
}
}
}
// Benchmark tests for FromSeqPair
func BenchmarkFromSeqPair(b *testing.B) {
pairs := From(
pair.MakePair(1, "a"),
pair.MakePair(2, "b"),
pair.MakePair(3, "c"),
pair.MakePair(4, "d"),
pair.MakePair(5, "e"),
)
b.ResetTimer()
for range b.N {
seq2 := FromSeqPair(pairs)
for range seq2 {
}
}
}
func BenchmarkRoundTrip(b *testing.B) {
seq := MonadZip(From(1, 2, 3, 4, 5), From("a", "b", "c", "d", "e"))
b.ResetTimer()
for range b.N {
pairs := ToSeqPair(seq)
restored := FromSeqPair(pairs)
for range restored {
}
}
}
// Example tests for Async2
func ExampleAsync2() {
seq := MonadZip(From(1, 2, 3), From("a", "b", "c"))
async := Async2(seq, 10)
for k, v := range async {
fmt.Printf("%d: %s\n", k, v)
}
// Output:
// 1: a
// 2: b
// 3: c
}
func ExampleAsync2_earlyTermination() {
seq := MonadZip(From(1, 2, 3, 4, 5), From("a", "b", "c", "d", "e"))
async := Async2(seq, 5)
count := 0
for k, v := range async {
fmt.Printf("%d: %s\n", k, v)
count++
if count >= 2 {
break
}
}
// Output:
// 1: a
// 2: b
}
// Example tests for FromSeqPair
func ExampleFromSeqPair() {
pairs := From(
pair.MakePair(1, "a"),
pair.MakePair(2, "b"),
pair.MakePair(3, "c"),
)
seq2 := FromSeqPair(pairs)
for k, v := range seq2 {
fmt.Printf("%d: %s\n", k, v)
}
// Output:
// 1: a
// 2: b
// 3: c
}

View File

@@ -1002,3 +1002,132 @@ func ToSeqPair[A, B any](as Seq2[A, B]) Seq[Pair[A, B]] {
}
}
}
// FromSeqPair converts a sequence of Pairs into a key-value sequence.
//
// This function transforms a Seq[Pair[A, B]] (which yields Pair objects when iterated)
// into a Seq2[A, B] (which yields key-value pairs as separate arguments). This is the
// inverse operation of ToSeqPair and is useful when you need to convert from working
// with pairs as first-class values back to the key-value iteration pattern.
//
// # Type Parameters
//
// - A: The type of the first element (key) in each pair
// - B: The type of the second element (value) in each pair
//
// # Parameters
//
// - as: A Seq that yields Pair objects
//
// # Returns
//
// - Seq2[A, B]: A key-value sequence that yields the unpacked pairs
//
// # Example Usage
//
// // Create a sequence of pairs
// pairs := From(
// pair.MakePair("a", 1),
// pair.MakePair("b", 2),
// pair.MakePair("c", 3),
// )
// seq2 := FromSeqPair(pairs)
//
// // Iterate as key-value pairs
// for k, v := range seq2 {
// fmt.Printf("%s: %d\n", k, v)
// }
// // Output:
// // a: 1
// // b: 2
// // c: 3
//
// # Example with Map
//
// pairs := From(
// pair.MakePair(1, 10),
// pair.MakePair(2, 20),
// pair.MakePair(3, 30),
// )
// seq2 := FromSeqPair(pairs)
//
// // Use with Seq2 operations
// mapped := MonadMapWithKey(seq2, func(k, v int) int {
// return k + v
// })
// // yields: 11, 22, 33
//
// # Example - Round-trip conversion
//
// original := MonadZip(From(1, 2, 3), From("a", "b", "c"))
// pairs := ToSeqPair(original)
// restored := FromSeqPair(pairs)
// // restored is equivalent to original
//
// # See Also
//
// - ToSeqPair: Converts Seq2 to Seq of Pairs (inverse operation)
// - MonadZip: Creates key-value sequences from two sequences
// - pair.MakePair: Creates a Pair from two values
// - pair.Unpack: Unpacks a Pair into two values
func FromSeqPair[A, B any](as Seq[Pair[A, B]]) Seq2[A, B] {
return func(yield func(A, B) bool) {
for p := range as {
if !yield(pair.Unpack(p)) {
return
}
}
}
}
// 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.
//
// 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 })
}

View File

@@ -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]
}

View File

@@ -78,3 +78,144 @@ 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.
//
// 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.
//
// 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
}
}
}
}
}

View File

@@ -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
}

View File

@@ -115,10 +115,7 @@ func Inc[T Number](value T) T {
// result := Min(5, 10) // returns 5
// result := Min(3.14, 2.71) // returns 2.71
func Min[A C.Ordered](a, b A) A {
if a < b {
return a
}
return b
return min(a, b)
}
// Max returns the maximum of two ordered values.
@@ -132,10 +129,7 @@ func Min[A C.Ordered](a, b A) A {
// result := Max(5, 10) // returns 10
// result := Max(3.14, 2.71) // returns 3.14
func Max[A C.Ordered](a, b A) A {
if a > b {
return a
}
return b
return max(a, b)
}
// MoreThan is a curried comparison function that checks if a value is more than (greater than) another.

View File

@@ -22,6 +22,7 @@ import (
"github.com/IBM/fp-go/v2/endomorphism"
EQ "github.com/IBM/fp-go/v2/eq"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/functor"
)
// setCopy wraps a setter for a pointer into a setter that first creates a copy before
@@ -909,6 +910,83 @@ func Modify[S any, FCT ~func(A) A, A any](f FCT) func(Lens[S, A]) Endomorphism[S
}
}
// ModifyF transforms a value through a lens using a function that returns a value in a functor context.
//
// This is the functorial version of Modify, allowing transformations that produce effects
// (like Option, Either, IO, etc.) while updating the focused value. The functor's map operation
// is used to apply the lens's setter to the transformed value, preserving the computational context.
//
// This function corresponds to modifyF from monocle-ts, enabling effectful updates through lenses.
//
// # Type Parameters
//
// - S: Structure type
// - A: Focus type (the value being transformed)
// - HKTA: Higher-kinded type containing the transformed value (e.g., Option[A], Either[E, A])
// - HKTS: Higher-kinded type containing the updated structure (e.g., Option[S], Either[E, S])
//
// # Parameters
//
// - fmap: A functor map operation that transforms A to S within the functor context
//
// # Returns
//
// - A curried function that takes:
// 1. A transformation function (A → HKTA)
// 2. A Lens[S, A]
// 3. A structure S
// And returns the updated structure in the functor context (HKTS)
//
// # Example Usage
//
// type Person struct {
// Name string
// Age int
// }
//
// ageLens := lens.MakeLens(
// func(p Person) int { return p.Age },
// func(p Person, age int) Person { p.Age = age; return p },
// )
//
// // Validate age is positive, returning Option
// validateAge := func(age int) option.Option[int] {
// if age > 0 {
// return option.Some(age)
// }
// return option.None[int]()
// }
//
// // Create a modifier that validates while updating
// modifyAge := lens.ModifyF[Person, int](option.Functor[int, Person]().Map)
//
// person := Person{Name: "Alice", Age: 30}
// result := modifyAge(validateAge)(ageLens)(person)
// // result is Some(Person{Name: "Alice", Age: 30})
//
// invalidResult := modifyAge(func(age int) option.Option[int] {
// return option.None[int]()
// })(ageLens)(person)
// // invalidResult is None[Person]()
//
// # See Also
//
// - Modify: Non-functorial version for simple transformations
// - functor.Functor: The functor interface used for mapping
func ModifyF[S, A, HKTA, HKTS any](
fmap functor.MapType[A, S, HKTA, HKTS],
) func(func(A) HKTA) func(Lens[S, A]) func(S) HKTS {
return func(f func(A) HKTA) func(Lens[S, A]) func(S) HKTS {
return func(sa Lens[S, A]) func(S) HKTS {
return func(s S) HKTS {
return fmap(func(a A) S {
return sa.Set(a)(s)
})(f(sa.Get(s)))
}
}
}
}
// IMap transforms the focus type of a lens using an isomorphism.
//
// An isomorphism is a pair of functions (A → B, B → A) that are inverses of each other.

View File

@@ -16,6 +16,7 @@
package lens
import (
"errors"
"testing"
EQ "github.com/IBM/fp-go/v2/eq"
@@ -937,3 +938,367 @@ func TestMakeLensWithEq_WithNilState_MultipleOperations(t *testing.T) {
assert.NotNil(t, street4)
assert.Equal(t, "", street4.name)
}
// TestModifyF_Success tests ModifyF with a simple Maybe-like functor for successful transformations
func TestModifyF_Success(t *testing.T) {
// Define a simple Maybe type for testing
type Maybe[A any] struct {
value *A
}
some := func(a int) Maybe[int] {
return Maybe[int]{value: &a}
}
none := func() Maybe[int] {
return Maybe[int]{value: nil}
}
// Functor map for Maybe
maybeMap := func(f func(int) Inner) func(Maybe[int]) Maybe[Inner] {
return func(ma Maybe[int]) Maybe[Inner] {
if ma.value == nil {
return Maybe[Inner]{value: nil}
}
result := f(*ma.value)
return Maybe[Inner]{value: &result}
}
}
t.Run("transforms value with successful result", func(t *testing.T) {
ageLens := MakeLens(
func(p Inner) int { return p.Value },
func(p Inner, age int) Inner { p.Value = age; return p },
)
// Function that returns Some for positive values
validatePositive := func(n int) Maybe[int] {
if n > 0 {
return some(n * 2)
}
return none()
}
modifyAge := ModifyF[Inner, int](maybeMap)
person := Inner{Value: 5, Foo: "test"}
result := modifyAge(validatePositive)(ageLens)(person)
assert.NotNil(t, result.value)
updated := *result.value
assert.Equal(t, 10, updated.Value)
assert.Equal(t, "test", updated.Foo)
})
t.Run("preserves structure with identity transformation", func(t *testing.T) {
type MaybeStr struct {
value *string
}
someStr := func(s string) MaybeStr {
return MaybeStr{value: &s}
}
maybeStrMap := func(f func(string) Street) func(MaybeStr) struct{ value *Street } {
return func(ma MaybeStr) struct{ value *Street } {
if ma.value == nil {
return struct{ value *Street }{value: nil}
}
result := f(*ma.value)
return struct{ value *Street }{value: &result}
}
}
nameLens := MakeLens(
func(s Street) string { return s.name },
func(s Street, name string) Street { s.name = name; return s },
)
identity := func(s string) MaybeStr {
return someStr(s)
}
modifyName := ModifyF[Street, string](maybeStrMap)
street := Street{num: 1, name: "Main"}
result := modifyName(identity)(nameLens)(street)
assert.NotNil(t, result.value)
assert.Equal(t, street, *result.value)
})
}
// TestModifyF_Failure tests ModifyF with failures
func TestModifyF_Failure(t *testing.T) {
type Maybe[A any] struct {
value *A
}
some := func(a int) Maybe[int] {
return Maybe[int]{value: &a}
}
none := func() Maybe[int] {
return Maybe[int]{value: nil}
}
maybeMap := func(f func(int) Inner) func(Maybe[int]) Maybe[Inner] {
return func(ma Maybe[int]) Maybe[Inner] {
if ma.value == nil {
return Maybe[Inner]{value: nil}
}
result := f(*ma.value)
return Maybe[Inner]{value: &result}
}
}
t.Run("returns None when transformation fails", func(t *testing.T) {
ageLens := MakeLens(
func(p Inner) int { return p.Value },
func(p Inner, age int) Inner { p.Value = age; return p },
)
validatePositive := func(n int) Maybe[int] {
if n > 0 {
return some(n)
}
return none()
}
modifyAge := ModifyF[Inner, int](maybeMap)
person := Inner{Value: -5, Foo: "test"}
result := modifyAge(validatePositive)(ageLens)(person)
assert.Nil(t, result.value)
})
}
// TestModifyF_WithResult tests ModifyF with Result/Either-like functor
func TestModifyF_WithResult(t *testing.T) {
type Result[A any] struct {
value *A
err error
}
ok := func(a int) Result[int] {
return Result[int]{value: &a, err: nil}
}
fail := func(e error) Result[int] {
return Result[int]{value: nil, err: e}
}
resultMap := func(f func(int) Inner) func(Result[int]) Result[Inner] {
return func(r Result[int]) Result[Inner] {
if r.err != nil {
return Result[Inner]{value: nil, err: r.err}
}
result := f(*r.value)
return Result[Inner]{value: &result, err: nil}
}
}
t.Run("returns success for valid transformation", func(t *testing.T) {
ageLens := MakeLens(
func(p Inner) int { return p.Value },
func(p Inner, age int) Inner { p.Value = age; return p },
)
validateAge := func(n int) Result[int] {
if n >= 0 && n <= 150 {
return ok(n + 1)
}
return fail(errors.New("age out of range"))
}
modifyAge := ModifyF[Inner, int](resultMap)
person := Inner{Value: 30, Foo: "test"}
result := modifyAge(validateAge)(ageLens)(person)
assert.Nil(t, result.err)
assert.NotNil(t, result.value)
assert.Equal(t, 31, result.value.Value)
assert.Equal(t, "test", result.value.Foo)
})
t.Run("returns error for failed validation", func(t *testing.T) {
ageLens := MakeLens(
func(p Inner) int { return p.Value },
func(p Inner, age int) Inner { p.Value = age; return p },
)
validateAge := func(n int) Result[int] {
if n >= 0 && n <= 150 {
return ok(n)
}
return fail(errors.New("age out of range"))
}
modifyAge := ModifyF[Inner, int](resultMap)
person := Inner{Value: 200, Foo: "test"}
result := modifyAge(validateAge)(ageLens)(person)
assert.NotNil(t, result.err)
assert.Equal(t, "age out of range", result.err.Error())
assert.Nil(t, result.value)
})
}
// TestModifyF_EdgeCases tests edge cases for ModifyF
func TestModifyF_EdgeCases(t *testing.T) {
type Maybe[A any] struct {
value *A
}
some := func(a int) Maybe[int] {
return Maybe[int]{value: &a}
}
maybeMap := func(f func(int) Inner) func(Maybe[int]) Maybe[Inner] {
return func(ma Maybe[int]) Maybe[Inner] {
if ma.value == nil {
return Maybe[Inner]{value: nil}
}
result := f(*ma.value)
return Maybe[Inner]{value: &result}
}
}
t.Run("handles zero values", func(t *testing.T) {
ageLens := MakeLens(
func(p Inner) int { return p.Value },
func(p Inner, age int) Inner { p.Value = age; return p },
)
identity := func(n int) Maybe[int] {
return some(n)
}
modifyAge := ModifyF[Inner, int](maybeMap)
person := Inner{Value: 0, Foo: ""}
result := modifyAge(identity)(ageLens)(person)
assert.NotNil(t, result.value)
assert.Equal(t, person, *result.value)
})
t.Run("works with composed lenses", func(t *testing.T) {
innerLens := MakeLens(
Outer.GetInner,
Outer.SetInner,
)
valueLens := MakeLensRef(
(*Inner).GetValue,
(*Inner).SetValue,
)
composedLens := Compose[Outer](valueLens)(innerLens)
maybeMapOuter := func(f func(int) Outer) func(Maybe[int]) Maybe[Outer] {
return func(ma Maybe[int]) Maybe[Outer] {
if ma.value == nil {
return Maybe[Outer]{value: nil}
}
result := f(*ma.value)
return Maybe[Outer]{value: &result}
}
}
validatePositive := func(n int) Maybe[int] {
if n > 0 {
return some(n * 2)
}
return Maybe[int]{value: nil}
}
modifyValue := ModifyF[Outer, int](maybeMapOuter)
outer := Outer{inner: &Inner{Value: 5, Foo: "test"}}
result := modifyValue(validatePositive)(composedLens)(outer)
assert.NotNil(t, result.value)
assert.Equal(t, 10, result.value.inner.Value)
assert.Equal(t, "test", result.value.inner.Foo)
})
}
// TestModifyF_Integration tests integration scenarios
func TestModifyF_Integration(t *testing.T) {
type Maybe[A any] struct {
value *A
}
some := func(a int) Maybe[int] {
return Maybe[int]{value: &a}
}
maybeMap := func(f func(int) Inner) func(Maybe[int]) Maybe[Inner] {
return func(ma Maybe[int]) Maybe[Inner] {
if ma.value == nil {
return Maybe[Inner]{value: nil}
}
result := f(*ma.value)
return Maybe[Inner]{value: &result}
}
}
t.Run("chains multiple ModifyF operations", func(t *testing.T) {
ageLens := MakeLens(
func(p Inner) int { return p.Value },
func(p Inner, age int) Inner { p.Value = age; return p },
)
increment := func(n int) Maybe[int] {
return some(n + 1)
}
modifyAge := ModifyF[Inner, int](maybeMap)
person := Inner{Value: 5, Foo: "test"}
// Apply transformation twice
result1 := modifyAge(increment)(ageLens)(person)
assert.NotNil(t, result1.value)
result2 := modifyAge(increment)(ageLens)(*result1.value)
assert.NotNil(t, result2.value)
assert.Equal(t, 7, result2.value.Value)
})
t.Run("combines with regular Modify", func(t *testing.T) {
ageLens := MakeLens(
func(p Inner) int { return p.Value },
func(p Inner, age int) Inner { p.Value = age; return p },
)
// First use regular Modify
person := Inner{Value: 5, Foo: "test"}
modified := F.Pipe2(
ageLens,
Modify[Inner](func(n int) int { return n * 2 }),
func(endoFn func(Inner) Inner) Inner {
return endoFn(person)
},
)
assert.Equal(t, 10, modified.Value)
// Then use ModifyF with validation
validateRange := func(n int) Maybe[int] {
if n >= 0 && n <= 100 {
return some(n)
}
return Maybe[int]{value: nil}
}
modifyAge := ModifyF[Inner, int](maybeMap)
result := modifyAge(validateRange)(ageLens)(modified)
assert.NotNil(t, result.value)
})
}

View File

@@ -5,6 +5,7 @@ package lenses
// 2026-01-27 16:08:47.5483589 +0100 CET m=+0.003380301
import (
"net"
url "net/url"
__iso_option "github.com/IBM/fp-go/v2/optics/iso/option"
@@ -119,6 +120,8 @@ type URLLenses struct {
RawQuery __lens.Lens[url.URL, string]
Fragment __lens.Lens[url.URL, string]
RawFragment __lens.Lens[url.URL, string]
Hostname __lens.Lens[url.URL, string]
Port __lens.Lens[url.URL, string]
// optional fields
SchemeO __lens_option.LensO[url.URL, string]
OpaqueO __lens_option.LensO[url.URL, string]
@@ -131,6 +134,8 @@ type URLLenses struct {
RawQueryO __lens_option.LensO[url.URL, string]
FragmentO __lens_option.LensO[url.URL, string]
RawFragmentO __lens_option.LensO[url.URL, string]
HostnameO __lens_option.LensO[url.URL, string]
PortO __lens_option.LensO[url.URL, string]
}
// URLRefLenses provides lenses for accessing fields of url.URL via a reference to url.URL
@@ -147,6 +152,8 @@ type URLRefLenses struct {
RawQuery __lens.Lens[*url.URL, string]
Fragment __lens.Lens[*url.URL, string]
RawFragment __lens.Lens[*url.URL, string]
Hostname __lens.Lens[*url.URL, string]
Port __lens.Lens[*url.URL, string]
// optional fields
SchemeO __lens_option.LensO[*url.URL, string]
OpaqueO __lens_option.LensO[*url.URL, string]
@@ -159,6 +166,8 @@ type URLRefLenses struct {
RawQueryO __lens_option.LensO[*url.URL, string]
FragmentO __lens_option.LensO[*url.URL, string]
RawFragmentO __lens_option.LensO[*url.URL, string]
HostnameO __lens_option.LensO[*url.URL, string]
PortO __lens_option.LensO[*url.URL, string]
}
// MakeURLLenses creates a new URLLenses with lenses for all fields
@@ -219,6 +228,38 @@ func MakeURLLenses() URLLenses {
func(s url.URL, v string) url.URL { s.RawFragment = v; return s },
"URL.RawFragment",
)
lensHostname := __lens.MakeLensWithName(
func(s url.URL) string {
host, _, err := net.SplitHostPort(s.Host)
if err != nil {
return s.Host
}
return host
},
func(s url.URL, v string) url.URL {
_, port, err := net.SplitHostPort(s.Host)
if err != nil {
s.Host = v
} else {
s.Host = net.JoinHostPort(v, port)
}
return s
},
"URL.Hostname",
)
lensPort := __lens.MakeLensWithName(
func(s url.URL) string { return s.Port() },
func(s url.URL, v string) url.URL {
host, _, err := net.SplitHostPort(s.Host)
if err != nil {
s.Host = net.JoinHostPort(s.Host, v)
} else {
s.Host = net.JoinHostPort(host, v)
}
return s
},
"URL.Port",
)
// optional lenses
lensSchemeO := __lens_option.FromIso[url.URL](__iso_option.FromZero[string]())(lensScheme)
lensOpaqueO := __lens_option.FromIso[url.URL](__iso_option.FromZero[string]())(lensOpaque)
@@ -231,6 +272,8 @@ func MakeURLLenses() URLLenses {
lensRawQueryO := __lens_option.FromIso[url.URL](__iso_option.FromZero[string]())(lensRawQuery)
lensFragmentO := __lens_option.FromIso[url.URL](__iso_option.FromZero[string]())(lensFragment)
lensRawFragmentO := __lens_option.FromIso[url.URL](__iso_option.FromZero[string]())(lensRawFragment)
lensHostnameO := __lens_option.FromIso[url.URL](__iso_option.FromZero[string]())(lensHostname)
lensPortO := __lens_option.FromIso[url.URL](__iso_option.FromZero[string]())(lensPort)
return URLLenses{
// mandatory lenses
Scheme: lensScheme,
@@ -244,6 +287,8 @@ func MakeURLLenses() URLLenses {
RawQuery: lensRawQuery,
Fragment: lensFragment,
RawFragment: lensRawFragment,
Hostname: lensHostname,
Port: lensPort,
// optional lenses
SchemeO: lensSchemeO,
OpaqueO: lensOpaqueO,
@@ -256,6 +301,8 @@ func MakeURLLenses() URLLenses {
RawQueryO: lensRawQueryO,
FragmentO: lensFragmentO,
RawFragmentO: lensRawFragmentO,
HostnameO: lensHostnameO,
PortO: lensPortO,
}
}
@@ -317,6 +364,38 @@ func MakeURLRefLenses() URLRefLenses {
func(s *url.URL, v string) *url.URL { s.RawFragment = v; return s },
"(*url.URL).RawFragment",
)
lensHostname := __lens.MakeLensStrictWithName(
func(s *url.URL) string {
host, _, err := net.SplitHostPort(s.Host)
if err != nil {
return s.Host
}
return host
},
func(s *url.URL, v string) *url.URL {
_, port, err := net.SplitHostPort(s.Host)
if err != nil {
s.Host = v
} else {
s.Host = net.JoinHostPort(v, port)
}
return s
},
"URL.Hostname",
)
lensPort := __lens.MakeLensStrictWithName(
(*url.URL).Port,
func(s *url.URL, v string) *url.URL {
host, _, err := net.SplitHostPort(s.Host)
if err != nil {
s.Host = net.JoinHostPort(s.Host, v)
} else {
s.Host = net.JoinHostPort(host, v)
}
return s
},
"URL.Port",
)
// optional lenses
lensSchemeO := __lens_option.FromIso[*url.URL](__iso_option.FromZero[string]())(lensScheme)
lensOpaqueO := __lens_option.FromIso[*url.URL](__iso_option.FromZero[string]())(lensOpaque)
@@ -329,6 +408,8 @@ func MakeURLRefLenses() URLRefLenses {
lensRawQueryO := __lens_option.FromIso[*url.URL](__iso_option.FromZero[string]())(lensRawQuery)
lensFragmentO := __lens_option.FromIso[*url.URL](__iso_option.FromZero[string]())(lensFragment)
lensRawFragmentO := __lens_option.FromIso[*url.URL](__iso_option.FromZero[string]())(lensRawFragment)
lensHostnameO := __lens_option.FromIso[*url.URL](__iso_option.FromZero[string]())(lensHostname)
lensPortO := __lens_option.FromIso[*url.URL](__iso_option.FromZero[string]())(lensPort)
return URLRefLenses{
// mandatory lenses
Scheme: lensScheme,
@@ -342,6 +423,8 @@ func MakeURLRefLenses() URLRefLenses {
RawQuery: lensRawQuery,
Fragment: lensFragment,
RawFragment: lensRawFragment,
Hostname: lensHostname,
Port: lensPort,
// optional lenses
SchemeO: lensSchemeO,
OpaqueO: lensOpaqueO,
@@ -354,6 +437,8 @@ func MakeURLRefLenses() URLRefLenses {
RawQueryO: lensRawQueryO,
FragmentO: lensFragmentO,
RawFragmentO: lensRawFragmentO,
HostnameO: lensHostnameO,
PortO: lensPortO,
}
}

File diff suppressed because it is too large Load Diff