Skip to content

Commit 2647a46

Browse files
authored
Adapt project to current 1.23 iter API (#106)
Adapt project to current 1.23 iter API Please provide a brief description of the change. - All iterators now natively return and use the Seq types rather than this projects type alias, favouring compatibility with the standard library over convenience features like chaining. - Added iterators for the Seq2 variants of existing iterators - Renamed Lift to Values and moved to future shadow package slices (to be deleted when Go 1.23 is released) - Renamed LiftHashMap to All and moved to future shadow package maps (to be deleted when Go 1.23 is released)
1 parent 0dff3ac commit 2647a46

24 files changed

+572
-278
lines changed

future/maps/maps.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Package maps provides early implementations of map-related functions
2+
// available in Go 1.23+.
3+
package maps
4+
5+
import "iter"
6+
7+
// All returns an iterator over key-value pairs from m. The iteration order is
8+
// not specified and is not guaranteed to be the same from one call to the
9+
// next.
10+
func All[Map ~map[K]V, K comparable, V any](m Map) iter.Seq2[K, V] {
11+
return func(yield func(K, V) bool) {
12+
for key, value := range m {
13+
if !yield(key, value) {
14+
return
15+
}
16+
}
17+
}
18+
}
19+
20+
// Collect collects key-value pairs from seq into a new map and returns it.
21+
func Collect[K comparable, V any](seq iter.Seq2[K, V]) map[K]V {
22+
m := make(map[K]V)
23+
24+
for key, value := range seq {
25+
m[key] = value
26+
}
27+
28+
return m
29+
}

future/maps/maps_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package maps_test
2+
3+
import (
4+
"fmt"
5+
"iter"
6+
"sort"
7+
"testing"
8+
9+
"github.com/BooleanCat/go-functional/v2/future/maps"
10+
"github.com/BooleanCat/go-functional/v2/internal/assert"
11+
)
12+
13+
func ExampleAll() {
14+
for key, value := range maps.All(map[int]string{1: "one", 2: "two", 3: "three"}) {
15+
fmt.Println(key, value)
16+
}
17+
}
18+
19+
type keyValuePair[K comparable, V any] struct {
20+
key K
21+
value V
22+
}
23+
24+
func TestAll(t *testing.T) {
25+
t.Parallel()
26+
27+
values := make([]keyValuePair[int, string], 0, 3)
28+
29+
for key, value := range maps.All(map[int]string{1: "one", 2: "two", 3: "three"}) {
30+
values = append(values, keyValuePair[int, string]{key, value})
31+
}
32+
33+
sort.Slice(values, func(i, j int) bool {
34+
return values[i].key < values[j].key
35+
})
36+
37+
assert.SliceEqual(t, values, []keyValuePair[int, string]{
38+
{1, "one"},
39+
{2, "two"},
40+
{3, "three"},
41+
})
42+
}
43+
44+
func TestAllEmpty(t *testing.T) {
45+
t.Parallel()
46+
47+
assert.Equal(t, len(maps.Collect(maps.All(map[int]string{}))), 0)
48+
}
49+
50+
func TestAllTerminateEarly(t *testing.T) {
51+
t.Parallel()
52+
53+
_, stop := iter.Pull2(maps.All(map[int]string{1: "one", 2: "two", 3: "three"}))
54+
stop()
55+
}
56+
57+
func ExampleCollect() {
58+
numbers := maps.Collect(maps.All(map[int]string{1: "one", 2: "two"}))
59+
60+
fmt.Println(numbers[0])
61+
fmt.Println(numbers[1])
62+
// Output
63+
// one
64+
// two
65+
}
66+
67+
func TestCollect(t *testing.T) {
68+
t.Parallel()
69+
70+
numbers := maps.Collect(maps.All(map[int]string{1: "one", 2: "two"}))
71+
72+
assert.Equal(t, numbers[1], "one")
73+
assert.Equal(t, numbers[2], "two")
74+
}
75+
76+
func TestCollectEmpty(t *testing.T) {
77+
t.Parallel()
78+
79+
assert.Equal(t, len(maps.Collect(maps.All(map[int]string{}))), 0)
80+
}

future/slices/slices.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Package slices provides early implementations of slice-related functions
2+
// available in Go 1.23+.
3+
package slices
4+
5+
import "iter"
6+
7+
// Values returns an iterator over the slice elements, starting with s[0].
8+
func Values[Slice ~[]E, E any](slice Slice) iter.Seq[E] {
9+
return func(yield func(E) bool) {
10+
for _, value := range slice {
11+
if !yield(value) {
12+
return
13+
}
14+
}
15+
}
16+
}
17+
18+
// Collect collects values from seq into a new slice and returns it.
19+
func Collect[E any](seq iter.Seq[E]) []E {
20+
slice := make([]E, 0)
21+
22+
for item := range seq {
23+
slice = append(slice, item)
24+
}
25+
26+
return slice
27+
}

future/slices/slices_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package slices_test
2+
3+
import (
4+
"fmt"
5+
"iter"
6+
"testing"
7+
8+
"github.com/BooleanCat/go-functional/v2/future/slices"
9+
"github.com/BooleanCat/go-functional/v2/internal/assert"
10+
)
11+
12+
func ExampleValues() {
13+
for number := range slices.Values([]int{1, 2, 3}) {
14+
fmt.Println(number)
15+
}
16+
17+
// Output:
18+
// 1
19+
// 2
20+
// 3
21+
}
22+
23+
func TestValuesEmpty(t *testing.T) {
24+
t.Parallel()
25+
26+
for number := range slices.Values([]int{}) {
27+
t.Error("unexpected", number)
28+
}
29+
}
30+
31+
func TestValuesTerminateEarly(t *testing.T) {
32+
t.Parallel()
33+
34+
_, stop := iter.Pull(slices.Values([]int{1, 2, 3}))
35+
stop()
36+
}
37+
38+
func ExampleCollect() {
39+
fmt.Println(slices.Collect(slices.Values([]int{1, 2, 3})))
40+
// Output: [1 2 3]
41+
}
42+
43+
func TestCollectEmpty(t *testing.T) {
44+
t.Parallel()
45+
46+
assert.Empty[int](t, slices.Collect(slices.Values([]int{})))
47+
}

internal/assert/assert.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func EqualElements[T comparable](t *testing.T, a, b []T) {
6565
}
6666
}
6767

68-
func Empty[S any, T []S | ~string](t *testing.T, items T) {
68+
func Empty[E any, Slice ~[]E | ~string](t *testing.T, items Slice) {
6969
t.Helper()
7070

7171
if len(items) != 0 {

iter/count.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ package iter
33
import "iter"
44

55
// Count yields all non-negative integers in ascending order.
6-
func Count() Iterator[int] {
7-
return Iterator[int](iter.Seq[int](func(yield func(int) bool) {
8-
for i := 0; ; i++ {
6+
func Count[V ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~int | ~int8 | ~int16 | ~int32 | ~int64]() iter.Seq[V] {
7+
return func(yield func(V) bool) {
8+
var i V
9+
10+
for {
911
if !yield(i) {
1012
return
1113
}
14+
i++
1215
}
13-
}))
16+
}
1417
}

iter/count_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
)
1010

1111
func ExampleCount() {
12-
for i := range iter.Count() {
12+
for i := range iter.Count[int]() {
1313
if i >= 3 {
1414
break
1515
}
@@ -26,6 +26,6 @@ func ExampleCount() {
2626
func TestCountTerminateEarly(t *testing.T) {
2727
t.Parallel()
2828

29-
_, stop := it.Pull(it.Seq[int](iter.Count()))
29+
_, stop := it.Pull(iter.Count[int]())
3030
stop()
3131
}

iter/drop.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import "iter"
44

55
// Drop yields all values from a delegate [Iterator] except the first `count`
66
// values.
7-
func Drop[V any](delegate Iterator[V], count int) Iterator[V] {
8-
return Iterator[V](iter.Seq[V](func(yield func(V) bool) {
7+
func Drop[V any](delegate iter.Seq[V], count int) iter.Seq[V] {
8+
return func(yield func(V) bool) {
99
for value := range delegate {
1010
if count > 0 {
1111
count--
@@ -16,10 +16,32 @@ func Drop[V any](delegate Iterator[V], count int) Iterator[V] {
1616
return
1717
}
1818
}
19-
}))
19+
}
2020
}
2121

2222
// Drop is a convenience method for chaining [Drop] on [Iterator]s.
23-
func (iter Iterator[V]) Drop(count int) Iterator[V] {
24-
return Drop[V](iter, count)
23+
func (iterator Iterator[V]) Drop(count int) Iterator[V] {
24+
return Iterator[V](Drop(iter.Seq[V](iterator), count))
25+
}
26+
27+
// Drop2 yields all pairs of values from a delegate [Iterator2] except the
28+
// first `count` pairs.
29+
func Drop2[V, W any](delegate iter.Seq2[V, W], count int) iter.Seq2[V, W] {
30+
return func(yield func(V, W) bool) {
31+
for v, w := range delegate {
32+
if count > 0 {
33+
count--
34+
continue
35+
}
36+
37+
if !yield(v, w) {
38+
return
39+
}
40+
}
41+
}
42+
}
43+
44+
// Drop is a convenience method for chaining [Drop] on [Iterator2]s.
45+
func (iterator Iterator2[V, W]) Drop(count int) Iterator2[V, W] {
46+
return Iterator2[V, W](Drop2(iter.Seq2[V, W](iterator), count))
2547
}

iter/drop_test.go

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ package iter_test
33
import (
44
"fmt"
55
it "iter"
6+
sl "slices"
67
"testing"
78

9+
"github.com/BooleanCat/go-functional/v2/future/maps"
10+
"github.com/BooleanCat/go-functional/v2/future/slices"
11+
"github.com/BooleanCat/go-functional/v2/internal/assert"
812
"github.com/BooleanCat/go-functional/v2/iter"
913
)
1014

1115
func ExampleDrop() {
12-
for value := range iter.Drop(iter.Lift([]int{1, 2, 3, 4, 5}), 2) {
16+
for value := range iter.Drop(slices.Values([]int{1, 2, 3, 4, 5}), 2) {
1317
fmt.Println(value)
1418
}
1519

@@ -20,7 +24,7 @@ func ExampleDrop() {
2024
}
2125

2226
func ExampleDrop_method() {
23-
for value := range iter.Lift([]int{1, 2, 3, 4, 5}).Drop(2) {
27+
for value := range iter.Iterator[int](slices.Values([]int{1, 2, 3, 4, 5})).Drop(2) {
2428
fmt.Println(value)
2529
}
2630

@@ -33,14 +37,62 @@ func ExampleDrop_method() {
3337
func TestDropTerminateEarly(t *testing.T) {
3438
t.Parallel()
3539

36-
_, stop := it.Pull(it.Seq[int](iter.Drop(iter.Lift([]int{1, 2, 3}), 2)))
40+
_, stop := it.Pull(iter.Drop(slices.Values([]int{1, 2, 3}), 2))
3741
stop()
3842
}
3943

4044
func TestDropEmpty(t *testing.T) {
4145
t.Parallel()
4246

43-
for _ = range iter.Drop(iter.Lift([]int{}), 2) {
44-
t.Error("unexpected")
47+
assert.Empty[int](t, slices.Collect(iter.Drop(slices.Values([]int{}), 2)))
48+
}
49+
50+
func ExampleDrop2() {
51+
numbers := maps.Collect(iter.Drop2(maps.All(map[int]string{1: "one", 2: "two", 3: "three"}), 1))
52+
53+
fmt.Println(len(numbers))
54+
// Output: 2
55+
}
56+
57+
func ExampleDrop2_method() {
58+
numbers := iter.Iterator2[int, string](maps.All(map[int]string{1: "one", 2: "two", 3: "three"})).Drop(1)
59+
60+
fmt.Println(len(maps.Collect(it.Seq2[int, string](numbers))))
61+
// Output: 2
62+
}
63+
64+
func TestDrop2(t *testing.T) {
65+
t.Parallel()
66+
67+
keys := []int{1, 2, 3}
68+
values := []string{"one", "two", "three"}
69+
70+
numbers := maps.Collect(iter.Drop2(iter.Zip(slices.Values(keys), slices.Values(values)), 1))
71+
72+
assert.Equal(t, len(numbers), 2)
73+
74+
for key := range numbers {
75+
assert.True(t, sl.Contains(keys, key))
4576
}
4677
}
78+
79+
func TestDrop2Empty(t *testing.T) {
80+
t.Parallel()
81+
82+
assert.Equal(t, len(maps.Collect(iter.Drop2(maps.All(map[int]string{}), 1))), 0)
83+
}
84+
85+
func TestDrop2Zero(t *testing.T) {
86+
t.Parallel()
87+
88+
numbers := maps.Collect(iter.Drop2(maps.All(map[int]string{1: "one", 2: "two", 3: "three"}), 0))
89+
90+
assert.Equal(t, len(numbers), 3)
91+
}
92+
93+
func TestDrop2TerminateEarly(t *testing.T) {
94+
t.Parallel()
95+
96+
_, stop := it.Pull2(iter.Drop2(maps.All(map[int]string{1: "one", 2: "two", 3: "three"}), 1))
97+
stop()
98+
}

0 commit comments

Comments
 (0)