Skip to content

Commit

Permalink
Update for Go 1.22 (#11)
Browse files Browse the repository at this point in the history
* Update for Go 1.22, and add types and functions in anticipation of Go 1.23 iterators.

* Update CI to Go 1.22.

* Tweak docstrings.
  • Loading branch information
bobg authored Feb 7, 2024
1 parent 6701aaa commit 0dfeb15
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 4 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.21
go-version: 1.22

- name: Unit tests
run: go test -v -coverprofile=cover.out ./...
Expand All @@ -27,7 +27,7 @@ jobs:

- name: Modver
if: ${{ github.event_name == 'pull_request' }}
uses: bobg/modver@v2.5.0
uses: bobg/modver@v2.7.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
pull_request_url: https://github.com/${{ github.repository }}/pull/${{ github.event.number }}
2 changes: 1 addition & 1 deletion .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
steps:
- uses: actions/setup-go@v3
with:
go-version: '1.21'
go-version: '1.22'
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/bobg/go-generics/v3

go 1.21
go 1.22

require (
github.com/mattn/go-sqlite3 v1.14.12
Expand Down
74 changes: 74 additions & 0 deletions iter/iter.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Package iter defines an iterator interface,
// a collection of concrete iterator types,
// and some functions for operating on iterators.
// It is also a drop-in replacement for the Go 1.23 standard library package iter
// (a preview of which is available in Go 1.22 when building with GOEXPERIMENT=rangefunc).
package iter

// Of is the interface implemented by iterators.
Expand All @@ -24,3 +26,75 @@ type Of[T any] interface {
// It may be called only after Next returns false.
Err() error
}

// Seq is a Go 1.23 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.
//
// This type is defined in the same way as in the standard library,
// but is not identical,
// because Go type aliases cannot (yet?) be used with generic types.
type Seq[V any] func(yield func(V) bool)

// Seq2 is a Go 1.23 iterator over sequences of pairs of values, most commonly key-value pairs.
// When called as seq(yield), seq calls yield(k, v) for each pair (k, v) in the sequence,
// stopping early if yield returns false.
//
// This type is defined in the same way as in the standard library,
// but is not identical,
// because Go type aliases cannot (yet?) be used with generic types.
type Seq2[K, V any] func(yield func(K, V) bool)

// All makes a Go 1.23 iterator from an Of[T],
// suitable for use in a one-variable for-range loop.
// To try this in Go 1.22,
// build with the environment variable GOEXPERIMENT set to rangefunc.
// See https://go.dev/wiki/RangefuncExperiment.
//
// The caller should still check the iterator's Err method after the loop terminates.
func All[T any](inp Of[T]) Seq[T] {
return func(yield func(T) bool) {
for inp.Next() {
if !yield(inp.Val()) {
return
}
}
}
}

// AllCount makes a Go 1.23 counting iterator from an Of[T],
// suitable for use in a two-variable for-range loop.
// To try this in Go 1.22,
// build with the environment variable GOEXPERIMENT set to rangefunc.
// See https://go.dev/wiki/RangefuncExperiment.
//
// The caller should still check the iterator's Err method after the loop terminates.
func AllCount[T any](inp Of[T]) Seq2[int, T] {
return func(yield func(int, T) bool) {
var i int
for inp.Next() {
if !yield(i, inp.Val()) {
return
}
i++
}
}
}

// AllPairs makes a Go 1.23 pair iterator from an Of[Pair[T, U]],
// suitable for use in a two-variable for-range loop.
// To try this in Go 1.22,
// build with the environment variable GOEXPERIMENT set to rangefunc.
// See https://go.dev/wiki/RangefuncExperiment.
//
// The caller should still check the iterator's Err method after the loop terminates.
func AllPairs[T, U any](inp Of[Pair[T, U]]) Seq2[T, U] {
return func(yield func(T, U) bool) {
for inp.Next() {
p := inp.Val()
if !yield(p.X, p.Y) {
return
}
}
}
}
65 changes: 65 additions & 0 deletions iter/iter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package iter

import (
"reflect"
"testing"
)

func TestAll(t *testing.T) {
var (
ints = Ints(1, 1) // All integers starting at 1
first5 = FirstN(ints, 5) // First 5 integers
)

testSeq(t, All(first5), []int{1, 2, 3, 4, 5})
}

func TestAllCount(t *testing.T) {
names := FromSlice([]string{"Alice", "Bob", "Carol"})

testSeq2(t, AllCount(names), []int{0, 1, 2}, []string{"Alice", "Bob", "Carol"})
}

func TestAllPairs(t *testing.T) {
var (
letters = FromSlice([]string{"a", "b", "c", "d"})
nums = FromSlice([]int{1, 2, 3})
pairs = Zip(letters, nums)
)

testSeq2(t, AllPairs(pairs), []string{"a", "b", "c", "d"}, []int{1, 2, 3, 0})
}

func testSeq[T any](t *testing.T, seq Seq[T], want []T) {
var got []T

seq(func(val T) bool {
got = append(got, val)
return true
})

if !reflect.DeepEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
}

func testSeq2[T, U any](t *testing.T, seq Seq2[T, U], wantT []T, wantU []U) {
var (
gotT []T
gotU []U
)

seq(func(valT T, valU U) bool {
gotT = append(gotT, valT)
gotU = append(gotU, valU)
return true
})

if !reflect.DeepEqual(gotT, wantT) {
t.Errorf("got %v, want %v", gotT, wantT)
}

if !reflect.DeepEqual(gotU, wantU) {
t.Errorf("got %v, want %v", gotU, wantU)
}
}
51 changes: 51 additions & 0 deletions iter/pull.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//go:build go1.23 || goexperiment.rangefunc

package iter

import "iter"

// Pull converts the “push-style” iterator sequence seq
// into a “pull-style” iterator accessed by the two functions
// next and stop.
//
// Next returns the next value in the sequence
// and a boolean indicating whether the value is valid.
// When the sequence is over, next returns the zero V and false.
// It is valid to call next after reaching the end of the sequence
// or after calling stop. These calls will continue
// to return the zero V and false.
//
// Stop ends the iteration. It must be called when the caller is
// no longer interested in next values and next has not yet
// signaled that the sequence is over (with a false boolean return).
// It is valid to call stop multiple times and when next has
// already returned false.
//
// It is an error to call next or stop from multiple goroutines
// simultaneously.
func Pull[V any](seq Seq[V]) (next func() (V, bool), stop func()) {
return iter.Pull(iter.Seq[V](seq))
}

// Pull2 converts the “push-style” iterator sequence seq
// into a “pull-style” iterator accessed by the two functions
// next and stop.
//
// Next returns the next pair in the sequence
// and a boolean indicating whether the pair is valid.
// When the sequence is over, next returns a pair of zero values and false.
// It is valid to call next after reaching the end of the sequence
// or after calling stop. These calls will continue
// to return a pair of zero values and false.
//
// Stop ends the iteration. It must be called when the caller is
// no longer interested in next values and next has not yet
// signaled that the sequence is over (with a false boolean return).
// It is valid to call stop multiple times and when next has
// already returned false.
//
// It is an error to call next or stop from multiple goroutines
// simultaneously.
func Pull2[K, V any](seq Seq2[K, V]) (next func() (K, V, bool), stop func()) {
return iter.Pull2(iter.Seq2[K, V](seq))
}
65 changes: 65 additions & 0 deletions iter/pull_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//go:build go1.23 || goexperiment.rangefunc

package iter

import (
"reflect"
"testing"
)

func TestPull(t *testing.T) {
var (
ints = Ints(1, 1) // All integers starting at 1
next, stop = Pull(All(ints))
want = []int{1, 2, 3, 4, 5}
got []int
)

for range 5 {
v, ok := next()
if !ok {
break
}
got = append(got, v)
}
stop()

if !reflect.DeepEqual(got, want) {
t.Errorf("have %v, want %v", got, want)
}

v, ok := next()
if v != 0 || ok != false {
t.Errorf("next() after stop() gives %d, %v, want 0, false", v, ok)
}
}

func TestPull2(t *testing.T) {
var (
names = FromSlice([]string{"Alice", "Bob", "Carol", "Dave"})
namelens = FromSlice([]int{5, 3, 5, 4})
pairs = Zip(names, namelens)
next, stop = Pull2(AllPairs(pairs))
want = []any{
"Alice", 5,
"Bob", 3,
"Carol", 5,
"Dave", 4,
"", 0,
}
got []any
)

for {
name, namelen, ok := next()
got = append(got, name, namelen)
if !ok {
break
}
}
stop()

if !reflect.DeepEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
}
5 changes: 5 additions & 0 deletions slices/dropin.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,8 @@ func BinarySearch[S ~[]E, E cmp.Ordered](x S, target E) (int, bool) {
func BinarySearchFunc[S ~[]E, E, T any](x S, target T, cmp func(E, T) int) (int, bool) {
return slices.BinarySearchFunc(x, target, cmp)
}

// Concat returns a new slice concatenating the passed in slices.
func Concat[S ~[]E, E any](s ...S) S {
return slices.Concat(s...)
}
11 changes: 11 additions & 0 deletions slices/dropin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -976,3 +976,14 @@ func TestBinarySearchFunc(t *testing.T) {
t.Errorf("BinarySearchFunc(%v, %q, cmp) = %v, %v, want %v, %v", data, "2", pos, found, 3, true)
}
}

func TestConcat(t *testing.T) {
var (
a = []int{1, 2, 3}
b = []int{4, 5, 6}
want = []int{1, 2, 3, 4, 5, 6}
)
if got := Concat(a, b); !Equal(got, want) {
t.Errorf("Concat(%v, %v) = %v, want %v", a, b, got, want)
}
}

0 comments on commit 0dfeb15

Please sign in to comment.