Skip to content

Commit 794e35a

Browse files
authored
Allow iterators to be cloned (#33)
Add `Iterator.Clone` method so iterators that support it can be cloned
1 parent 050a39d commit 794e35a

File tree

7 files changed

+67
-2
lines changed

7 files changed

+67
-2
lines changed

iterator.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
package fun
22

3+
import "errors"
4+
35
// Iterator allows you to iterate over collections.
46
type Iterator[T any] interface {
57
Next() bool
68
Value() T
9+
Clone() Result[Iterator[T]]
710
}
811

12+
// ErrIteratorNotCloneable is returned by iterators that can't be cloned.
13+
var ErrIteratorNotCloneable = errors.New("iterator is not cloneable")
14+
915
// Map applies a mapper function to every element of an iterator an returns a slice.
1016
func Map[T, R any](iter Iterator[T], mapper func(T) R) []R {
1117
results := []R{}

iterator/filter.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package iterator
22

33
import (
44
"github.com/sagmor/fun"
5+
"github.com/sagmor/fun/result"
56
)
67

78
type filteredIterator[T any] struct {
@@ -27,6 +28,16 @@ func (iter *filteredIterator[T]) Value() T {
2728
return iter.iterator.Value()
2829
}
2930

31+
// Clone implements fun.Iterator.
32+
func (iter *filteredIterator[T]) Clone() fun.Result[fun.Iterator[T]] {
33+
return result.Step(
34+
iter.iterator.Clone(),
35+
func(cloned fun.Iterator[T]) (fun.Iterator[T], error) {
36+
return WithFilter(cloned, iter.filter), nil
37+
},
38+
)
39+
}
40+
3041
// WithFilter creates an iterator that filter values as it's called.
3142
func WithFilter[T any](iter fun.Iterator[T], filter func(T) bool) fun.Iterator[T] {
3243
return &filteredIterator[T]{

iterator/slice.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
// Package iterator implements functions to work with fun.Iterator[T] types.
22
package iterator
33

4-
import "github.com/sagmor/fun"
4+
import (
5+
"github.com/sagmor/fun"
6+
"github.com/sagmor/fun/result"
7+
)
58

69
type sliceIterator[T any] struct {
710
values []T
@@ -20,6 +23,11 @@ func (i *sliceIterator[T]) Value() T {
2023
return i.values[i.current]
2124
}
2225

26+
// Clone implements fun.Iterator.
27+
func (i *sliceIterator[T]) Clone() fun.Result[fun.Iterator[T]] {
28+
return result.Success(FromSlice[T](i.values))
29+
}
30+
2331
// FromSlice builds an iterator for a slice.
2432
func FromSlice[T any](slice []T) fun.Iterator[T] {
2533
return &sliceIterator[T]{
@@ -45,6 +53,11 @@ func (i *revertSliceIterator[T]) Value() T {
4553
return i.values[i.current]
4654
}
4755

56+
// Clone implements fun.Iterator.
57+
func (i *revertSliceIterator[T]) Clone() fun.Result[fun.Iterator[T]] {
58+
return result.Success(FromRevertSlice[T](i.values))
59+
}
60+
4861
// FromRevertSlice builds an iterator for a slice that iterates backwards.
4962
func FromRevertSlice[T any](slice []T) fun.Iterator[T] {
5063
return &revertSliceIterator[T]{

iterator/transforming.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package iterator
22

33
import (
44
"github.com/sagmor/fun"
5+
"github.com/sagmor/fun/result"
56
)
67

78
type transformingIterator[From, To any] struct {
@@ -19,6 +20,16 @@ func (iter *transformingIterator[From, To]) Value() To {
1920
return iter.transform(iter.iterator.Value())
2021
}
2122

23+
// Clone implements fun.Iterator.
24+
func (iter *transformingIterator[From, To]) Clone() fun.Result[fun.Iterator[To]] {
25+
return result.Step(
26+
iter.iterator.Clone(),
27+
func(clone fun.Iterator[From]) (fun.Iterator[To], error) {
28+
return WithTransform(clone, iter.transform), nil
29+
},
30+
)
31+
}
32+
2233
// WithTransform creates an iterator that transforms values as it's called.
2334
func WithTransform[From, To any](iter fun.Iterator[From], transform func(From) To) fun.Iterator[To] {
2435
return &transformingIterator[From, To]{

promise/iterator.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package promise
22

33
import (
44
"github.com/sagmor/fun"
5+
"github.com/sagmor/fun/result"
56
)
67

78
type promiseIterator[T any] struct {
@@ -28,6 +29,11 @@ func (iter *promiseIterator[T]) Value() fun.Result[T] {
2829
return iter.promises[iter.current].Result()
2930
}
3031

32+
// Clone implements fun.Iterator.
33+
func (*promiseIterator[T]) Clone() fun.Result[fun.Iterator[fun.Result[T]]] {
34+
return result.Failure[fun.Iterator[fun.Result[T]]](fun.ErrIteratorNotCloneable)
35+
}
36+
3137
// All iterates over all promises as they are resolved.
3238
func All[T any](promises ...fun.Promise[T]) fun.Iterator[fun.Result[T]] {
3339
iter := &promiseIterator[T]{

tests/iterator_test.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ func TestIteratorFromRevertSlice(t *testing.T) {
3737
assert.True(t, it.Next())
3838
assert.Equal(t, 2, it.Value())
3939

40+
cloned := it.Clone().RequireValue()
41+
assert.True(t, cloned.Next())
42+
assert.Equal(t, 3, cloned.Value())
43+
4044
assert.True(t, it.Next())
4145
assert.Equal(t, 1, it.Value())
4246

@@ -86,6 +90,10 @@ func TestIteratorWithTransform(t *testing.T) {
8690
assert.True(t, it.Next())
8791
assert.Equal(t, "2", it.Value())
8892

93+
cloned := it.Clone().RequireValue()
94+
assert.True(t, cloned.Next())
95+
assert.Equal(t, "1", cloned.Value())
96+
8997
assert.True(t, it.Next())
9098
assert.Equal(t, "3", it.Value())
9199

@@ -96,7 +104,7 @@ func TestIteratorWithFilter(t *testing.T) {
96104
t.Parallel()
97105

98106
it := iterator.WithFilter(
99-
iterator.FromSlice([]int{1, 2, 3, 4, 5}),
107+
iterator.FromSlice([]int{1, 2, 3, 4, 5, 6}),
100108
func(i int) bool {
101109
return i%2 == 0
102110
},
@@ -107,5 +115,12 @@ func TestIteratorWithFilter(t *testing.T) {
107115
assert.True(t, it.Next())
108116
assert.Equal(t, 4, it.Value())
109117

118+
cloned := it.Clone().RequireValue()
119+
assert.True(t, cloned.Next())
120+
assert.Equal(t, 2, cloned.Value())
121+
122+
assert.True(t, it.Next())
123+
assert.Equal(t, 6, it.Value())
124+
110125
assert.False(t, it.Next())
111126
}

tests/promise_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ func TestPromiseAll(t *testing.T) {
102102
assert.True(t, all.Next())
103103
assert.Equal(t, 2, all.Value().RequireValue())
104104

105+
cloned := all.Clone()
106+
assert.Error(t, cloned.Error())
107+
105108
assert.True(t, all.Next())
106109
assert.Equal(t, 3, all.Value().RequireValue())
107110

0 commit comments

Comments
 (0)