From c9702532922b34a6bebc632be0c1eae42fe8e3e3 Mon Sep 17 00:00:00 2001 From: DeedleFake Date: Fri, 21 Aug 2026 14:26:16 -0400 Subject: [PATCH 1/8] meta: update Go to 1.27 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index b970ff6..0cd63f5 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module deedles.dev/xiter -go 1.24.6 +go 1.27.0 From cb99b99f7800eb33cad5a53b69de5f39389ae80c Mon Sep 17 00:00:00 2001 From: DeedleFake Date: Fri, 21 Aug 2026 14:26:52 -0400 Subject: [PATCH 2/8] xiter: add `Seq` and `Seq2` wrappers --- xiter.go | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/xiter.go b/xiter.go index 5bf1e15..2270572 100644 --- a/xiter.go +++ b/xiter.go @@ -1,9 +1,42 @@ -// Package xiter provides iterator-related functionality compatible -// with, but not requiring, Go 1.23. +// Package xiter provides iterator-related functionality. package xiter import "iter" +// Seq is a local implementation of [iter.Seq] to allow adding +// methods. +type Seq[T any] iter.Seq[T] + +// S is a convenience function to provide type inference when +// converting from an [iter.Seq]. +func S[T any](seq iter.Seq[T]) Seq[T] { return Seq[T](seq) } + +// Seq converts back to an [iter.Seq]. +func (seq Seq[T]) Seq() iter.Seq[T] { return iter.Seq[T](seq) } + +// SeqCompat is a constraint to allow functions to accept either +// [iter.Seq] or [Seq]. +type SeqCompat[T any] interface { + ~func(func(T) bool) +} + +// Seq2 is a local implementation of [iter.Seq2] to allow adding +// methods. +type Seq2[T1, T2 any] iter.Seq2[T1, T2] + +// S2 is a convenience function to provide type inference when +// converting from an [iter.Seq2]. +func S2[T1, T2 any](seq iter.Seq2[T1, T2]) Seq2[T1, T2] { return Seq2[T1, T2](seq) } + +// Seq converts back to an [iter.Seq2]. +func (seq Seq2[T1, T2]) Seq() iter.Seq2[T1, T2] { return iter.Seq2[T1, T2](seq) } + +// Seq2Compat is a constraint to allow functions to accept either +// [iter.Seq2] or [Seq2]. +type Seq2Compat[T1, T2 any] interface { + ~func(func(T1, T2) bool) +} + // A SplitSeq is like a Seq but can yield via either of two functions. // It might not be useful, but is included anyways because it might // be. @@ -101,9 +134,9 @@ func Coroutine[In, Out any](coroutine CoroutineFunc[In, Out]) (yield CoroutineYi // wrapping certain types of APIs to make them interact more cleanly // with [iter.Seq], like that shown in the example. // -// If full, two-way communication with coroutine is necessary, see +// If full, two-way communication with a coroutine is necessary, see // [Coroutine]. -func Push[In, Out any](coroutine func(iter.Seq[In]) Out) (yield func(In) bool, stop func() Out) { +func Push[In, Out any, S SeqCompat[In]](coroutine func(S) Out) (yield func(In) bool, stop func() Out) { next, stop := Coroutine(func(v In, next func(Out) (In, bool)) Out { return coroutine(func(yield func(In) bool) { if !yield(v) { From 071eb934de695b30bfe8552a25b84a261ebe44bd Mon Sep 17 00:00:00 2001 From: DeedleFake Date: Fri, 21 Aug 2026 15:04:48 -0400 Subject: [PATCH 3/8] xiter: convert to methods --- sink.go | 88 ++++++++++++++++++++++----------------------- sink_test.go | 27 +++++++------- source.go | 47 ++++++++++++------------ source_test.go | 16 ++++----- transform.go | 74 +++++++++++++++++++------------------- transform_test.go | 91 ++++++++++++++++++++--------------------------- xiter.go | 14 ++++---- 7 files changed, 170 insertions(+), 187 deletions(-) diff --git a/sink.go b/sink.go index 2ced149..52a2643 100644 --- a/sink.go +++ b/sink.go @@ -10,13 +10,13 @@ import ( // CollectSize pre-allocates the slice being collected into to the // given size. It is provided purely for convenience. -func CollectSize[T any](seq iter.Seq[T], len int) []T { - return slices.AppendSeq(make([]T, 0, len), seq) +func (seq Seq[T]) CollectSize(len int) []T { + return slices.AppendSeq(make([]T, 0, len), seq.Seq()) } // Find returns the first value of seq for which f(value) returns // true. -func Find[T any](seq iter.Seq[T], f func(T) bool) (r T, ok bool) { +func (seq Seq[T]) Find(f func(T) bool) (r T, ok bool) { seq(func(v T) bool { if !f(v) { return true @@ -29,20 +29,20 @@ func Find[T any](seq iter.Seq[T], f func(T) bool) (r T, ok bool) { } // Contains returns true if v is an element of seq. -func Contains[T comparable](seq iter.Seq[T], v T) bool { - _, ok := Find(seq, func(e T) bool { return v == e }) +func Contains[T comparable](seq Seq[T], v T) bool { + _, ok := seq.Find(func(e T) bool { return v == e }) return ok } // Any returns true if f(element) is true for any elements of seq. -func Any[T any](seq iter.Seq[T], f func(T) bool) bool { - _, ok := Find(seq, f) +func (seq Seq[T]) Any(f func(T) bool) bool { + _, ok := seq.Find(f) return ok } // All returns true if f(element) is true for every element of seq. -func All[T any](seq iter.Seq[T], f func(T) bool) bool { - return !Any(seq, f) +func (seq Seq[T]) All(f func(T) bool) bool { + return !seq.Any(f) } // Reduce calls reducer on each value of seq, passing it initial as @@ -53,8 +53,8 @@ func All[T any](seq iter.Seq[T], f func(T) bool) bool { // Reduce can be somewhat complicated to get the hang of, but very // powerful. For example, a simple summation of values can be done as // -// sum := Reduce(seq, 0, func(total, v int) int { return total + v }) -func Reduce[T, R any](seq iter.Seq[T], initial R, reducer func(R, T) R) R { +// sum := seq.Reduce(0, func(total, v int) int { return total + v }) +func (seq Seq[T]) Reduce[R any](initial R, reducer func(R, T) R) R { seq(func(v T) bool { initial = reducer(initial, v) return true @@ -62,10 +62,10 @@ func Reduce[T, R any](seq iter.Seq[T], initial R, reducer func(R, T) R) R { return initial } -// Fold performs a [Reduce] but uses the first value yielded by seq -// instead of a provided initial value. If seq doesn't yield any +// Fold performs a [Seq.Reduce] but uses the first value yielded by +// seq instead of a provided initial value. If seq doesn't yield any // values, the zero value of T is returned. -func Fold[T any](seq iter.Seq[T], reducer func(T, T) T) T { +func (seq Seq[T]) Fold(reducer func(T, T) T) T { var prev T r := func(v1, v2 T) T { return v2 } seq(func(v T) bool { @@ -78,25 +78,25 @@ func Fold[T any](seq iter.Seq[T], reducer func(T, T) T) T { // Sum returns the values of seq added together in the order that they // are yielded. -func Sum[T Addable](seq iter.Seq[T]) T { - return Fold(seq, func(total, v T) T { return total + v }) +func Sum[T Addable](seq Seq[T]) T { + return seq.Fold(func(total, v T) T { return total + v }) } // Product returns the values of seq multiplied together. It returns // 1 if no values are yielded. -func Product[T Multiplyable](seq iter.Seq[T]) T { - return Reduce(seq, 1, func(product, v T) T { return product * v }) +func Product[T Multiplyable](seq Seq[T]) T { + return seq.Reduce(1, func(product, v T) T { return product * v }) } // IsSorted returns true if each element of seq is greater than or // equal to the previous one. -func IsSorted[T cmp.Ordered](seq iter.Seq[T]) bool { - return IsSortedFunc(seq, cmp.Compare) +func IsSorted[T cmp.Ordered](seq Seq[T]) bool { + return seq.IsSortedFunc(cmp.Compare) } // IsSortedFunc is like [IsSorted] but uses a custom comparison // function. -func IsSortedFunc[T any](seq iter.Seq[T], compare func(T, T) int) bool { +func (seq Seq[T]) IsSortedFunc(compare func(T, T) int) bool { var prev T c := func(T, T) int { return -1 } @@ -112,16 +112,16 @@ func IsSortedFunc[T any](seq iter.Seq[T], compare func(T, T) int) bool { // Equal returns true if seq1 and seq2 are the same length and each // element of each is equal to the element at the same point in the // sequence of the other. -func Equal[T cmp.Ordered](seq1, seq2 iter.Seq[T]) bool { +func Equal[T cmp.Ordered](seq1, seq2 Seq[T]) bool { return EqualFunc(seq1, seq2, func(v1, v2 T) bool { return v1 == v2 }) } // EqualFunc is like [Equal] but uses a custom comparison function to // determine the equivalence of the elements of each sequence. -func EqualFunc[T1, T2 any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], equal func(T1, T2) bool) bool { - p1, stop := iter.Pull(seq1) +func EqualFunc[T1, T2 any](seq1 Seq[T1], seq2 Seq[T2], equal func(T1, T2) bool) bool { + p1, stop := iter.Pull(seq1.Seq()) defer stop() - p2, stop := iter.Pull(seq2) + p2, stop := iter.Pull(seq2.Seq()) defer stop() for { @@ -138,7 +138,7 @@ func EqualFunc[T1, T2 any](seq1 iter.Seq[T1], seq2 iter.Seq[T2], equal func(T1, // Drain empties seq, returning the last value yielded. If no values // are yielded, ok will be false. -func Drain[T any](seq iter.Seq[T]) (v T, ok bool) { +func (seq Seq[T]) Drain() (v T, ok bool) { seq(func(val T) bool { v = val ok = true @@ -148,13 +148,13 @@ func Drain[T any](seq iter.Seq[T]) (v T, ok bool) { } // CollectSplit is like [Collect], but for a SplitSeq. -func CollectSplit[T1, T2 any](seq SplitSeq[T1, T2]) (y1 []T1, y2 []T2) { - return AppendSplitTo(seq, y1, y2) +func (seq SplitSeq[T1, T2]) CollectSplit() (y1 []T1, y2 []T2) { + return seq.AppendSplitTo(y1, y2) } // AppendSplitTo collects the elements of seq by appending them to // existing slices. -func AppendSplitTo[T1, T2 any](seq SplitSeq[T1, T2], s1 []T1, s2 []T2) ([]T1, []T2) { +func (seq SplitSeq[T1, T2]) AppendSplitTo(s1 []T1, s2 []T2) ([]T1, []T2) { seq( func(v T1) bool { s1 = append(s1, v) @@ -171,30 +171,30 @@ func AppendSplitTo[T1, T2 any](seq SplitSeq[T1, T2], s1 []T1, s2 []T2) ([]T1, [] // Partition returns two slices, one containing all of the elements of // seq for which f(element) is true and one containing all of those // for which it is false. -func Partition[T any](seq iter.Seq[T], f func(T) bool) (true, false []T) { - return PartitionInto(seq, f, true, false) +func (seq Seq[T]) Partition(f func(T) bool) (true, false []T) { + return seq.PartitionInto(f, true, false) } -// PartitionInto performs a [Partition] by appending to two existing -// slices. -func PartitionInto[T any](seq iter.Seq[T], f func(T) bool, true, false []T) ([]T, []T) { - return AppendSplitTo(Split(seq, f), true, false) +// PartitionInto performs a [Seq.Partition] by appending to two +// existing slices. +func (seq Seq[T]) PartitionInto(f func(T) bool, true, false []T) ([]T, []T) { + return seq.Split(f).AppendSplitTo(true, false) } // Min returns the minimum element yielded by seq or the zero value if // seq doesn't yield anything. -func Min[T cmp.Ordered](seq iter.Seq[T]) T { - return Fold(seq, func(v1, v2 T) T { return min(v1, v2) }) +func Min[T cmp.Ordered](seq Seq[T]) T { + return seq.Fold(func(v1, v2 T) T { return min(v1, v2) }) } // Max returns maximum element yielded by seq or the zero value if seq // doesn't yield anything. -func Max[T cmp.Ordered](seq iter.Seq[T]) T { - return Fold(seq, func(v1, v2 T) T { return max(v1, v2) }) +func Max[T cmp.Ordered](seq Seq[T]) T { + return seq.Fold(func(v1, v2 T) T { return max(v1, v2) }) } // FromPair converts a Seq of pairs to a two-value Seq. -func FromPair[T1, T2 any](seq iter.Seq[Pair[T1, T2]]) iter.Seq2[T1, T2] { +func FromPair[T1, T2 any](seq Seq[Pair[T1, T2]]) Seq2[T1, T2] { return func(yield func(T1, T2) bool) { seq(func(v Pair[T1, T2]) bool { return yield(v.Split()) @@ -205,7 +205,7 @@ func FromPair[T1, T2 any](seq iter.Seq[Pair[T1, T2]]) iter.Seq2[T1, T2] { // SendContext sends values from seq to c repeatedly until either the // sequence ends or ctx is canceled. It blocks until one of those two // things happens. -func SendContext[T any](seq iter.Seq[T], ctx context.Context, c chan<- T) { +func (seq Seq[T]) SendContext(ctx context.Context, c chan<- T) { seq(func(v T) bool { select { case <-ctx.Done(): @@ -216,9 +216,9 @@ func SendContext[T any](seq iter.Seq[T], ctx context.Context, c chan<- T) { }) } -// StringJoin works exactly like [strings.Join] but it operates on an -// [iter.Seq] instead of a []string. -func StringJoin(seq iter.Seq[string], sep string) string { +// StringJoin works exactly like [strings.Join] but it operates on a +// [Seq] instead of a []string. +func StringJoin(seq Seq[string], sep string) string { var buf strings.Builder var s string for v := range seq { diff --git a/sink_test.go b/sink_test.go index 89e8524..37069a0 100644 --- a/sink_test.go +++ b/sink_test.go @@ -7,10 +7,7 @@ import ( ) func TestFind(t *testing.T) { - s, _ := Find(Windows(Generate( - 0, 1), - 3), - func(win []int) bool { return Sum(slices.Values(win)) >= 100 }) + s, _ := Windows(Generate(0, 1), 3).Find(func(win []int) bool { return Sum(Of(win...)) >= 100 }) if [3]int(s) != [...]int{33, 34, 35} { t.Fatal(s) } @@ -24,7 +21,7 @@ func TestContains(t *testing.T) { } func TestSum(t *testing.T) { - s := Sum(slices.Values([]string{"a", " ", "test"})) + s := Sum(Of("a", " ", "test")) if s != "a test" { t.Fatal(s) } @@ -38,11 +35,11 @@ func TestProduct(t *testing.T) { } func TestPartition(t *testing.T) { - s1, s2 := Partition(Of(1, 2, 3, 4, 5), func(v int) bool { return v%2 == 0 }) - if !Equal(slices.Values(s1), Of(2, 4)) { + s1, s2 := Of(1, 2, 3, 4, 5).Partition(func(v int) bool { return v%2 == 0 }) + if !Equal(Of(s1...), Of(2, 4)) { t.Fatal(s1) } - if !Equal(slices.Values(s2), Of(1, 3, 5)) { + if !Equal(Of(s2...), Of(1, 3, 5)) { t.Fatal(s2) } } @@ -60,14 +57,14 @@ func TestExtent(t *testing.T) { } func TestAny(t *testing.T) { - r := Any(Of(2, 4, 6, 7), func(v int) bool { return v%2 != 0 }) + r := Of(2, 4, 6, 7).Any(func(v int) bool { return v%2 != 0 }) if !r { t.Fatal(r) } } func TestAll(t *testing.T) { - r := All(Of(2, 4, 6, 7), func(v int) bool { return v%2 == 0 }) + r := Of(2, 4, 6, 7).All(func(v int) bool { return v%2 == 0 }) if r { t.Fatal(r) } @@ -75,7 +72,7 @@ func TestAll(t *testing.T) { func TestSendContext(t *testing.T) { c := make(chan int, 3) - SendContext(Of(3, 2, 5), context.Background(), c) + Of(3, 2, 5).SendContext(context.Background(), c) s := []int{<-c, <-c, <-c} select { case v := <-c: @@ -95,9 +92,9 @@ func FuzzSendRecvContext(f *testing.F) { defer cancel() c := make(chan byte, len(data)) - SendContext(slices.Values(data), ctx, c) + S(slices.Values(data)).SendContext(ctx, c) close(c) - s := slices.Collect(RecvContext(ctx, c)) + s := slices.Collect(RecvContext(ctx, c).Seq()) if !slices.Equal(data, s) { t.Fatal(s) } @@ -105,12 +102,12 @@ func FuzzSendRecvContext(f *testing.F) { } func TestDrain(t *testing.T) { - v, ok := Drain(Of(3, 2, 5)) + v, ok := Of(3, 2, 5).Drain() if !ok || v != 5 { t.Fatalf("%v, %v", v, ok) } - v, ok = Drain(Of[int]()) + v, ok = Of[int]().Drain() if ok || v != 0 { t.Fatalf("%v, %v", v, ok) } diff --git a/source.go b/source.go index 05f2a3d..c60034b 100644 --- a/source.go +++ b/source.go @@ -3,7 +3,6 @@ package xiter import ( "context" "io" - "iter" "slices" "strings" "unicode" @@ -14,8 +13,8 @@ import ( // Generate returns a Seq that first yields start and then yields // successive values by adding step to the previous continuously. The // returned Seq does not end. To limit it to a specific number of -// returned elements, use [Limit]. -func Generate[T Addable](start, step T) iter.Seq[T] { +// returned elements, use [Seq.Limit]. +func Generate[T Addable](start, step T) Seq[T] { return func(yield func(T) bool) { for { if !yield(start) { @@ -27,12 +26,12 @@ func Generate[T Addable](start, step T) iter.Seq[T] { } // Of returns a Seq that yields the provided values. -func Of[T any](vals ...T) iter.Seq[T] { - return slices.Values(vals) +func Of[T any](vals ...T) Seq[T] { + return S(slices.Values(vals)) } // Bytes returns a Seq over the bytes of s. -func Bytes(s string) iter.Seq[byte] { +func Bytes(s string) Seq[byte] { return func(yield func(byte) bool) { for i := 0; i < len(s); i++ { if !yield(s[i]) { @@ -43,7 +42,7 @@ func Bytes(s string) iter.Seq[byte] { } // Runes returns a Seq over the runes of s. -func Runes[T ~[]byte | ~string](s T) iter.Seq[rune] { +func Runes[T ~[]byte | ~string](s T) Seq[rune] { return func(yield func(rune) bool) { b := unsafe.Slice(unsafe.StringData(*(*string)(unsafe.Pointer(&s))), len(s)) for len(b) > 0 { @@ -58,9 +57,9 @@ func Runes[T ~[]byte | ~string](s T) iter.Seq[rune] { // StringSplit returns an iterator over the substrings of s that are // separated by sep. It behaves very similarly to [strings.Split]. -func StringSplit(s, sep string) iter.Seq[string] { +func StringSplit(s, sep string) Seq[string] { if sep == "" { - return Map(Runes(s), func(c rune) string { return string(c) }) + return Runes(s).Map(func(c rune) string { return string(c) }) } return func(yield func(string) bool) { @@ -81,17 +80,17 @@ func StringSplit(s, sep string) iter.Seq[string] { // StringFields returns an iterator over the substrings of s that are // seperated by consecutive whitespace as determined by // [unicode.IsSpace]. It is very similar to [strings.Fields]. -func StringFields(s string) iter.Seq[string] { +func StringFields(s string) Seq[string] { return StringFieldsFunc(s, unicode.IsSpace) } // StringFieldsFunc returns an iterator over the substrings of s that // are seperated by consecutive sections of runes for which sep // returns true. It behaves very similarly to [strings.FieldsFunc]. -func StringFieldsFunc(s string, sep func(rune) bool) iter.Seq[string] { +func StringFieldsFunc(s string, sep func(rune) bool) Seq[string] { return func(yield func(string) bool) { start := 0 - for i, r := range Enumerate(Runes(s)) { + for i, r := range Runes(s).Enumerate() { if !sep(r) { continue } @@ -119,7 +118,7 @@ func StringFieldsFunc(s string, sep func(rune) bool) iter.Seq[string] { // ToPair takes a two-value iterator and produces a single-value // iterator of pairs. -func ToPair[T1, T2 any](seq iter.Seq2[T1, T2]) iter.Seq[Pair[T1, T2]] { +func ToPair[T1, T2 any](seq Seq2[T1, T2]) Seq[Pair[T1, T2]] { return func(yield func(Pair[T1, T2]) bool) { seq(func(v1 T1, v2 T2) bool { return yield(P(v1, v2)) @@ -128,7 +127,7 @@ func ToPair[T1, T2 any](seq iter.Seq2[T1, T2]) iter.Seq[Pair[T1, T2]] { } // V1 returns a Seq which iterates over only the T1 elements of seq. -func V1[T1, T2 any](seq iter.Seq2[T1, T2]) iter.Seq[T1] { +func (seq Seq2[T1, T2]) V1() Seq[T1] { return func(yield func(T1) bool) { seq(func(v1 T1, v2 T2) bool { return yield(v1) @@ -137,7 +136,7 @@ func V1[T1, T2 any](seq iter.Seq2[T1, T2]) iter.Seq[T1] { } // V2 returns a Seq which iterates over only the T2 elements of seq. -func V2[T1, T2 any](seq iter.Seq2[T1, T2]) iter.Seq[T2] { +func (seq Seq2[T1, T2]) V2() Seq[T2] { return func(yield func(T2) bool) { seq(func(v1 T1, v2 T2) bool { return yield(v2) @@ -147,7 +146,7 @@ func V2[T1, T2 any](seq iter.Seq2[T1, T2]) iter.Seq[T2] { // OfChan returns a Seq which yields values received from c. The // sequence ends when c is closed. It is equivalent to range c. -func OfChan[T any](c <-chan T) iter.Seq[T] { +func OfChan[T any](c <-chan T) Seq[T] { return func(yield func(T) bool) { for v := range c { if !yield(v) { @@ -159,7 +158,7 @@ func OfChan[T any](c <-chan T) iter.Seq[T] { // RecvContext returns a Seq that receives from c continuously until // either c is closed or the given context is canceled. -func RecvContext[T any](ctx context.Context, c <-chan T) iter.Seq[T] { +func RecvContext[T any](ctx context.Context, c <-chan T) Seq[T] { return func(yield func(T) bool) { for { select { @@ -175,11 +174,11 @@ func RecvContext[T any](ctx context.Context, c <-chan T) iter.Seq[T] { } // SliceChunksFunc is like [ChunksFunc] but operates on a slice -// instead of an [iter.Seq]. When dealing with data that is in a +// instead of a [Seq]. When dealing with data that is in a // slice, this is more effecient than using ChunksFunc as it can yield // subslices of the underlying slice instead of having to allocate a // moving window. The yielded subslices have their capacity clipped. -func SliceChunksFunc[T any, C comparable, S ~[]T](s S, chunker func(T) C) iter.Seq[S] { +func SliceChunksFunc[T any, C comparable, S ~[]T](s S, chunker func(T) C) Seq[S] { return func(yield func(S) bool) { if len(s) == 0 { return @@ -213,7 +212,7 @@ func SliceChunksFunc[T any, C comparable, S ~[]T](s S, chunker func(T) C) iter.S // that function returns a non-nil error, the iterator will yield that // error and then exit. If the iterator is terminated early, it will // call the provided done function first. -func reader[T byte | rune](read func() (T, error), done func()) iter.Seq2[T, error] { +func reader[T byte | rune](read func() (T, error), done func()) Seq2[T, error] { return func(yield func(T, error) bool) { for { c, err := read() @@ -232,7 +231,7 @@ func reader[T byte | rune](read func() (T, error), done func()) iter.Seq2[T, err // ReadBytes returns an iterator over the bytes of r. If reading the // next byte returns an error, the iterator will yield a non-nil error // and then exit. -func ReadBytes(r io.ByteReader) iter.Seq2[byte, error] { +func ReadBytes(r io.ByteReader) Seq2[byte, error] { return reader( r.ReadByte, func() {}, @@ -242,7 +241,7 @@ func ReadBytes(r io.ByteReader) iter.Seq2[byte, error] { // ReadRunes returns an iterator over the runes of r. If reading the // next rune returns an error, the iterator will yield a non-nil error // and then exit. -func ReadRunes(r io.RuneReader) iter.Seq2[rune, error] { +func ReadRunes(r io.RuneReader) Seq2[rune, error] { return reader( func() (rune, error) { c, _, err := r.ReadRune() @@ -260,7 +259,7 @@ func ReadRunes(r io.RuneReader) iter.Seq2[rune, error] { // the last byte read, allowing it to be used again to continue from // where it left off. If this is not the desired behavior, use // [ReadBytes] instead. -func ScanBytes(r io.ByteScanner) iter.Seq2[byte, error] { +func ScanBytes(r io.ByteScanner) Seq2[byte, error] { return reader( r.ReadByte, func() { r.UnreadByte() }, @@ -275,7 +274,7 @@ func ScanBytes(r io.ByteScanner) iter.Seq2[byte, error] { // the last rune read, allowing it to be used again to continue from // where it left off. If this is not the desired behavior, use // [ReadRunes] instead. -func ScanRunes(r io.RuneScanner) iter.Seq2[rune, error] { +func ScanRunes(r io.RuneScanner) Seq2[rune, error] { return reader( func() (rune, error) { c, _, err := r.ReadRune() diff --git a/source_test.go b/source_test.go index 42d8cfb..1ed7607 100644 --- a/source_test.go +++ b/source_test.go @@ -11,21 +11,21 @@ import ( ) func TestBytes(t *testing.T) { - s := slices.Collect(Bytes("テスト")) + s := slices.Collect(Bytes("テスト").Seq()) if !slices.Equal(s, []byte("テスト")) { t.Fatal(s) } } func TestRunes(t *testing.T) { - s := slices.Collect(Runes("これはテストです。")) + s := slices.Collect(Runes("これはテストです。").Seq()) if [9]rune(s) != [9]rune([]rune("これはテストです。")) { t.Fatal(s) } } func TestMapEntries(t *testing.T) { - s := slices.Collect(ToPair(maps.All(map[string]string{"this": "is", "a": "test"}))) + s := slices.Collect(ToPair(S2(maps.All(map[string]string{"this": "is", "a": "test"}))).Seq()) slices.SortFunc(s, func(e1, e2 Pair[string, string]) int { return cmp.Compare(e1.V1, e2.V2) }) if [2]Pair[string, string](s) != [...]Pair[string, string]{{"a", "test"}, {"this", "is"}} { t.Fatal(s) @@ -39,33 +39,33 @@ func TestRecvContext(t *testing.T) { c <- 5 close(c) - s := slices.Collect(RecvContext(context.Background(), c)) + s := slices.Collect(RecvContext(context.Background(), c).Seq()) if !slices.Equal(s, []int{3, 2, 5}) { t.Fatal(s) } } func TestStringSplit(t *testing.T) { - s := slices.Collect(StringSplit("this is a test", " ")) + s := slices.Collect(StringSplit("this is a test", " ").Seq()) if !slices.Equal(s, []string{"this", "is", "a", "test"}) { t.Fatal(s) } } func TestStringFields(t *testing.T) { - s := slices.Collect(StringFields(" this is a test ")) + s := slices.Collect(StringFields(" this is a test ").Seq()) if !slices.Equal(s, []string{"this", "is", "a", "test"}) { t.Fatal(s) } - s = slices.Collect(StringFields(" this is a test")) + s = slices.Collect(StringFields(" this is a test").Seq()) if !slices.Equal(s, []string{"this", "is", "a", "test"}) { t.Fatal(s) } } func TestSliceChunkBy(t *testing.T) { - s := slices.Collect(SliceChunksFunc([]int{-1, -2, -3, 1, 2, 3, -1, -2, 3}, func(v int) int { return cmp.Compare(v, 0) })) + s := slices.Collect(SliceChunksFunc([]int{-1, -2, -3, 1, 2, 3, -1, -2, 3}, func(v int) int { return cmp.Compare(v, 0) }).Seq()) if !slices.EqualFunc(s, [][]int{{-1, -2, -3}, {1, 2, 3}, {-1, -2}, {3}}, slices.Equal) { t.Fatal(s) } diff --git a/transform.go b/transform.go index 0f519e4..719b59e 100644 --- a/transform.go +++ b/transform.go @@ -9,7 +9,7 @@ import ( ) // Map returns a Seq that yields the values of seq transformed via f. -func Map[T1, T2 any](seq iter.Seq[T1], f func(T1) T2) iter.Seq[T2] { +func (seq Seq[T1]) Map[T2 any](f func(T1) T2) Seq[T2] { return func(yield func(T2) bool) { seq(func(v T1) bool { return yield(f(v)) @@ -19,7 +19,7 @@ func Map[T1, T2 any](seq iter.Seq[T1], f func(T1) T2) iter.Seq[T2] { // Filter returns a Seq that yields only the values of seq for which // f(value) returns true. -func Filter[T any](seq iter.Seq[T], f func(T) bool) iter.Seq[T] { +func (seq Seq[T]) Filter(f func(T) bool) Seq[T] { return func(yield func(T) bool) { seq(func(v T) bool { if !f(v) { @@ -32,7 +32,7 @@ func Filter[T any](seq iter.Seq[T], f func(T) bool) iter.Seq[T] { // Skip returns a Seq that skips over the first n elements of seq and // then yields the rest normally. -func Skip[T any](seq iter.Seq[T], n int) iter.Seq[T] { +func (seq Seq[T]) Skip(n int) Seq[T] { return func(yield func(T) bool) { seq(func(v T) bool { if n > 0 { @@ -52,7 +52,7 @@ func Skip[T any](seq iter.Seq[T], n int) iter.Seq[T] { // TODO: This is significantly less useful than it could be. For // example, there's no way to tell it to skip the yield but continue // iteration anyways. -func Handle[T any](seq iter.Seq2[T, error], f func(error) bool) iter.Seq[T] { +func Handle[T any](seq Seq2[T, error], f func(error) bool) Seq[T] { return func(yield func(T) bool) { seq(func(v T, err error) bool { if err != nil { @@ -64,7 +64,7 @@ func Handle[T any](seq iter.Seq2[T, error], f func(error) bool) iter.Seq[T] { } // Limit returns a Seq that yields at most n values from seq. -func Limit[T any](seq iter.Seq[T], n int) iter.Seq[T] { +func (seq Seq[T]) Limit(n int) Seq[T] { return func(yield func(T) bool) { seq(func(v T) bool { if !yield(v) { @@ -78,15 +78,15 @@ func Limit[T any](seq iter.Seq[T], n int) iter.Seq[T] { // Concat creates a new Seq that yields the values of each of the // provided Seqs in turn. -func Concat[T any](seqs ...iter.Seq[T]) iter.Seq[T] { - return Flatten(slices.Values(seqs)) +func Concat[T any, S SeqLike[T]](seqs ...S) Seq[T] { + return Flatten(Of(seqs...)) } // Flatten yields all of the elements of each Seq yielded from seq in // turn. -func Flatten[T any](seq iter.Seq[iter.Seq[T]]) iter.Seq[T] { +func Flatten[T any, S SeqLike[T]](seq Seq[S]) Seq[T] { return func(yield func(T) bool) { - seq(func(s iter.Seq[T]) bool { + seq(func(s S) bool { cont := true s(func(v T) bool { cont = yield(v) @@ -108,11 +108,11 @@ type Zipped[T1, T2 any] struct { // Zip returns a new Seq that yields the values of seq1 and seq2 // simultaneously. -func Zip[T1, T2 any](seq1 iter.Seq[T1], seq2 iter.Seq[T2]) iter.Seq[Zipped[T1, T2]] { +func Zip[T1, T2 any](seq1 Seq[T1], seq2 Seq[T2]) Seq[Zipped[T1, T2]] { return func(yield func(Zipped[T1, T2]) bool) { - p1, stop := iter.Pull(seq1) + p1, stop := iter.Pull(seq1.Seq()) defer stop() - p2, stop := iter.Pull(seq2) + p2, stop := iter.Pull(seq2.Seq()) defer stop() for { @@ -129,17 +129,17 @@ func Zip[T1, T2 any](seq1 iter.Seq[T1], seq2 iter.Seq[T2]) iter.Seq[Zipped[T1, T // Merge returns a sequence that yields values from the ordered // sequences seq1 and seq2 one at a time to produce a new ordered // sequence made up of all of the elements of both seq1 and seq2. -func Merge[T cmp.Ordered](seq1, seq2 iter.Seq[T]) iter.Seq[T] { +func Merge[T cmp.Ordered](seq1, seq2 Seq[T]) Seq[T] { return MergeFunc(seq1, seq2, cmp.Compare) } // MergeFunc is like [Merge], but uses a custom comparison function // for determining the order of values. -func MergeFunc[T any](seq1, seq2 iter.Seq[T], compare func(T, T) int) iter.Seq[T] { +func MergeFunc[T any](seq1, seq2 Seq[T], compare func(T, T) int) Seq[T] { return func(yield func(T) bool) { - p1, stop := iter.Pull(seq1) + p1, stop := iter.Pull(seq1.Seq()) defer stop() - p2, stop := iter.Pull(seq2) + p2, stop := iter.Pull(seq2.Seq()) defer stop() v1, ok1 := p1() @@ -185,9 +185,9 @@ func MergeFunc[T any](seq1, seq2 iter.Seq[T], compare func(T, T) int) iter.Seq[T // // and so on. The slice yielded is reused from one iteration to the // next, so it should not be held onto after each iteration has ended. -// [Map] and [slices.Clone] may come in handy for dealing with +// [Seq.Map] and [slices.Clone] may come in handy for dealing with // situations where this is necessary. -func Windows[T any](seq iter.Seq[T], n int) iter.Seq[[]T] { +func Windows[T any](seq Seq[T], n int) Seq[[]T] { return func(yield func([]T) bool) { win := make([]T, 0, n) @@ -223,7 +223,7 @@ func Windows[T any](seq iter.Seq[T], n int) iter.Seq[[]T] { // [6, 7, 8] // // Like with Windows, the slice is reused between iterations. -func Chunks[T any](seq iter.Seq[T], n int) iter.Seq[[]T] { +func Chunks[T any](seq Seq[T], n int) Seq[[]T] { return func(yield func([]T) bool) { win := make([]T, 0, n) @@ -257,9 +257,9 @@ func Chunks[T any](seq iter.Seq[T], n int) iter.Seq[[]T] { // the function changes from the previous call, a new chunk is started. // // Like with Chunks, the slice is reused between iterations. -func ChunksFunc[T any, C comparable](seq iter.Seq[T], chunker func(T) C) iter.Seq[[]T] { +func ChunksFunc[T any, C comparable](seq Seq[T], chunker func(T) C) Seq[[]T] { return func(yield func([]T) bool) { - next, stop := iter.Pull(seq) + next, stop := iter.Pull(seq.Seq()) defer stop() cur, ok := next() @@ -299,7 +299,7 @@ func ChunksFunc[T any, C comparable](seq iter.Seq[T], chunker func(T) C) iter.Se // Split returns a SplitSeq which yields the values of seq for which // f(value) is true to its first yield function and the rest to its // second. -func Split[T any](seq iter.Seq[T], f func(T) bool) SplitSeq[T, T] { +func (seq Seq[T]) Split(f func(T) bool) SplitSeq[T, T] { return func(true, false func(T) bool) { seq(func(v T) bool { y := false @@ -313,7 +313,7 @@ func Split[T any](seq iter.Seq[T], f func(T) bool) SplitSeq[T, T] { // Split2 transforms a Seq2 into a SplitSeq. Every iteration of the // Seq2 yields both values via the SplitSeq. -func Split2[T1, T2 any](seq iter.Seq2[T1, T2]) SplitSeq[T1, T2] { +func (seq Seq2[T1, T2]) Split2() SplitSeq[T1, T2] { return func(y1 func(T1) bool, y2 func(T2) bool) { seq(func(v1 T1, v2 T2) bool { return y1(v1) && y2(v2) @@ -325,7 +325,7 @@ func Split2[T1, T2 any](seq iter.Seq2[T1, T2]) SplitSeq[T1, T2] { // first iteration, it yields the values from seq and caches them. On // subsequent iterations, it yields the cached values from the first // iteration. -func Cache[T any](seq iter.Seq[T]) iter.Seq[T] { +func (seq Seq[T]) Cache() Seq[T] { var cache []T return func(yield func(T) bool) { if cache != nil { @@ -343,7 +343,7 @@ func Cache[T any](seq iter.Seq[T]) iter.Seq[T] { // Enumerate returns a Seq2 that counts the number of iterations of // seq as it yields elements from it, starting at 0. -func Enumerate[T any](seq iter.Seq[T]) iter.Seq2[int, T] { +func (seq Seq[T]) Enumerate() Seq2[int, T] { return func(yield func(int, T) bool) { i := -1 seq(func(v T) bool { @@ -355,10 +355,10 @@ func Enumerate[T any](seq iter.Seq[T]) iter.Seq2[int, T] { // Or yields all of the values from the first Seq which yields at // least one value and then stops. -func Or[T any](seqs ...iter.Seq[T]) iter.Seq[T] { - ss := Filter(slices.Values(seqs), func(s iter.Seq[T]) bool { return s != nil }) +func Or[T any, S SeqLike[T]](seqs ...S) Seq[T] { + ss := Of(seqs...).Filter(func(s S) bool { return s != nil }) return func(yield func(T) bool) { - ss(func(seq iter.Seq[T]) bool { + ss(func(seq S) bool { cont := true seq(func(v T) bool { cont = false @@ -373,7 +373,7 @@ func Or[T any](seqs ...iter.Seq[T]) iter.Seq[T] { // seq once. Note that to do this, it stores a set of all elements // that have been seen, so this iterator can use a large amount of // memory if seq yields a very large number of unique elements. -func Dedup[T comparable](seq iter.Seq[T]) iter.Seq[T] { +func Dedup[T comparable](seq Seq[T]) Seq[T] { return func(yield func(T) bool) { found := make(map[T]struct{}) for v := range seq { @@ -393,15 +393,15 @@ func Dedup[T comparable](seq iter.Seq[T]) iter.Seq[T] { // not filtered out. Unlike Dedup, it does not store all found values, // and so does not have the same performance implication that Dedup // does. -func Uniq[T comparable](seq iter.Seq[T]) iter.Seq[T] { - return UniqFunc(seq, func(v1, v2 T) bool { return v1 == v2 }) +func Uniq[T comparable](seq Seq[T]) Seq[T] { + return seq.UniqFunc(func(v1, v2 T) bool { return v1 == v2 }) } // UniqFunc is like [Uniq] but uses the provided comparison function // to check for duplicates. -func UniqFunc[T comparable](seq iter.Seq[T], eq func(T, T) bool) iter.Seq[T] { +func (seq Seq[T]) UniqFunc(eq func(T, T) bool) Seq[T] { return func(yield func(T) bool) { - next, stop := iter.Pull(seq) + next, stop := iter.Pull(seq.Seq()) defer stop() cur, ok := next() @@ -428,8 +428,8 @@ func UniqFunc[T comparable](seq iter.Seq[T], eq func(T, T) bool) iter.Seq[T] { // Sorted collects the entirety of seq and then returns a one-time use // iterator which yields the elements of seq in a sorted order. -func Sorted[T cmp.Ordered](seq iter.Seq[T]) iter.Seq[T] { - s := slices.Collect(seq) +func Sorted[T cmp.Ordered](seq Seq[T]) Seq[T] { + s := slices.Collect(seq.Seq()) xheap.Init(s) return func(yield func(T) bool) { @@ -446,8 +446,8 @@ func Sorted[T cmp.Ordered](seq iter.Seq[T]) iter.Seq[T] { // SortedFunc collects the entirety of seq and then returns a one-time // use iterator which yields the elements of seq in a sorted order // determined by the provided comparison function. -func SortedFunc[T any](seq iter.Seq[T], compare func(T, T) int) iter.Seq[T] { - s := slices.Collect(seq) +func (seq Seq[T]) SortedFunc(compare func(T, T) int) Seq[T] { + s := slices.Collect(seq.Seq()) xheap.InitFunc(s, compare) return func(yield func(T) bool) { diff --git a/transform_test.go b/transform_test.go index 8647571..e051a71 100644 --- a/transform_test.go +++ b/transform_test.go @@ -3,63 +3,55 @@ package xiter import ( "bytes" "cmp" - "iter" "slices" "testing" ) func TestMap(t *testing.T) { - s := slices.Values([]int{1, 2, 3}) - n := slices.Collect(Map(s, func(v int) float64 { return float64(v * 2) })) + s := Of(1, 2, 3) + n := slices.Collect(s.Map(func(v int) float64 { return float64(v * 2) }).Seq()) if [3]float64(n) != [...]float64{2, 4, 6} { t.Fatal(n) } } func TestFilter(t *testing.T) { - s := slices.Values([]int{1, 2, 3}) - n := slices.Collect(Filter(s, func(v int) bool { return v%2 != 0 })) + s := Of(1, 2, 3) + n := slices.Collect(s.Filter(func(v int) bool { return v%2 != 0 }).Seq()) if [2]int(n) != [...]int{1, 3} { t.Fatal(n) } } func TestSkip(t *testing.T) { - s := slices.Collect(Skip(Limit(Generate( - 0, 1), - 3), - 2), - ) - if !Equal(slices.Values(s), Of(2)) { + s := slices.Collect(Generate(0, 1).Limit(3).Skip(2).Seq()) + if !Equal(Of(s...), Of(2)) { t.Fatal(s) } } func TestLimit(t *testing.T) { - s := slices.Collect(Limit(Generate( - 0, 2), - 3), - ) + s := slices.Collect(Generate(0, 2).Limit(3).Seq()) if [3]int(s) != [...]int{0, 2, 4} { t.Fatal(s) } } func TestConcat(t *testing.T) { - s := slices.Collect(Concat(slices.Values([]int{1, 2, 3}), slices.Values([]int{3, 2, 5}))) + s := slices.Collect(Concat(Of(1, 2, 3), Of(3, 2, 5)).Seq()) if [6]int(s) != [...]int{1, 2, 3, 3, 2, 5} { t.Fatal(s) } - s = slices.Collect(Concat(slices.Values([]int{1, 2, 3}), slices.Values([]int{3, 2, 5}))) + s = slices.Collect(Concat(Of(1, 2, 3), Of(3, 2, 5)).Seq()) if [6]int(s) != [...]int{1, 2, 3, 3, 2, 5} { t.Fatal(s) } } func TestZip(t *testing.T) { - s1 := slices.Values([]int{1, 2, 3, 4, 5}) - s2 := slices.Values([]int{2, 3, 4, 5, 6}) + s1 := Of(1, 2, 3, 4, 5) + s2 := Of(2, 3, 4, 5, 6) seq := Zip(s1, s2) seq(func(v Zipped[int, int]) bool { if v.V2-v.V1 != 1 { @@ -74,8 +66,8 @@ func BenchmarkZip(b *testing.B) { slice2 := []int{2, 3, 4, 5, 6} for b.Loop() { - s1 := slices.Values(slice1) - s2 := slices.Values(slice2) + s1 := S(slices.Values(slice1)) + s2 := S(slices.Values(slice2)) seq := Zip(s1, s2) seq(func(v Zipped[int, int]) bool { return true @@ -84,29 +76,29 @@ func BenchmarkZip(b *testing.B) { } func TestIsSorted(t *testing.T) { - if IsSorted(slices.Values([]int{1, 2, 3, 2})) { + if IsSorted(Of(1, 2, 3, 2)) { t.Fatal("is not sorted") } - if !IsSorted(slices.Values([]int{1, 2, 3, 4, 5})) { + if !IsSorted(Of(1, 2, 3, 4, 5)) { t.Fatal("is sorted") } - if !IsSorted(slices.Values([]int{48, 48})) { + if !IsSorted(Of(48, 48)) { t.Fatal("is sorted") } } func TestMerge(t *testing.T) { - s1 := slices.Values([]int{2, 3, 5}) - s2 := slices.Values([]int{1, 2, 3, 4, 5}) - r := slices.Collect(Merge(s1, s2)) + s1 := Of(2, 3, 5) + s2 := Of(1, 2, 3, 4, 5) + r := slices.Collect(Merge(s1, s2).Seq()) if [8]int(r) != [...]int{1, 2, 2, 3, 3, 4, 5, 5} { t.Fatal(r) } } -func splitmerge[T cmp.Ordered](s []T) iter.Seq[T] { +func splitmerge[T cmp.Ordered](s []T) Seq[T] { if len(s) <= 1 { - return slices.Values(s) + return S(slices.Values(s)) } left := splitmerge(s[:len(s)/2]) @@ -115,7 +107,7 @@ func splitmerge[T cmp.Ordered](s []T) iter.Seq[T] { } func mergesort[T cmp.Ordered](s []T) { - _ = slices.AppendSeq(s[:0], splitmerge(s)) + _ = slices.AppendSeq(s[:0], splitmerge(s).Seq()) } func TestMergeSort(t *testing.T) { @@ -133,34 +125,29 @@ func FuzzMergeSort(f *testing.F) { slices.Sort(check) mergesort(s) - if !Equal(slices.Values(s), slices.Values(check)) { + if !Equal(S(slices.Values(s)), S(slices.Values(check))) { t.Fatal(s) } }) } func TestChunks(t *testing.T) { - s := slices.Collect(Map(Chunks(slices.Values([]int{1, 2, 3, 4, 5}), - 2), - slices.Clone), - ) + s := slices.Collect(Chunks(Of(1, 2, 3, 4, 5), 2).Map(slices.Clone).Seq()) if !slices.EqualFunc(s, [][]int{{1, 2}, {3, 4}, {5}}, slices.Equal) { t.Fatal(s) } } func TestChunksFunc(t *testing.T) { - s := slices.Collect(Map(ChunksFunc(slices.Values([]int{0, 0, 0, 0, 1, 0, 1, 1, 0, 1}), - func(v int) bool { return v%2 == 0 }), - slices.Clone), - ) + s := slices.Collect(ChunksFunc(Of(0, 0, 0, 0, 1, 0, 1, 1, 0, 1), + func(v int) bool { return v%2 == 0 }).Map(slices.Clone).Seq()) if !slices.EqualFunc(s, [][]int{{0, 0, 0, 0}, {1}, {0}, {1, 1}, {0}, {1}}, slices.Equal) { t.Fatal(s) } } func TestSplit2(t *testing.T) { - s1, s2 := CollectSplit(Split2(FromPair(slices.Values([]Pair[int32, int64]{{1, 2}, {3, 4}, {5, 6}})))) + s1, s2 := FromPair(Of(Pair[int32, int64]{1, 2}, Pair[int32, int64]{3, 4}, Pair[int32, int64]{5, 6})).Split2().CollectSplit() if !slices.Equal(s1, []int32{1, 3, 5}) { t.Fatal(s1) } @@ -171,54 +158,54 @@ func TestSplit2(t *testing.T) { func TestCache(t *testing.T) { var i int - f := func(yield func(int) bool) { + f := S(func(yield func(int) bool) { yield(i) i++ - } - seq := Cache(f) - if s := slices.Collect(seq); !slices.Equal(s, []int{0}) { + }) + seq := f.Cache() + if s := slices.Collect(seq.Seq()); !slices.Equal(s, []int{0}) { t.Fatal(s) } - if s := slices.Collect(seq); !slices.Equal(s, []int{0}) { + if s := slices.Collect(seq.Seq()); !slices.Equal(s, []int{0}) { t.Fatal(s) } } func TestEnumerate(t *testing.T) { - s := slices.Collect(ToPair(Enumerate(Limit(Generate(0, 2), 3)))) + s := slices.Collect(ToPair(Generate(0, 2).Limit(3).Enumerate()).Seq()) if !slices.Equal(s, []Pair[int, int]{{0, 0}, {1, 2}, {2, 4}}) { t.Fatal(s) } } func TestOr(t *testing.T) { - s := slices.Collect(Or(Of[int](), nil, Of(1, 2, 3), Of(4, 5, 6))) + s := slices.Collect(Or(Of[int](), nil, Of(1, 2, 3), Of(4, 5, 6)).Seq()) if !slices.Equal(s, []int{1, 2, 3}) { t.Fatal(s) } - s = slices.Collect(Or(Of[int](), Of(1, 2, 3), Of(4, 5, 6))) + s = slices.Collect(Or(Of[int](), Of(1, 2, 3), Of(4, 5, 6)).Seq()) if !slices.Equal(s, []int{1, 2, 3}) { t.Fatal(s) } } func TestDedup(t *testing.T) { - s := slices.Collect(Dedup(Of(1, 2, 3, 1, 2, 3, 4, 5, 3, 3, 3, 1, 2, 10))) + s := slices.Collect(Dedup(Of(1, 2, 3, 1, 2, 3, 4, 5, 3, 3, 3, 1, 2, 10)).Seq()) if !slices.Equal(s, []int{1, 2, 3, 4, 5, 10}) { t.Fatal(s) } } func TestUniq(t *testing.T) { - s := slices.Collect(Uniq(Of(1, 2, 3, 1, 2, 3, 4, 5, 3, 3, 3, 1, 2, 10))) + s := slices.Collect(Uniq(Of(1, 2, 3, 1, 2, 3, 4, 5, 3, 3, 3, 1, 2, 10)).Seq()) if !slices.Equal(s, []int{1, 2, 3, 1, 2, 3, 4, 5, 3, 1, 2, 10}) { t.Fatal(s) } } func TestSorted(t *testing.T) { - s := slices.Collect(Sorted(Of(3, 2, 5, 1, 7, 7, 8, 2))) + s := slices.Collect(Sorted(Of(3, 2, 5, 1, 7, 7, 8, 2)).Seq()) if !slices.Equal(s, []int{1, 2, 2, 3, 5, 7, 7, 8}) { t.Fatal(s) } @@ -226,7 +213,7 @@ func TestSorted(t *testing.T) { func TestSortedFunc(t *testing.T) { compare := func(v1, v2 int) int { return v2 - v1 } - s := slices.Collect(SortedFunc(Of(3, 2, 5, 1, 7, 7, 8, 2), compare)) + s := slices.Collect(Of(3, 2, 5, 1, 7, 7, 8, 2).SortedFunc(compare).Seq()) if !slices.Equal(s, []int{8, 7, 7, 5, 3, 2, 2, 1}) { t.Fatal(s) } diff --git a/xiter.go b/xiter.go index 2270572..d7c8513 100644 --- a/xiter.go +++ b/xiter.go @@ -9,14 +9,14 @@ type Seq[T any] iter.Seq[T] // S is a convenience function to provide type inference when // converting from an [iter.Seq]. -func S[T any](seq iter.Seq[T]) Seq[T] { return Seq[T](seq) } +func S[T any, S SeqLike[T]](seq S) Seq[T] { return Seq[T](seq) } // Seq converts back to an [iter.Seq]. func (seq Seq[T]) Seq() iter.Seq[T] { return iter.Seq[T](seq) } -// SeqCompat is a constraint to allow functions to accept either +// SeqLike is a constraint to allow functions to accept either // [iter.Seq] or [Seq]. -type SeqCompat[T any] interface { +type SeqLike[T any] interface { ~func(func(T) bool) } @@ -26,14 +26,14 @@ type Seq2[T1, T2 any] iter.Seq2[T1, T2] // S2 is a convenience function to provide type inference when // converting from an [iter.Seq2]. -func S2[T1, T2 any](seq iter.Seq2[T1, T2]) Seq2[T1, T2] { return Seq2[T1, T2](seq) } +func S2[T1, T2 any, S Seq2Like[T1, T2]](seq S) Seq2[T1, T2] { return Seq2[T1, T2](seq) } // Seq converts back to an [iter.Seq2]. func (seq Seq2[T1, T2]) Seq() iter.Seq2[T1, T2] { return iter.Seq2[T1, T2](seq) } -// Seq2Compat is a constraint to allow functions to accept either +// Seq2Like is a constraint to allow functions to accept either // [iter.Seq2] or [Seq2]. -type Seq2Compat[T1, T2 any] interface { +type Seq2Like[T1, T2 any] interface { ~func(func(T1, T2) bool) } @@ -136,7 +136,7 @@ func Coroutine[In, Out any](coroutine CoroutineFunc[In, Out]) (yield CoroutineYi // // If full, two-way communication with a coroutine is necessary, see // [Coroutine]. -func Push[In, Out any, S SeqCompat[In]](coroutine func(S) Out) (yield func(In) bool, stop func() Out) { +func Push[In, Out any, S SeqLike[In]](coroutine func(S) Out) (yield func(In) bool, stop func() Out) { next, stop := Coroutine(func(v In, next func(Out) (In, bool)) Out { return coroutine(func(yield func(In) bool) { if !yield(v) { From 3a775ee7dfc8598ffc3b952082c317f892ed89c1 Mon Sep 17 00:00:00 2001 From: DeedleFake Date: Mon, 24 Aug 2026 14:17:03 -0400 Subject: [PATCH 4/8] xiter: explain why some functions are still functions instead of methods --- sink.go | 9 --------- source.go | 28 ---------------------------- transform.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 37 deletions(-) diff --git a/sink.go b/sink.go index 52a2643..3f5eaaa 100644 --- a/sink.go +++ b/sink.go @@ -193,15 +193,6 @@ func Max[T cmp.Ordered](seq Seq[T]) T { return seq.Fold(func(v1, v2 T) T { return max(v1, v2) }) } -// FromPair converts a Seq of pairs to a two-value Seq. -func FromPair[T1, T2 any](seq Seq[Pair[T1, T2]]) Seq2[T1, T2] { - return func(yield func(T1, T2) bool) { - seq(func(v Pair[T1, T2]) bool { - return yield(v.Split()) - }) - } -} - // SendContext sends values from seq to c repeatedly until either the // sequence ends or ctx is canceled. It blocks until one of those two // things happens. diff --git a/source.go b/source.go index c60034b..79d9d62 100644 --- a/source.go +++ b/source.go @@ -116,34 +116,6 @@ func StringFieldsFunc(s string, sep func(rune) bool) Seq[string] { } } -// ToPair takes a two-value iterator and produces a single-value -// iterator of pairs. -func ToPair[T1, T2 any](seq Seq2[T1, T2]) Seq[Pair[T1, T2]] { - return func(yield func(Pair[T1, T2]) bool) { - seq(func(v1 T1, v2 T2) bool { - return yield(P(v1, v2)) - }) - } -} - -// V1 returns a Seq which iterates over only the T1 elements of seq. -func (seq Seq2[T1, T2]) V1() Seq[T1] { - return func(yield func(T1) bool) { - seq(func(v1 T1, v2 T2) bool { - return yield(v1) - }) - } -} - -// V2 returns a Seq which iterates over only the T2 elements of seq. -func (seq Seq2[T1, T2]) V2() Seq[T2] { - return func(yield func(T2) bool) { - seq(func(v1 T1, v2 T2) bool { - return yield(v2) - }) - } -} - // OfChan returns a Seq which yields values received from c. The // sequence ends when c is closed. It is equivalent to range c. func OfChan[T any](c <-chan T) Seq[T] { diff --git a/transform.go b/transform.go index 719b59e..746badb 100644 --- a/transform.go +++ b/transform.go @@ -108,6 +108,8 @@ type Zipped[T1, T2 any] struct { // Zip returns a new Seq that yields the values of seq1 and seq2 // simultaneously. +// +// Must not be a method. See https://github.com/golang/go/issues/80172. func Zip[T1, T2 any](seq1 Seq[T1], seq2 Seq[T2]) Seq[Zipped[T1, T2]] { return func(yield func(Zipped[T1, T2]) bool) { p1, stop := iter.Pull(seq1.Seq()) @@ -187,6 +189,8 @@ func MergeFunc[T any](seq1, seq2 Seq[T], compare func(T, T) int) Seq[T] { // next, so it should not be held onto after each iteration has ended. // [Seq.Map] and [slices.Clone] may come in handy for dealing with // situations where this is necessary. +// +// Must not be a method. See https://github.com/golang/go/issues/80172. func Windows[T any](seq Seq[T], n int) Seq[[]T] { return func(yield func([]T) bool) { win := make([]T, 0, n) @@ -223,6 +227,8 @@ func Windows[T any](seq Seq[T], n int) Seq[[]T] { // [6, 7, 8] // // Like with Windows, the slice is reused between iterations. +// +// Must not be a method. See https://github.com/golang/go/issues/80172. func Chunks[T any](seq Seq[T], n int) Seq[[]T] { return func(yield func([]T) bool) { win := make([]T, 0, n) @@ -257,6 +263,8 @@ func Chunks[T any](seq Seq[T], n int) Seq[[]T] { // the function changes from the previous call, a new chunk is started. // // Like with Chunks, the slice is reused between iterations. +// +// Must not be a method. See https://github.com/golang/go/issues/80172. func ChunksFunc[T any, C comparable](seq Seq[T], chunker func(T) C) Seq[[]T] { return func(yield func([]T) bool) { next, stop := iter.Pull(seq.Seq()) @@ -460,3 +468,44 @@ func (seq Seq[T]) SortedFunc(compare func(T, T) int) Seq[T] { } } } + +// ToPair takes a two-value iterator and produces a single-value +// iterator of pairs. +// +// Must not be a method. See https://github.com/golang/go/issues/80172. +func ToPair[T1, T2 any](seq Seq2[T1, T2]) Seq[Pair[T1, T2]] { + return func(yield func(Pair[T1, T2]) bool) { + seq(func(v1 T1, v2 T2) bool { + return yield(P(v1, v2)) + }) + } +} + +// V1 returns a Seq which iterates over only the T1 elements of seq. +func (seq Seq2[T1, T2]) V1() Seq[T1] { + return func(yield func(T1) bool) { + seq(func(v1 T1, v2 T2) bool { + return yield(v1) + }) + } +} + +// V2 returns a Seq which iterates over only the T2 elements of seq. +func (seq Seq2[T1, T2]) V2() Seq[T2] { + return func(yield func(T2) bool) { + seq(func(v1 T1, v2 T2) bool { + return yield(v2) + }) + } +} + +// FromPair converts a Seq of pairs to a two-value Seq. +// +// Must not be a method. See https://github.com/golang/go/issues/80172. +func FromPair[T1, T2 any](seq Seq[Pair[T1, T2]]) Seq2[T1, T2] { + return func(yield func(T1, T2) bool) { + seq(func(v Pair[T1, T2]) bool { + return yield(v.Split()) + }) + } +} From 22bc4bdb6c59a303f65ea6ff0bcc7a276a89a898 Mon Sep 17 00:00:00 2001 From: DeedleFake Date: Mon, 24 Aug 2026 14:24:21 -0400 Subject: [PATCH 5/8] xiter: add `Collect()` --- sink.go | 7 +++++++ sink_test.go | 2 +- source_test.go | 16 ++++++++-------- transform.go | 4 ++-- transform_test.go | 38 +++++++++++++++++++------------------- 5 files changed, 37 insertions(+), 30 deletions(-) diff --git a/sink.go b/sink.go index 3f5eaaa..1df508e 100644 --- a/sink.go +++ b/sink.go @@ -8,6 +8,13 @@ import ( "strings" ) +// Collect collects all items yielded by seq into a slice. It is +// equivalent to [slices.Collect] and is provided purely for +// convenience. +func (seq Seq[T]) Collect() []T { + return slices.Collect(seq.Seq()) +} + // CollectSize pre-allocates the slice being collected into to the // given size. It is provided purely for convenience. func (seq Seq[T]) CollectSize(len int) []T { diff --git a/sink_test.go b/sink_test.go index 37069a0..344ebdf 100644 --- a/sink_test.go +++ b/sink_test.go @@ -94,7 +94,7 @@ func FuzzSendRecvContext(f *testing.F) { c := make(chan byte, len(data)) S(slices.Values(data)).SendContext(ctx, c) close(c) - s := slices.Collect(RecvContext(ctx, c).Seq()) + s := RecvContext(ctx, c).Collect() if !slices.Equal(data, s) { t.Fatal(s) } diff --git a/source_test.go b/source_test.go index 1ed7607..d098397 100644 --- a/source_test.go +++ b/source_test.go @@ -11,21 +11,21 @@ import ( ) func TestBytes(t *testing.T) { - s := slices.Collect(Bytes("テスト").Seq()) + s := Bytes("テスト").Collect() if !slices.Equal(s, []byte("テスト")) { t.Fatal(s) } } func TestRunes(t *testing.T) { - s := slices.Collect(Runes("これはテストです。").Seq()) + s := Runes("これはテストです。").Collect() if [9]rune(s) != [9]rune([]rune("これはテストです。")) { t.Fatal(s) } } func TestMapEntries(t *testing.T) { - s := slices.Collect(ToPair(S2(maps.All(map[string]string{"this": "is", "a": "test"}))).Seq()) + s := ToPair(S2(maps.All(map[string]string{"this": "is", "a": "test"}))).Collect() slices.SortFunc(s, func(e1, e2 Pair[string, string]) int { return cmp.Compare(e1.V1, e2.V2) }) if [2]Pair[string, string](s) != [...]Pair[string, string]{{"a", "test"}, {"this", "is"}} { t.Fatal(s) @@ -39,33 +39,33 @@ func TestRecvContext(t *testing.T) { c <- 5 close(c) - s := slices.Collect(RecvContext(context.Background(), c).Seq()) + s := RecvContext(context.Background(), c).Collect() if !slices.Equal(s, []int{3, 2, 5}) { t.Fatal(s) } } func TestStringSplit(t *testing.T) { - s := slices.Collect(StringSplit("this is a test", " ").Seq()) + s := StringSplit("this is a test", " ").Collect() if !slices.Equal(s, []string{"this", "is", "a", "test"}) { t.Fatal(s) } } func TestStringFields(t *testing.T) { - s := slices.Collect(StringFields(" this is a test ").Seq()) + s := StringFields(" this is a test ").Collect() if !slices.Equal(s, []string{"this", "is", "a", "test"}) { t.Fatal(s) } - s = slices.Collect(StringFields(" this is a test").Seq()) + s = StringFields(" this is a test").Collect() if !slices.Equal(s, []string{"this", "is", "a", "test"}) { t.Fatal(s) } } func TestSliceChunkBy(t *testing.T) { - s := slices.Collect(SliceChunksFunc([]int{-1, -2, -3, 1, 2, 3, -1, -2, 3}, func(v int) int { return cmp.Compare(v, 0) }).Seq()) + s := SliceChunksFunc([]int{-1, -2, -3, 1, 2, 3, -1, -2, 3}, func(v int) int { return cmp.Compare(v, 0) }).Collect() if !slices.EqualFunc(s, [][]int{{-1, -2, -3}, {1, 2, 3}, {-1, -2}, {3}}, slices.Equal) { t.Fatal(s) } diff --git a/transform.go b/transform.go index 746badb..94ab977 100644 --- a/transform.go +++ b/transform.go @@ -437,7 +437,7 @@ func (seq Seq[T]) UniqFunc(eq func(T, T) bool) Seq[T] { // Sorted collects the entirety of seq and then returns a one-time use // iterator which yields the elements of seq in a sorted order. func Sorted[T cmp.Ordered](seq Seq[T]) Seq[T] { - s := slices.Collect(seq.Seq()) + s := seq.Collect() xheap.Init(s) return func(yield func(T) bool) { @@ -455,7 +455,7 @@ func Sorted[T cmp.Ordered](seq Seq[T]) Seq[T] { // use iterator which yields the elements of seq in a sorted order // determined by the provided comparison function. func (seq Seq[T]) SortedFunc(compare func(T, T) int) Seq[T] { - s := slices.Collect(seq.Seq()) + s := seq.Collect() xheap.InitFunc(s, compare) return func(yield func(T) bool) { diff --git a/transform_test.go b/transform_test.go index e051a71..c1c285c 100644 --- a/transform_test.go +++ b/transform_test.go @@ -9,7 +9,7 @@ import ( func TestMap(t *testing.T) { s := Of(1, 2, 3) - n := slices.Collect(s.Map(func(v int) float64 { return float64(v * 2) }).Seq()) + n := s.Map(func(v int) float64 { return float64(v * 2) }).Collect() if [3]float64(n) != [...]float64{2, 4, 6} { t.Fatal(n) } @@ -17,33 +17,33 @@ func TestMap(t *testing.T) { func TestFilter(t *testing.T) { s := Of(1, 2, 3) - n := slices.Collect(s.Filter(func(v int) bool { return v%2 != 0 }).Seq()) + n := s.Filter(func(v int) bool { return v%2 != 0 }).Collect() if [2]int(n) != [...]int{1, 3} { t.Fatal(n) } } func TestSkip(t *testing.T) { - s := slices.Collect(Generate(0, 1).Limit(3).Skip(2).Seq()) + s := Generate(0, 1).Limit(3).Skip(2).Collect() if !Equal(Of(s...), Of(2)) { t.Fatal(s) } } func TestLimit(t *testing.T) { - s := slices.Collect(Generate(0, 2).Limit(3).Seq()) + s := Generate(0, 2).Limit(3).Collect() if [3]int(s) != [...]int{0, 2, 4} { t.Fatal(s) } } func TestConcat(t *testing.T) { - s := slices.Collect(Concat(Of(1, 2, 3), Of(3, 2, 5)).Seq()) + s := Concat(Of(1, 2, 3), Of(3, 2, 5)).Collect() if [6]int(s) != [...]int{1, 2, 3, 3, 2, 5} { t.Fatal(s) } - s = slices.Collect(Concat(Of(1, 2, 3), Of(3, 2, 5)).Seq()) + s = Concat(Of(1, 2, 3), Of(3, 2, 5)).Collect() if [6]int(s) != [...]int{1, 2, 3, 3, 2, 5} { t.Fatal(s) } @@ -90,7 +90,7 @@ func TestIsSorted(t *testing.T) { func TestMerge(t *testing.T) { s1 := Of(2, 3, 5) s2 := Of(1, 2, 3, 4, 5) - r := slices.Collect(Merge(s1, s2).Seq()) + r := Merge(s1, s2).Collect() if [8]int(r) != [...]int{1, 2, 2, 3, 3, 4, 5, 5} { t.Fatal(r) } @@ -132,15 +132,15 @@ func FuzzMergeSort(f *testing.F) { } func TestChunks(t *testing.T) { - s := slices.Collect(Chunks(Of(1, 2, 3, 4, 5), 2).Map(slices.Clone).Seq()) + s := Chunks(Of(1, 2, 3, 4, 5), 2).Map(slices.Clone).Collect() if !slices.EqualFunc(s, [][]int{{1, 2}, {3, 4}, {5}}, slices.Equal) { t.Fatal(s) } } func TestChunksFunc(t *testing.T) { - s := slices.Collect(ChunksFunc(Of(0, 0, 0, 0, 1, 0, 1, 1, 0, 1), - func(v int) bool { return v%2 == 0 }).Map(slices.Clone).Seq()) + s := ChunksFunc(Of(0, 0, 0, 0, 1, 0, 1, 1, 0, 1), + func(v int) bool { return v%2 == 0 }).Map(slices.Clone).Collect() if !slices.EqualFunc(s, [][]int{{0, 0, 0, 0}, {1}, {0}, {1, 1}, {0}, {1}}, slices.Equal) { t.Fatal(s) } @@ -163,49 +163,49 @@ func TestCache(t *testing.T) { i++ }) seq := f.Cache() - if s := slices.Collect(seq.Seq()); !slices.Equal(s, []int{0}) { + if s := seq.Collect(); !slices.Equal(s, []int{0}) { t.Fatal(s) } - if s := slices.Collect(seq.Seq()); !slices.Equal(s, []int{0}) { + if s := seq.Collect(); !slices.Equal(s, []int{0}) { t.Fatal(s) } } func TestEnumerate(t *testing.T) { - s := slices.Collect(ToPair(Generate(0, 2).Limit(3).Enumerate()).Seq()) + s := ToPair(Generate(0, 2).Limit(3).Enumerate()).Collect() if !slices.Equal(s, []Pair[int, int]{{0, 0}, {1, 2}, {2, 4}}) { t.Fatal(s) } } func TestOr(t *testing.T) { - s := slices.Collect(Or(Of[int](), nil, Of(1, 2, 3), Of(4, 5, 6)).Seq()) + s := Or(Of[int](), nil, Of(1, 2, 3), Of(4, 5, 6)).Collect() if !slices.Equal(s, []int{1, 2, 3}) { t.Fatal(s) } - s = slices.Collect(Or(Of[int](), Of(1, 2, 3), Of(4, 5, 6)).Seq()) + s = Or(Of[int](), Of(1, 2, 3), Of(4, 5, 6)).Collect() if !slices.Equal(s, []int{1, 2, 3}) { t.Fatal(s) } } func TestDedup(t *testing.T) { - s := slices.Collect(Dedup(Of(1, 2, 3, 1, 2, 3, 4, 5, 3, 3, 3, 1, 2, 10)).Seq()) + s := Dedup(Of(1, 2, 3, 1, 2, 3, 4, 5, 3, 3, 3, 1, 2, 10)).Collect() if !slices.Equal(s, []int{1, 2, 3, 4, 5, 10}) { t.Fatal(s) } } func TestUniq(t *testing.T) { - s := slices.Collect(Uniq(Of(1, 2, 3, 1, 2, 3, 4, 5, 3, 3, 3, 1, 2, 10)).Seq()) + s := Uniq(Of(1, 2, 3, 1, 2, 3, 4, 5, 3, 3, 3, 1, 2, 10)).Collect() if !slices.Equal(s, []int{1, 2, 3, 1, 2, 3, 4, 5, 3, 1, 2, 10}) { t.Fatal(s) } } func TestSorted(t *testing.T) { - s := slices.Collect(Sorted(Of(3, 2, 5, 1, 7, 7, 8, 2)).Seq()) + s := Sorted(Of(3, 2, 5, 1, 7, 7, 8, 2)).Collect() if !slices.Equal(s, []int{1, 2, 2, 3, 5, 7, 7, 8}) { t.Fatal(s) } @@ -213,7 +213,7 @@ func TestSorted(t *testing.T) { func TestSortedFunc(t *testing.T) { compare := func(v1, v2 int) int { return v2 - v1 } - s := slices.Collect(Of(3, 2, 5, 1, 7, 7, 8, 2).SortedFunc(compare).Seq()) + s := Of(3, 2, 5, 1, 7, 7, 8, 2).SortedFunc(compare).Collect() if !slices.Equal(s, []int{8, 7, 7, 5, 3, 2, 2, 1}) { t.Fatal(s) } From 7c66322e136d1f212b99f54d303780167b953980 Mon Sep 17 00:00:00 2001 From: DeedleFake Date: Mon, 24 Aug 2026 14:32:17 -0400 Subject: [PATCH 6/8] xiter: fix `All()` --- sink.go | 2 +- sink_test.go | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/sink.go b/sink.go index 1df508e..58cf2d0 100644 --- a/sink.go +++ b/sink.go @@ -49,7 +49,7 @@ func (seq Seq[T]) Any(f func(T) bool) bool { // All returns true if f(element) is true for every element of seq. func (seq Seq[T]) All(f func(T) bool) bool { - return !seq.Any(f) + return !seq.Any(func(v T) bool { return !f(v) }) } // Reduce calls reducer on each value of seq, passing it initial as diff --git a/sink_test.go b/sink_test.go index 344ebdf..a9d279d 100644 --- a/sink_test.go +++ b/sink_test.go @@ -64,10 +64,27 @@ func TestAny(t *testing.T) { } func TestAll(t *testing.T) { - r := Of(2, 4, 6, 7).All(func(v int) bool { return v%2 == 0 }) + even := func(v int) bool { return v%2 == 0 } + + r := Of(2, 4, 6).All(even) + if !r { + t.Fatal(r) + } + + r = Of(2, 4, 6, 7).All(even) if r { t.Fatal(r) } + + r = Of(1, 3, 5).All(even) + if r { + t.Fatal(r) + } + + r = Of[int]().All(even) + if !r { + t.Fatal(r) + } } func TestSendContext(t *testing.T) { From 5543fa0e6b02c6fd8d008680c2cadbd95501b0f7 Mon Sep 17 00:00:00 2001 From: DeedleFake Date: Mon, 24 Aug 2026 14:34:53 -0400 Subject: [PATCH 7/8] meta: clean up README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a89897..02f757c 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,4 @@ xiter [![Go Reference](https://pkg.go.dev/badge/deedles.dev/xiter.svg)](https://pkg.go.dev/deedles.dev/xiter) -xiter is a simple implementation of iterator utility functions for Go 1.23's iterators. +xiter is a simple implementation of Go iterator utility functionality. From e7965b7836a6f23e95255f559c78ca626274ece1 Mon Sep 17 00:00:00 2001 From: DeedleFake Date: Mon, 24 Aug 2026 14:40:19 -0400 Subject: [PATCH 8/8] meta: add CI config --- .github/workflows/ci.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..135c978 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI + +on: + workflow_dispatch: + push: + branches: + - master + pull_request: + +jobs: + go-vet: + name: go vet + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + - name: Setup Go + uses: actions/setup-go@v7 + with: + go-version-file: go.mod + - name: go vet + run: go vet -v ./... + + go-test: + name: go test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + - name: Setup Go + uses: actions/setup-go@v7 + with: + go-version-file: go.mod + - name: go test + run: go test -v ./...