1
0
mirror of https://github.com/IBM/fp-go.git synced 2026-04-09 15:26:02 +02:00

Compare commits

...

4 Commits

Author SHA1 Message Date
Dr. Carsten Leue
57318e2d1d fix: make use of Empty lazy
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-04-08 15:00:39 +02:00
Dr. Carsten Leue
2b937d3e93 doc: add marble diagrams
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2026-03-30 10:15:27 +02:00
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
17 changed files with 1639 additions and 20 deletions

View File

@@ -323,34 +323,31 @@ func Clone[AS ~[]A, A any](f func(A) A) func(as AS) AS {
}
func FoldMap[AS ~[]A, A, B any](m M.Monoid[B]) func(func(A) B) func(AS) B {
empty := m.Empty()
concat := m.Concat
return func(f func(A) B) func(AS) B {
return func(as AS) B {
return array.Reduce(as, func(cur B, a A) B {
return concat(cur, f(a))
}, empty)
}, m.Empty())
}
}
}
func FoldMapWithIndex[AS ~[]A, A, B any](m M.Monoid[B]) func(func(int, A) B) func(AS) B {
empty := m.Empty()
concat := m.Concat
return func(f func(int, A) B) func(AS) B {
return func(as AS) B {
return array.ReduceWithIndex(as, func(idx int, cur B, a A) B {
return concat(cur, f(idx, a))
}, empty)
}, m.Empty())
}
}
}
func Fold[AS ~[]A, A any](m M.Monoid[A]) func(AS) A {
empty := m.Empty()
concat := m.Concat
return func(as AS) A {
return array.Reduce(as, concat, empty)
return array.Reduce(as, concat, m.Empty())
}
}

View File

@@ -25,7 +25,7 @@ func MonadSequence[HKTA, HKTRA any](
fof func(HKTA) HKTRA,
m M.Monoid[HKTRA],
ma []HKTA) HKTRA {
return array.MonadSequence(fof, m.Empty(), m.Concat, ma)
return array.MonadSequence(fof, m.Empty, m.Concat, ma)
}
// Sequence takes an array where elements are HKT<A> (higher kinded type) and,
@@ -67,7 +67,7 @@ func Sequence[HKTA, HKTRA any](
fof func(HKTA) HKTRA,
m M.Monoid[HKTRA],
) func([]HKTA) HKTRA {
return array.Sequence[[]HKTA](fof, m.Empty(), m.Concat)
return array.Sequence[[]HKTA](fof, m.Empty, m.Concat)
}
// ArrayOption returns a function to convert a sequence of options into an option of a sequence.

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

@@ -4,10 +4,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/urfave/cli/v3 v3.6.2 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8=
github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/urfave/cli/v3 v3.7.0 h1:AGSnbUyjtLiM+WJUb4dzXKldl/gL+F8OwmRDtVr6g2U=
github.com/urfave/cli/v3 v3.7.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI=
github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -46,7 +46,7 @@ import (
// - Multiple elements: recursively divides and conquers
func MonadSequenceSegment[HKTB, HKTRB any](
fof func(HKTB) HKTRB,
empty HKTRB,
empty func() HKTRB,
concat func(HKTRB, HKTRB) HKTRB,
fbs []HKTB,
start, end int,
@@ -54,7 +54,7 @@ func MonadSequenceSegment[HKTB, HKTRB any](
switch end - start {
case 0:
return empty
return empty()
case 1:
return fof(fbs[start])
default:
@@ -254,7 +254,7 @@ HKTAB = HKT<func(A)B>
*/
func MonadSequence[GA ~[]HKTA, HKTA, HKTRA any](
fof func(HKTA) HKTRA,
empty HKTRA,
empty func() HKTRA,
concat func(HKTRA, HKTRA) HKTRA,
ta GA) HKTRA {
@@ -263,7 +263,7 @@ func MonadSequence[GA ~[]HKTA, HKTA, HKTRA any](
func Sequence[GA ~[]HKTA, HKTA, HKTRA any](
fof func(HKTA) HKTRA,
empty HKTRA,
empty func() HKTRA,
concat func(HKTRA, HKTRA) HKTRA,
) func(GA) HKTRA {

View File

@@ -73,7 +73,7 @@ func MonadTraverse[GA ~func(yield func(A) bool), GB ~func(yield func(B) bool), A
fof := F.Bind2nd(fmap_b, Of[GB])
empty := fof_gb(Empty[GB]())
empty := F.Nullary2(Empty[GB], fof_gb)
cb := F.Curry2(Concat[GB])
concat_gb := F.Bind2nd(fmap_gb, cb)
@@ -180,7 +180,7 @@ func MonadSequence[GA ~func(yield func(HKTA) bool), HKTA, HKTRA any](
// convert to an array
hktb := ToArray[GA, []HKTA](ta)
return INTA.MonadSequenceSegment(fof, m.Empty(), m.Concat, hktb, 0, len(hktb))
return INTA.MonadSequenceSegment(fof, m.Empty, m.Concat, hktb, 0, len(hktb))
}
// MonadTraverseWithIndex traverses an iterator sequence with index tracking, applying an effectful
@@ -223,7 +223,7 @@ func MonadTraverseWithIndex[GA ~func(yield func(A) bool), A, HKTB, HKTRB any](
// convert to an array
hktb := MonadMapToArrayWithIndex[GA, []HKTB](ta, f)
return INTA.MonadSequenceSegment(fof, m.Empty(), m.Concat, hktb, 0, len(hktb))
return INTA.MonadSequenceSegment(fof, m.Empty, m.Concat, hktb, 0, len(hktb))
}
// Sequence is the curried version of MonadSequence, returning a function that sequences an iterator of effects.

View File

@@ -34,6 +34,13 @@ import (
// 3. Filtering to keep only pairs where the boolean (tail) is true
// 4. Extracting the original values (head) from the filtered pairs
//
// Marble Diagram:
//
// Data: --1--2--3--4--5-->
// Selectors: --T--F--T--F--T-->
// Compress
// Output: --1-----3-----5-->
//
// RxJS Equivalent: Similar to combining [zip] with [filter] - https://rxjs.dev/api/operators/zip
//
// Type Parameters:

View File

@@ -21,6 +21,12 @@ package iter
// all elements repeatedly. When the end of the input sequence is reached, it starts over
// from the beginning, continuing this pattern forever.
//
// Marble Diagram:
//
// Input: --1--2--3|
// Cycle
// Output: --1--2--3--1--2--3--1--2--3--> (infinite)
//
// RxJS Equivalent: [repeat] - https://rxjs.dev/api/operators/repeat
//
// WARNING: This creates an INFINITE sequence for non-empty inputs. It must be used with

View File

@@ -23,6 +23,16 @@ import "github.com/IBM/fp-go/v2/option"
// contains at least one element, it returns Some(element). If the iterator is empty,
// it returns None. The function consumes only the first element of the iterator.
//
// Marble Diagram:
//
// Input: --1--2--3--4--5-->
// First
// Output: --Some(1)|
//
// Input: --|
// First
// Output: --None|
//
// RxJS Equivalent: [first] - https://rxjs.dev/api/operators/first
//
// Type Parameters:

View File

@@ -82,6 +82,12 @@ func Of2[K, A any](k K, a A) Seq2[K, A] {
// MonadMap transforms each element in a sequence using the provided function.
// This is the monadic version that takes the sequence as the first parameter.
//
// Marble Diagram:
//
// Input: --1--2--3-->
// Map(x => x * 2)
// Output: --2--4--6-->
//
// RxJS Equivalent: [map] - https://rxjs.dev/api/operators/map
//
// Example:
@@ -186,6 +192,12 @@ func MapWithKey[K, A, B any](f func(K, A) B) Operator2[K, A, B] {
// MonadFilter returns a sequence containing only elements that satisfy the predicate.
//
// Marble Diagram:
//
// Input: --1--2--3--4--5-->
// Filter(x => x % 2 == 0)
// Output: -----2-----4----->
//
// RxJS Equivalent: [filter] - https://rxjs.dev/api/operators/filter
//
// Example:
@@ -293,6 +305,12 @@ func FilterWithKey[K, A any](pred func(K, A) bool) Operator2[K, A, A] {
// MonadFilterMap applies a function that returns an Option to each element,
// keeping only the Some values and unwrapping them.
//
// Marble Diagram:
//
// Input: --1--2--3--4--5-->
// FilterMap(x => x % 2 == 0 ? Some(x * 10) : None)
// Output: -----20----40---->
//
// Example:
//
// seq := From(1, 2, 3, 4, 5)
@@ -430,6 +448,12 @@ func FilterMapWithKey[K, A, B any](f func(K, A) Option[B]) Operator2[K, A, B] {
// MonadChain applies a function that returns a sequence to each element and flattens the results.
// This is the monadic bind operation (flatMap).
//
// Marble Diagram:
//
// Input: --1-----2-----3---->
// Chain(x => [x, x*10])
// Output: --1-10--2-20--3-30->
//
// RxJS Equivalent: [mergeMap/flatMap] - https://rxjs.dev/api/operators/mergeMap
//
// Example:
@@ -473,6 +497,12 @@ func FlatMap[A, B any](f func(A) Seq[B]) Operator[A, B] {
// Flatten flattens a sequence of sequences into a single sequence.
//
// Marble Diagram:
//
// Input: --[1,2]--[3,4]--[5]-->
// Flatten
// Output: --1-2----3-4----5---->
//
// RxJS Equivalent: [mergeAll] - https://rxjs.dev/api/operators/mergeAll
//
// Example:
@@ -489,6 +519,14 @@ func Flatten[A any](mma Seq[Seq[A]]) Seq[A] {
// MonadAp applies a sequence of functions to a sequence of values.
// This is the applicative apply operation.
//
// Marble Diagram:
//
// Functions: --(*2)---(+10)-->
// Values: --5------3------>
// Ap
// Output: --10-6---15-13-->
// (each function applied to each value)
//
// Example:
//
// fns := From(N.Mul(2), N.Add(10))
@@ -577,6 +615,13 @@ func Replicate[A any](n int, a A) Seq[A] {
// MonadReduce reduces a sequence to a single value by applying a function to each element
// and an accumulator, starting with an initial value.
//
// Marble Diagram:
//
// Input: --1--2--3--4--5--|
// Reduce((acc, x) => acc + x, 0)
// Output: ------------------15|
// (emits final result only)
//
// RxJS Equivalent: [reduce] - https://rxjs.dev/api/operators/reduce
//
// Example:
@@ -811,6 +856,13 @@ func FoldMapWithKey[K, A, B any](m M.Monoid[B]) func(func(K, A) B) func(Seq2[K,
// MonadFlap applies a fixed value to a sequence of functions.
// This is the dual of MonadAp.
//
// Marble Diagram:
//
// Functions: --(*2)---(+10)-->
// Value: 5 (fixed)
// Flap
// Output: --10-----15----->
//
// Example:
//
// fns := From(N.Mul(2), N.Add(10))
@@ -832,6 +884,12 @@ func Flap[B, A any](a A) Operator[func(A) B, B] {
// Prepend returns a function that adds an element to the beginning of a sequence.
//
// Marble Diagram:
//
// Input: -----2--3--4-->
// Prepend(1)
// Output: --1--2--3--4-->
//
// RxJS Equivalent: [startWith] - https://rxjs.dev/api/operators/startWith
//
// Example:
@@ -847,6 +905,12 @@ func Prepend[A any](head A) Operator[A, A] {
// Append returns a function that adds an element to the end of a sequence.
//
// Marble Diagram:
//
// Input: --1--2--3-----|
// Append(4)
// Output: --1--2--3--4--|
//
// RxJS Equivalent: [endWith] - https://rxjs.dev/api/operators/endWith
//
// Example:
@@ -863,6 +927,14 @@ func Append[A any](tail A) Operator[A, A] {
// MonadZip combines two sequences into a sequence of pairs.
// The resulting sequence stops when either input sequence is exhausted.
//
// Marble Diagram:
//
// SeqA: --1--2--3---->
// SeqB: --a--b------->
// Zip
// Output: --(1,a)-(2,b)|
// (stops when shorter sequence ends)
//
// RxJS Equivalent: [zip] - https://rxjs.dev/api/operators/zip
//
// Example:
@@ -1079,3 +1151,61 @@ func FromSeqPair[A, B any](as Seq[Pair[A, B]]) Seq2[A, B] {
}
}
}
// Skip returns an operator that skips the first n elements of a sequence.
//
// This function creates a transformation that discards the first n elements from
// the source sequence and yields all remaining elements. If n is less than or equal
// to 0, all elements are yielded. If n is greater than or equal to the sequence length,
// an empty sequence is returned.
//
// The operation is lazy and only consumes elements from the source sequence as needed.
// The first n elements are consumed and discarded, then subsequent elements are yielded.
//
// Marble Diagram:
//
// Input: --1--2--3--4--5--6--7--8-->
// Skip(3)
// Output: -----------4--5--6--7--8-->
//
// RxJS Equivalent: [skip] - https://rxjs.dev/api/operators/skip
//
// Type Parameters:
// - U: The type of elements in the sequence
//
// Parameters:
// - count: The number of elements to skip from the beginning of the sequence
//
// Returns:
// - An Operator that transforms a Seq[U] by skipping the first count elements
//
// Example - Skip first 3 elements:
//
// seq := From(1, 2, 3, 4, 5)
// result := Skip[int](3)(seq)
// // yields: 4, 5
//
// Example - Skip more than available:
//
// seq := From(1, 2)
// result := Skip[int](5)(seq)
// // yields: nothing (empty sequence)
//
// Example - Skip zero or negative:
//
// seq := From(1, 2, 3)
// result := Skip[int](0)(seq)
// // yields: 1, 2, 3 (all elements)
//
// Example - Chaining with other operations:
//
// seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// result := F.Pipe2(
// seq,
// Skip[int](3),
// MonadFilter(seq, func(x int) bool { return x%2 == 0 }),
// )
// // yields: 4, 6, 8, 10 (skip first 3, then filter evens)
func Skip[U any](count int) Operator[U, U] {
return FilterWithIndex(func(idx int, _ U) bool { return idx >= count })
}

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

@@ -10,6 +10,16 @@ import (
// sequence. If the iterator contains at least one element, it returns Some(element).
// If the iterator is empty, it returns None.
//
// Marble Diagram:
//
// Input: --1--2--3--4--5--|
// Last
// Output: -----------------Some(5)|
//
// Input: --|
// Last
// Output: --None|
//
// RxJS Equivalent: [last] - https://rxjs.dev/api/operators/last
//
// Type Parameters:

View File

@@ -28,6 +28,13 @@ import (
//
// This is the monadic form that takes the sequence as the first parameter.
//
// Marble Diagram:
//
// Input: --1--2--3--4--5-->
// ChainOptionK(x => x % 2 == 0 ? Some(x * 10) : None)
// Output: -----20----40---->
// (filters and transforms)
//
// RxJS Equivalent: [concatMap] combined with [filter] - https://rxjs.dev/api/operators/concatMap
//
// Type parameters:
@@ -72,6 +79,13 @@ func MonadChainOptionK[A, B any](as Seq[A], f option.Kleisli[A, B]) Seq[B] {
// This is the curried version of [MonadChainOptionK], useful for function composition
// and creating reusable transformations.
//
// Marble Diagram:
//
// Input: --1--2--3--4--5-->
// ChainOptionK(x => x > 2 ? Some(x) : None)
// Output: --------3--4--5-->
// (filters out values <= 2)
//
// RxJS Equivalent: [concatMap] combined with [filter] - https://rxjs.dev/api/operators/concatMap
//
// Type parameters:

View File

@@ -24,6 +24,13 @@ package iter
//
// The operation is lazy - intermediate values are computed only as they are consumed.
//
// Marble Diagram:
//
// Input: --1--2--3--4--5-->
// Scan((acc, x) => acc + x, 0)
// Output: --1--3--6--10-15->
// (running sum)
//
// RxJS Equivalent: [scan] - https://rxjs.dev/api/operators/scan
//
// Scan is useful for:

View File

@@ -27,6 +27,12 @@ import F "github.com/IBM/fp-go/v2/function"
// Once n elements have been yielded, iteration stops immediately without consuming
// the remaining elements from the source.
//
// Marble Diagram:
//
// Input: --1--2--3--4--5--6--7--8-->
// Take(3)
// Output: --1--2--3|
//
// RxJS Equivalent: [take] - https://rxjs.dev/api/operators/take
//
// Type Parameters:
@@ -78,3 +84,158 @@ func Take[U any](n int) Operator[U, U] {
}
}
}
// TakeWhile returns an operator that emits elements from a sequence while a predicate is satisfied.
//
// This function creates a transformation that yields elements from the source sequence
// as long as each element satisfies the provided predicate. Once an element fails the
// predicate test, the sequence terminates immediately, and no further elements are
// emitted, even if subsequent elements would satisfy the predicate.
//
// The operation is lazy and only consumes elements from the source sequence as needed.
// Once the predicate returns false, iteration stops immediately without consuming
// the remaining elements from the source.
//
// Marble Diagram:
//
// Input: --1--2--3--4--5--2--1-->
// TakeWhile(x < 4)
// Output: --1--2--3|
// (stops at 4)
//
// RxJS Equivalent: [takeWhile] - https://rxjs.dev/api/operators/takeWhile
//
// Type Parameters:
// - U: The type of elements in the sequence
//
// Parameters:
// - p: A predicate function that tests each element. Returns true to continue, false to stop
//
// Returns:
// - An Operator that transforms a Seq[U] by taking elements while the predicate is satisfied
//
// Example - Take while less than threshold:
//
// seq := From(1, 2, 3, 4, 5, 2, 1)
// result := TakeWhile(func(x int) bool { return x < 4 })(seq)
// // yields: 1, 2, 3 (stops at 4, doesn't continue to 2, 1)
//
// Example - Take while condition is met:
//
// seq := From("a", "b", "c", "1", "d", "e")
// isLetter := func(s string) bool { return s >= "a" && s <= "z" }
// result := TakeWhile(isLetter)(seq)
// // yields: "a", "b", "c" (stops at "1")
//
// Example - Take all when predicate always true:
//
// seq := From(2, 4, 6, 8)
// result := TakeWhile(func(x int) bool { return x%2 == 0 })(seq)
// // yields: 2, 4, 6, 8 (all elements satisfy predicate)
//
// Example - Take none when first element fails:
//
// seq := From(5, 1, 2, 3)
// result := TakeWhile(func(x int) bool { return x < 5 })(seq)
// // yields: nothing (first element fails predicate)
//
// Example - Chaining with other operations:
//
// seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// result := F.Pipe2(
// seq,
// MonadMap(seq, func(x int) int { return x * 2 }),
// TakeWhile(func(x int) bool { return x < 10 }),
// )
// // yields: 2, 4, 6, 8 (stops when doubled value reaches 10)
func TakeWhile[U any](p Predicate[U]) Operator[U, U] {
return func(s Seq[U]) Seq[U] {
return func(yield func(U) bool) {
for u := range s {
if !p(u) || !yield(u) {
return
}
}
}
}
}
// SkipWhile returns an operator that skips elements from a sequence while a predicate is satisfied.
//
// This function creates a transformation that discards elements from the source sequence
// as long as each element satisfies the provided predicate. Once an element fails the
// predicate test, that element and all subsequent elements are yielded, regardless of
// whether they satisfy the predicate.
//
// The operation is lazy and only consumes elements from the source sequence as needed.
// Once the predicate returns false, all remaining elements are yielded without further
// predicate evaluation.
//
// Marble Diagram:
//
// Input: --1--2--3--4--5--2--1-->
// SkipWhile(x < 4)
// Output: -----------4--5--2--1-->
// (starts at 4, continues with all)
//
// RxJS Equivalent: [skipWhile] - https://rxjs.dev/api/operators/skipWhile
//
// Type Parameters:
// - U: The type of elements in the sequence
//
// Parameters:
// - p: A predicate function that tests each element. Returns true to skip, false to start yielding
//
// Returns:
// - An Operator that transforms a Seq[U] by skipping elements while the predicate is satisfied
//
// Example - Skip while less than threshold:
//
// seq := From(1, 2, 3, 4, 5, 2, 1)
// result := SkipWhile(func(x int) bool { return x < 4 })(seq)
// // yields: 4, 5, 2, 1 (starts at 4, continues with all remaining)
//
// Example - Skip while condition is met:
//
// seq := From("a", "b", "c", "1", "d", "e")
// isLetter := func(s string) bool { return s >= "a" && s <= "z" }
// result := SkipWhile(isLetter)(seq)
// // yields: "1", "d", "e" (starts at "1", continues with all remaining)
//
// Example - Skip none when first element fails:
//
// seq := From(5, 1, 2, 3)
// result := SkipWhile(func(x int) bool { return x < 5 })(seq)
// // yields: 5, 1, 2, 3 (first element fails predicate, all yielded)
//
// Example - Skip all when predicate always true:
//
// seq := From(2, 4, 6, 8)
// result := SkipWhile(func(x int) bool { return x%2 == 0 })(seq)
// // yields: nothing (all elements satisfy predicate)
//
// Example - Chaining with other operations:
//
// seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// result := F.Pipe2(
// seq,
// SkipWhile(func(x int) bool { return x < 5 }),
// MonadMap(seq, func(x int) int { return x * 2 }),
// )
// // yields: 10, 12, 14, 16, 18, 20 (skip until 5, then double remaining)
func SkipWhile[U any](p Predicate[U]) Operator[U, U] {
return func(s Seq[U]) Seq[U] {
return func(yield func(U) bool) {
skipping := true
for u := range s {
if skipping && p(u) {
continue
}
skipping = false
if !yield(u) {
return
}
}
}
}
}

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

@@ -32,6 +32,13 @@ import (
// the number of unique keys encountered. The operation is lazy - elements are processed
// and filtered as they are consumed.
//
// Marble Diagram:
//
// Input: --1--2--3--2--4--1--5-->
// Uniq(identity)
// Output: --1--2--3-----4-----5-->
// (first occurrence only)
//
// RxJS Equivalent: [distinct] - https://rxjs.dev/api/operators/distinct
//
// Type Parameters:
@@ -119,6 +126,13 @@ func Uniq[A any, K comparable](f func(A) K) Operator[A, A] {
// The operation maintains a map of seen elements internally, so memory usage grows with
// the number of unique elements. Only the first occurrence of each unique element is kept.
//
// Marble Diagram:
//
// Input: --1--2--3--2--4--1--5-->
// StrictUniq
// Output: --1--2--3-----4-----5-->
// (first occurrence only)
//
// RxJS Equivalent: [distinct] - https://rxjs.dev/api/operators/distinct
//
// Type Parameters: