iterkit

package
v0.296.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 9, 2025 License: Apache-2.0 Imports: 13 Imported by: 2

README

iterkit

Go Reference

iterkit is a Go package that provides a collection of iterator implementations and utilities, designed to help developers work with data sequences in a memory-efficient manner. It offers tools for processing, transforming, and managing iterators in a way that is both intuitive and performant.

This package is designed to make working with iterators intuitive and efficient, while providing robust error handling options tailored to real-world scenarios. If you have any questions or need further clarification, feel free to reach out!

Features

  • Map: Transform an iter.Seq[From] into a iter.Seq[To].
  • Reduce: Combine items using a custom aggregation function.
  • Batch Processing: Group items into fixed-size batches for efficient batch processing.
    • optional wait time limit option for batching slow and infinite iterators where it is uncertain when exactly a full batch is reached
    • optional size option for configuring the size of a batch
  • Paginated Processing: Process paginated data sources efficiently.
  • Filter: Exclude items based on specific criteria.
  • Merge: Combine multiple iterators into one unified stream.
  • Sync: Ensure safe access to iterators across goroutines.
  • Once: Limit an iterator's usage to a single pass, ideal for stateful data sources.
  • Errors: Tools for handling errors that occur during iteration.
  • Head/Limit: Limit/Restrict the number of elements retrieved from an iterator.
  • Offset: Skip a specified number of elements before iteration begins.
  • First/Last: Retrieve only the first or last element.
  • Take/TakeAll: Collect the next n elements from a iter.Pull iteration.
  • Take/TakeAll: Collect the remaining elements from a iter.Pull iteration.
  • Count: Determine the number of elements in an iterator.
  • Channel Integration (Chan): Convert between iterators to channels for concurrent processing.
  • Range Creation: Generate sequences of values efficiently.
    • CharRange: Create an iterator over a range of characters.
    • IntRange: Generate a range of integers.
  • and more...

Error Handling in Iterators

Iterators often represent data sources that can encounter errors during their lifecycle. For example, iterating over database query results or processing messages from a pubsub subscription may involve potential failure points such as connection issues or mapping errors.

iterkit provides two approaches for handling errors:

  1. Separate Error Handling: Using iter.Seq[T] alongside a func() error to retrieve error values. However, this approach requires handling all errors at once rather than on a per-element basis.

  2. Integrated Error Handling: Using the iter.Seq2[T, error] type (aliased as iterkit.ErrSeq[T]). This allows users to handle errors flexibly within the iteration process, making it easier to manage errors as they occur.

After thorough testing and consideration of common use cases, especially those involving database or RPC interactions, iterkit has adopted the iter.Seq2[T, error] approach for its flexibility and wide applicability.

Many abstracted external resources can produce valid errors during iteration, rather than only at the end. Handling errors as part of the iteration process provides greater flexibility and aligns more naturally with how iterators function.

For example, sql.Rows#Scan can return an error for mapping, even when the iteration itself remains valid. Supporting iter.Seq2[T, error] allows for more intuitive and efficient error handling in such cases.

Functions Supporting Error Handling
  • CollectErr: Collects all items and any associated errors during iteration.
  • ReduceErr: Aggregates an iterator's results into a final value along with any errors encountered.
  • MapErr: Transforms elements of an iter.Seq[From] or iterkit.ErrSeq[From] into a new iterkit.ErrSeq[To].
  • Filter: Works seamlessly with both iter.Seq[From] and iterkit.ErrSeq[From], adapting its output type based on the input.

If you prefer to use a iterator helper function that doesn't support natively the Seq2[T, error] type, then you can work solely with the values, by using the iterkit.OnErrSeqValue, enabling you to treat an iterkit.ErrSeq[T] as a standard iter.Seq[T].

Installation

To integrate iterkit into your Go project, run:

go get go.llib.dev/frameless/pkg/iterkit

Usage

Below is an example demonstrating the use of iterkit to filter and transform a slice:

package main

import (
    "fmt"
    "go.llib.dev/frameless/pkg/iterkit"
)

func main() {
    // Create an iterator from a slice
    numbers := iterkit.Slice([]int{1, 2, 3, 4, 5, 6})

    // Filter even numbers
    evens := iterkit.Filter(numbers, func(n int) bool {
        return n%2 == 0
    })

    // Square each even number
    squares := iterkit.Map(evens, func(n int) int {
        return n * n
    })

    // Collect results into a slice
    result := iterkit.Collect(squares)

    fmt.Println(result) // Output: [4 16 36]
}

Documentation

Overview

package iterators provide iterator implementations.

Summary

An Iterator's goal is to decouple the origin of the data from the consumer who uses that data. Most commonly, iterators hide whether the data comes from a specific database, standard input, or elsewhere. This approach helps to design data consumers that are not dependent on the concrete implementation of the data source, while still allowing for the composition and various actions on the received data stream. An Iterator represents an iterable list of element, which length is not known until it is fully iterated, thus can range from zero to infinity. As a rule of thumb, if the consumer is not the final destination of the data stream, it should use the pipeline pattern to avoid bottlenecks with local resources such as memory.

Resources

https://en.wikipedia.org/wiki/Iterator_pattern https://en.wikipedia.org/wiki/Pipeline_(software)

Index

Examples

Constants

View Source
const NoMore errorkit.Error = "[[ErrNoMorePage]]"

Variables

This section is empty.

Functions

func Batch

func Batch[T any](i iter.Seq[T], opts ...BatchOption) iter.Seq[[]T]
Example
src := iterkit.IntRange(0, 1000)

batched := iterkit.Batch(src)

for vs := range batched {
	fmt.Printf("%#v\n", vs)
}
Output:

Example (WithSize)
src := iterkit.IntRange(0, 1000)

batched := iterkit.Batch(src, iterkit.BatchSize(100))

for vs := range batched {
	fmt.Printf("%#v\n", vs)
}
Output:

Example (WithWaitLimit)
slowIterSeq := iterkit.IntRange(0, 1000)

batched := iterkit.Batch(slowIterSeq, iterkit.BatchWaitLimit(time.Second))

// Batching will occure either when the batching size reached
// or when the wait limit duration passed
for vs := range batched {
	fmt.Printf("%#v\n", vs)
}
Output:

func Chan

func Chan[T any](ch <-chan T) iter.Seq[T]

Chan creates an iterator out from a channel

Example
ch := make(chan int)

i := iterkit.Chan(ch)

go func() {
	defer close(ch)
	ch <- 42
}()

for v := range i {
	fmt.Println(v) // 42 once
}
Output:

func CharRange

func CharRange(begin, end rune) iter.Seq[rune]

CharRange returns an iterator that will range between the specified `begin“ and the `end` rune.

Example
for char := range iterkit.CharRange('A', 'Z') {
	// prints characters between A and Z
	// A, B, C, D... Z
	fmt.Println(string(char))
}
Output:

func Collect

func Collect[T any](i iter.Seq[T]) []T
Example
var itr iter.Seq[int]

ints := iterkit.Collect(itr)
_ = ints
Output:

func Collect2

func Collect2[K, V, KV any](i iter.Seq2[K, V], m KVMapFunc[KV, K, V]) []KV
Example
var itr iter.Seq2[string, int]

type T struct {
	S string
	I int
}

ints := iterkit.Collect2(itr, func(s string, i int) T {
	return T{S: s, I: i}
})
_ = ints
Output:

func Collect2Map added in v0.294.0

func Collect2Map[K comparable, V any](i iter.Seq2[K, V]) map[K]V

Collect2Map will collect2 an iter.Seq2 into a map.

Example
var values iter.Seq2[string, int] = func(yield func(string, int) bool) {
	if !yield("foo", 42) {
		return
	}
	if !yield("bar", 7) {
		return
	}
	if !yield("baz", 13) {
		return
	}
}

vs := iterkit.Collect2Map(values)

_ = vs // map[string]int{"foo": 42, "bar": 7, "baz": 13}
Output:

func CollectErr

func CollectErr[T any](i iter.Seq2[T, error]) ([]T, error)
Example
var itr iter.Seq2[int, error] = func(yield func(int, error) bool) {
	for i := 0; i < 42; i++ {
		if !yield(i, nil) {
			return
		}
	}
}

vs, err := iterkit.CollectErr(itr)
_, _ = vs, err
Output:

func CollectErrIter deprecated

func CollectErrIter[T any](i iter.Seq2[T, error]) ([]T, error)

CollectErrIter is a temporal alias to CollectErr

Deprecated: use iterkit.CollectErr instead

func CollectPull

func CollectPull[T any](next func() (T, bool), stops ...func()) []T
Example
var itr iter.Seq[int] = iterkit.IntRange(1, 10)
vs := iterkit.CollectPull(iter.Pull(itr))
_ = vs
Output:

func CollectPullIter

func CollectPullIter[T any](itr PullIter[T]) ([]T, error)

func Count

func Count[T any](i iter.Seq[T]) int

Count will iterate over and count the total iterations number

Good when all you want is count all the elements in an iterator but don't want to do anything else.

Example
i := iterkit.Slice[int]([]int{1, 2, 3})
total := iterkit.Count[int](i)
_ = total // 3
Output:

func Count2

func Count2[K, V any](i iter.Seq2[K, V]) int
Example
itr := maps.All(map[string]int{
	"foo": 2,
	"bar": 4,
	"baz": 8,
})
iterkit.Count2(itr) // 3
Output:

func Empty

func Empty[T any]() iter.Seq[T]

Empty iterator is used to represent nil result with Null object pattern

Example
_ = iterkit.Empty[any]()
Output:

func Empty2

func Empty2[T1, T2 any]() iter.Seq2[T1, T2]

Empty2 iterator is used to represent nil result with Null object pattern

func Filter

func Filter[T any, Iter I1[T]](i Iter, filter func(T) bool) Iter
Example
var i iter.Seq[int]
i = iterkit.Slice([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
i = iterkit.Filter[int](i, func(n int) bool { return n > 2 })

for v := range i {
	fmt.Println(v)
}
Output:

Example (WithErrSeq)
var repo crud.AllFinder[Foo]
all, _ := repo.FindAll(context.Background())

hasBar := iterkit.Filter(all, func(foo Foo) bool {
	return foo.Bar != ""
})

_ = hasBar
Output:

func Filter2 added in v0.293.0

func Filter2[K, V any](i iter.Seq2[K, V], filter func(k K, v V) bool) iter.Seq2[K, V]

func First

func First[T any](i iter.Seq[T]) (T, bool)

First decode the first next value of the iterator and close the iterator

func First2

func First2[K, V any](i iter.Seq2[K, V]) (K, V, bool)

First2 decode the first next value of the iterator and close the iterator

Example
var itr iter.Seq2[string, int] = func(yield func(string, int) bool) {
	for i := 0; i < 42; i++ {
		if !yield(strconv.Itoa(i), i) {
			return
		}
	}
}

k, v, ok := iterkit.First2(itr)
_, _, _ = k, v, ok
Output:

func FromKV added in v0.295.0

func FromKV[K, V any](kvs []KV[K, V]) iter.Seq2[K, V]

func FromPull

func FromPull[T any](next func() (T, bool), stops ...func()) iter.Seq[T]

func FromPull2

func FromPull2[K, V any](next func() (K, V, bool), stops ...func()) iter.Seq2[K, V]
func Head[T any](i iter.Seq[T], n int) iter.Seq[T]

Head takes the first n element, similarly how the coreutils "head" app works.

Example
inf42 := func(yield func(int) bool) {
	for /* infinite */ {
		if !yield(42) {
			return
		}
	}
}

i := iterkit.Head[int](inf42, 3)

vs := iterkit.Collect(i)
_ = vs // []{42, 42, 42}, nil
Output:

func Head2 added in v0.294.0

func Head2[K, V any](i iter.Seq2[K, V], n int) iter.Seq2[K, V]

Head2 takes the first n element, similarly how the coreutils "head" app works.

Example
inf42 := func(yield func(int, int) bool) {
	for /* infinite */ {
		if !yield(42, 24) {
			return
		}
	}
}

i := iterkit.Head2[int](inf42, 3)

vs := iterkit.Collect2Map(i)
_ = vs // map[int]int{42:24, 42:24, 42:24}, nil
Output:

func IntRange

func IntRange(begin, end int) iter.Seq[int]

IntRange returns an iterator that will range between the specified `begin“ and the `end` int.

Example
for n := range iterkit.IntRange(1, 9) {
	// prints characters between 1 and 9
	// 1, 2, 3, 4, 5, 6, 7, 8, 9
	fmt.Println(n)
}
Output:

func Last

func Last[T any](i iter.Seq[T]) (T, bool)
Example
itr := iterkit.IntRange(0, 10)

n, ok := iterkit.Last(itr)
_ = ok // true
_ = n  // 10
Output:

func Last2 added in v0.287.0

func Last2[K, V any](i iter.Seq2[K, V]) (K, V, bool)
Example
var itr iter.Seq2[int, string] = func(yield func(int, string) bool) {
	for n := range iterkit.IntRange(0, 10) {
		if !yield(n, strconv.Itoa(n)) {
			return
		}
	}
}

num, str, ok := iterkit.Last2(itr)
_ = ok  // true
_ = num // 10
_ = str // "10"
Output:

func Limit

func Limit[V any](i iter.Seq[V], n int) iter.Seq[V]

func Map

func Map[To any, From any](i iter.Seq[From], transform func(From) To) iter.Seq[To]

Map allows you to do additional transformation on the values. This is useful in cases, where you have to alter the input value, or change the type all together. Like when you read lines from an input stream, and then you map the line content to a certain data structure, in order to not expose what steps needed in order to deserialize the input stream, thus protect the business rules from this information.

Example
rawNumbers := iterkit.Slice([]string{"1", "2", "42"})
numbers := iterkit.Map[int](rawNumbers, func(v string) int {
	return len(v)
})
_ = numbers
Output:

func Map2 added in v0.294.0

func Map2[OKey, OVal, IKey, IVal any](i iter.Seq2[IKey, IVal], transform func(IKey, IVal) (OKey, OVal)) iter.Seq2[OKey, OVal]
Example
itr := maps.All(map[int]string{1: "1", 2: "2", 3: "42"})

numbers := iterkit.Map2[int, int](itr, func(k int, v string) (int, int) {
	return k, len(v)
})

_ = numbers
Output:

func Merge

func Merge[T any](is ...iter.Seq[T]) iter.Seq[T]

func Merge2

func Merge2[K, V any](is ...iter.Seq2[K, V]) iter.Seq2[K, V]

func Offset

func Offset[V any](i iter.Seq[V], offset int) iter.Seq[V]

func Reduce

func Reduce[R, T any](i iter.Seq[T], initial R, fn func(R, T) R) R
Example
raw := iterkit.Slice([]int{1, 2, 42})

_ = iterkit.Reduce[[]int](raw, nil, func(vs []int, v int) []int {
	return append(vs, v)
})
Output:

func ReduceErr

func ReduceErr[R, T any, I I1[T]](i I, initial R, fn func(R, T) (R, error)) (result R, rErr error)
Example
raw := iterkit.Slice([]string{"1", "2", "42"})

_, _ = iterkit.ReduceErr[[]int](raw, nil, func(vs []int, raw string) ([]int, error) {

	v, err := strconv.Atoi(raw)
	if err != nil {
		return nil, err
	}
	return append(vs, v), nil

})
Output:

func Reverse

func Reverse[T any](i iter.Seq[T]) iter.Seq[T]

Reverse will reverse the iteration direction.

WARNING

It does not work with infinite iterators, as it requires to collect all values before it can reverse the elements.

Example
itr := iterkit.IntRange(1, 3) // []int{1, 2, 3}
itr = iterkit.Reverse(itr)    // []int{3, 2, 1}
for range itr {
}
Output:

func SingleValue

func SingleValue[T any](v T) iter.Seq[T]

SingleValue creates an iterator that can return one single element and will ensure that Next can only be called once.

func Slice

func Slice[T any](slice []T) iter.Seq[T]

func Take

func Take[T any](next func() (T, bool), n int) []T

Take will take the next N value from a pull iterator.

func Take2 added in v0.295.0

func Take2[KV any, K, V any](next func() (K, V, bool), n int, m KVMapFunc[KV, K, V]) []KV

Take will take the next N value from a pull iterator.

Example
kvs := maps.All(map[string]int{
	"foo": 42,
	"bar": 7,
	"baz": 13,
})

next, stop := iter.Pull2(kvs)
defer stop()

type E struct {
	Key   string
	Value int
}

es := iterkit.Take2[E](next, 3, func(k string, v int) E {
	return E{Key: k, Value: v}
})

_ = len(es) // 3
Output:

func Take2All added in v0.295.0

func Take2All[KV any, K, V any](next func() (K, V, bool), m KVMapFunc[KV, K, V]) []KV

TakeAll will take all the remaining values from a pull iterator.

func TakeAll

func TakeAll[T any](next func() (T, bool)) []T

TakeAll will take all the remaining values from a pull iterator.

Example
i := iterkit.Slice([]int{1, 2, 3, 4, 5})
next, stop := iter.Pull(i)
defer stop()
vs := iterkit.TakeAll(next)
_ = vs // []int{1, 2, 3, 4, 5}
Output:

func ToChan

func ToChan[T any](itr iter.Seq[T]) (_ <-chan T, cancel func())

Types

type BatchConfig added in v0.295.0

type BatchConfig struct {
	Size      int
	WaitLimit time.Duration
}

func (BatchConfig) Configure added in v0.295.0

func (c BatchConfig) Configure(t *BatchConfig)

type BatchOption added in v0.295.0

type BatchOption option.Option[BatchConfig]

func BatchSize added in v0.295.0

func BatchSize(n int) BatchOption

func BatchWaitLimit added in v0.295.0

func BatchWaitLimit(d time.Duration) BatchOption

type ErrFunc

type ErrFunc = errorkit.ErrFunc

ErrFunc is the check function that can tell if currently an iterator that is related to the error function has an issue or not.

func FromErrIter deprecated

func FromErrIter[T any](i ErrSeq[T]) (iter.Seq[T], ErrFunc)

FromErrIter is a temporal alias to SplitErrSeq

Deprecated: use iterkit.SplitErrSeq instead

func SplitErrSeq added in v0.295.0

func SplitErrSeq[T any](i ErrSeq[T]) (iter.Seq[T], ErrFunc)

SplitErrSeq will split an iter.Seq2[T, error] iterator into a iter.Seq[T] iterator plus an error retrival func.

Example
var sourceErrSeq iter.Seq2[int, error]

i, errFunc := iterkit.SplitErrSeq(sourceErrSeq)
for v := range i {
	fmt.Println(v)
}
if err := errFunc(); err != nil {
	fmt.Println(err.Error())
}
Output:

type ErrIter deprecated

type ErrIter[T any] = ErrSeq[T]

ErrIter is a temporal alias to ErrSeq for backward compability purposes.

Deprecated: use iterkit.ErrSeq[T] instead.

type ErrSeq added in v0.295.0

type ErrSeq[T any] = iter.Seq2[T, error]

ErrSeq is an iterator that can tell if a currently returned value has an issue or not.

func Error

func Error[T any](err error) ErrSeq[T]

Error returns an Interface that only can do is returning an Err and never have next element

func ErrorF

func ErrorF[T any](format string, a ...any) ErrSeq[T]

ErrorF behaves exactly like fmt.ErrorF but returns the error wrapped as iterator

func FromPages added in v0.295.0

func FromPages[T any](next func(offset int) (values []T, _ error)) ErrSeq[T]

FromPages will create an iter.Seq[T] which can be used like any other iterator, Under the hood the "more" function will be used to dynamically retrieve more values when the previously called values are already used up.

If the more function has a hard-coded true for the "has next page" return value, then the pagination will interpret an empty result as "no more pages left".

Example
ctx := context.Background()

fetchMoreFoo := func(offset int) ([]Foo, error) {
	const limit = 10
	query := url.Values{}
	query.Set("limit", strconv.Itoa(limit))
	query.Set("offset", strconv.Itoa(offset))
	resp, err := http.Get("https://api.mydomain.com/v1/foos?" + query.Encode())
	if err != nil {
		return nil, err
	}

	var values []FooDTO
	defer resp.Body.Close()
	dec := json.NewDecoder(resp.Body)
	dec.DisallowUnknownFields()
	if err := dec.Decode(&values); err != nil {
		return nil, err
	}

	vs, err := dtokit.Map[[]Foo](ctx, values)
	if err != nil {
		return nil, err
	}
	if len(vs) < limit {
		return vs, iterkit.NoMore
	}
	return vs, nil
}

foos := iterkit.FromPages(fetchMoreFoo)
_ = foos // foos can be called like any iterator,
// and under the hood, the fetchMoreFoo function will be used dynamically,
// to retrieve more values when the previously called values are already used up.
Output:

func FromPullIter

func FromPullIter[T any](itr PullIter[T]) ErrSeq[T]

func MapErr

func MapErr[To any, From any, Iter I1[From]](i Iter, transform func(From) (To, error)) ErrSeq[To]
Example
rawNumbers := iterkit.Slice([]string{"1", "2", "42"})
numbers := iterkit.MapErr[int](rawNumbers, strconv.Atoi)
_ = numbers
Output:

func OnErrIterValue deprecated

func OnErrIterValue[To any, From any](itr ErrSeq[From], pipeline func(itr iter.Seq[From]) iter.Seq[To]) ErrSeq[To]

OnErrIterValue is a temporal alias to OnErrSeqValue

Deprecated: use iterkit.OnErrSeqValue instead

func OnErrSeqValue added in v0.295.0

func OnErrSeqValue[To any, From any](itr ErrSeq[From], pipeline func(itr iter.Seq[From]) iter.Seq[To]) ErrSeq[To]

OnErrSeqValue will apply a iterator pipeline on a given ErrSeq

Example
var (
	input  iter.Seq2[int, error]
	output iter.Seq2[string, error]
)

output = iterkit.OnErrSeqValue(input, func(itr iter.Seq[int]) iter.Seq[string] {
	// we receive an iterator without the error second value
	// we do our iterator manipulation like it doesn't have an error
	// then we return it back
	itr = iterkit.Map(itr, func(v int) int { return v * 3 })
	itr = iterkit.Filter(itr, func(i int) bool { return i%2 == 0 })
	return iterkit.Map(itr, strconv.Itoa)
})

// the returned iter have the pipeline applied,
// but the elements still contain the potential error value in case something went wrong.
_ = output
Output:

func ToErrIter deprecated

func ToErrIter[T any](i iter.Seq[T], errFuncs ...ErrFunc) ErrSeq[T]

ToErrIter is a temporal alias to ToErrSeq

Deprecated: use iterkit.ToErrSeq instead

func ToErrSeq added in v0.295.0

func ToErrSeq[T any](i iter.Seq[T], errFuncs ...ErrFunc) ErrSeq[T]

ToErrSeq will turn a iter.Seq[T] into an iter.Seq2[T, error] iterator, and use the error function to yield potential issues with the iteration.

Example
seq1Iter := iterkit.Slice([]int{1, 2, 3})
errIter := iterkit.ToErrSeq(seq1Iter)
for v, err := range errIter {
	if err != nil {
		// will be always nil for the []int slice
	}
	_ = v // 1, 2, 3...
}
Output:

type I1 added in v0.295.0

type I1[T any] interface {
	iter.Seq[T] | ErrSeq[T]
}

type KV

type KV[K, V any] struct {
	K K
	V V
}

func CollectKV

func CollectKV[K, V any](i iter.Seq2[K, V]) []KV[K, V]
Example
var itr iter.Seq2[string, int]

ints := iterkit.CollectKV(itr)
_ = ints
Output:

type KVMapFunc added in v0.295.0

type KVMapFunc[KV any, K, V any] func(K, V) KV

type PullIter

type PullIter[V any] interface {
	// Next will ensure that Value returns the next item when executed.
	// If the next value is not retrievable, Next should return false and ensure Err() will return the error cause.
	Next() bool
	// Value returns the current value in the iterator.
	// The action should be repeatable without side effects.
	Value() V
	// Closer is required to make it able to cancel iterators where resources are being used behind the scene
	// for all other cases where the underling io is handled on a higher level, it should simply return nil
	io.Closer
	// Err return the error cause.
	Err() error
}

PullIter define a separate object that encapsulates accessing and traversing an aggregate object. Clients use an iterator to access and traverse an aggregate without knowing its representation (data structures). Interface design inspirited by https://golang.org/pkg/encoding/json/#Decoder https://en.wikipedia.org/wiki/Iterator_pattern

func ToPullIter

func ToPullIter[T any](itr ErrSeq[T]) PullIter[T]

type SingleUseErrSeq added in v0.295.0

type SingleUseErrSeq[T any] = ErrSeq[T]

SingleUseErrSeq is an iter.Seq2[T, error] that can only iterated once. After iteration, it is expected to yield no more values. For more information on single use sequences, please read the documentation of SingleUseSeq.

func BufioScanner

func BufioScanner[T string | []byte](s *bufio.Scanner, closer io.Closer) SingleUseErrSeq[T]

type SingleUseSeq added in v0.287.0

type SingleUseSeq[T any] = iter.Seq[T]

SingleUseSeq is an iter.Seq[T] that can only iterated once. After iteration, it is expected to yield no more values.

Most iterators provide the ability to walk an entire sequence: when called, the iterator does any setup necessary to start the sequence, then calls yield on successive elements of the sequence, and then cleans up before returning. Calling the iterator again walks the sequence again.

SingleUseSeq iterators break that convention, providing the ability to walk a sequence only once. These “single-use iterators” typically report values from a data stream that cannot be rewound to start over. Calling the iterator again after stopping early may continue the stream, but calling it again after the sequence is finished will yield no values at all.

If an iterator Sequence is single use, it should either has comments for functions or methods that it return single-use iterators or it should use the SingleUseSeq to clearly express it with a return type.

func Once

func Once[T any](i iter.Seq[T]) SingleUseSeq[T]

func Sync added in v0.286.0

func Sync[T any](i iter.Seq[T]) (SingleUseSeq[T], func())

Sync ensures that an iterator can be safely used by multiple goroutines at the same time.

Example
src := iterkit.IntRange(0, 100)
itr, cancel := iterkit.Sync(src)
defer cancel()

var g tasker.JobGroup[tasker.FireAndForget]
for range 2 {
	g.Go(func(ctx context.Context) error {
		for v := range itr {
			_ = v // use v
		}
		return nil
	})
}

g.Join()
Output:

type SingleUseSeq2 added in v0.287.0

type SingleUseSeq2[K, V any] = iter.Seq2[K, V]

SingleUseSeq2 is an iter.Seq2[K, V] that can only iterated once. After iteration, it is expected to yield no more values. For more information on single use sequences, please read the documentation of SingleUseSeq.

func Once2

func Once2[K, V any](i iter.Seq2[K, V]) SingleUseSeq2[K, V]

func Sync2 added in v0.286.0

func Sync2[K, V any](i iter.Seq2[K, V]) (SingleUseSeq2[K, V], func())

Sync2 ensures that an iterator can be safely used by multiple goroutines at the same time.

Example
src := iterkit.IntRange(0, 100)
itr, cancel := iterkit.Sync2(iterkit.ToErrSeq(src))
defer cancel()

var g tasker.JobGroup[tasker.FireAndForget]
for range 2 {
	g.Go(func(ctx context.Context) error {
		for v, err := range itr {
			_ = err // handle err
			_ = v   // use v
		}
		return nil
	})
}

g.Join()
Output:

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL