Proposal Details
I propose to add the following function to ease the creation of an iterator:
func New[T any](v T, next func() (T, bool)) iter.Seq[T] {
return func(yield func(T) bool) bool {
for ok := true; ok; v, ok = next(v) {
if !yield(v) {
return false
}
}
return true
}
}
Update: Instead of New
, another name ToSeq
is suggested.
Note: The example below is erroneous. Iteration should not call defer
. Moreover, instead of providing an All
method that returns an iter.Seq
or an Iter
method that returns a pulling iterator, the simplest and least error-prone way is to write an All
method with signature func (l *List[T]) All(f func(*Element[T]) bool)
.
Example
type Element[TElem] struct {
next *Element[TElem]
val TElem
}
func (e *Element[TElem]) Next() (*Element[TElem], bool) {
if e.next != nil {
return e.next, true
}
return nil, false
}
type List[TElem any] struct {
root *Element[TElem]
}
func New[TElem any](val TElem) *List[TElem] {
return &List{&Element{nil, val}}
}
func All[TElem any](l *List[TElem]) iter.Seq[*Element[TElem]] {
next := func[TElem any]() func() (*Element[TElem], bool) {
e := l.root
ok := e.next != nil
return func[TElem any]() (*Element[TElem], bool) {
defer func() { e, ok = e.Next() }
return e, ok
}
}
return iter.New(l.root, next())
}
Expected changes to stdlib
runtime
: Addruntime.AllCallersFrames(callers []uintptr) iter.Seq[runtime.Frame]
.
Then, one can write the following:
func main() {
c := func() {
pc := make([]uintptr, 10)
n := runtime.Callers(0, pc)
if n == 0 {
return
}
pc = pc[:n]
frames := xiter.Filter(
runtime.AllCallersFrames(pc),
func (f runtime.Frame) bool {
return strings.Contains(f.File, "runtime/")
})
for frame := range frames {
fmt.Println(frame.Function)
}
}
b := func() { c() }
a := func() { b() }
a()
}
database/sql
: Adddatabase/sql.db.QueryContextAll
.
With error handling flow from #67913, one can write the following:
func main() {
defer handle(func(e error) { log.Fatal(e) })
age := 27
rows := try(db.QueryContextAll(ctx, "SELECT name FROM users WHERE age=?", age))
defer rows.Close()
names := make([]string, 0)
for row := range rows {
var name string
try(scan(row, &name))
names = append(names, name)
}
log.Printf("%s are %d years old", strings.Join(names, ", "), age)
}
Comment From: anonld
Perhaps renaming the New
function to iter.ToSeq
can better indicate that it is converting a pulling iterator to a pushing iter.Seq
.
Regarding the suggested stdlib
API change, perhaps those package should keep providing pulling iterators like:
next := x.Iter()
for v, ok := next(); ok; v, ok = next() {
fmt.Println(v)
}
Rather than each package providing a function that converts a pulling iterator to iter.Seq
, people simply need to call a standard iter.ToSeq(x.Iter())
if they need such conversion.
Comment From: anonld
Since I filed this proposal for API change to the iter
package, I think I can suggest adding a few more marginally related functions and types to the iter
package in this issue. (If not, I will file another issue then.)
In #67924, I suggest adding UnwrapAll(err error) iter.Seq[error]
to the errors
package. With some more experiments of error inspection, I suggest adding an Inspect
function with signature func(error, inspector[error]) bool
to the errors
package in #67913 as an replacement for the UnwrapAll
function. Starting from there, I generalize the Inspect
function to a generic tree walking iterator and want to add the following the the iter
package:
type visitor[T any] func(T, inspector[T]) bool
func (v visitor[T]) Walk(x T, f inspector[T]) { v(x, f) }
type Walker[T any] struct {
root T
v interface{ Walk(T, inspector[T]) }
}
func NewWalker[T any](root T, v visitor[T]) Iter[T] { return &Walker[T]{root, v} }
func (w *Walker[T]) All(f func(T) bool) { w.v.Walk(w.root, f) }
type Iter[T any] interface {
All(f func(T) bool)
}
Example usage:
w := iter.NewWalker(err, errors.Inspect)
for e := range w.All {
fmt.Println(e)
}
With w.All
, the UnwrapAll
function is no longer needed for iterating an error tree or trees of other types such as those in go/ast
and io/fs
.
Note: Perhaps iter.NewWalker
is duplicating #67795, #66455 and #64341.
Update 2: the latest revision of errors.Handle
can keep errors.inspect
unexported, but whether to export the inspect
function is out of the scope of this proposal.
Update: one may argue that calling errors.Inspect(err, inspectfunc)
can do the job very well already, so is there a need for a standard iter.Walker
in the first place? Well, I agree that there is too much wrapping for the errors.Inspect
function in my example above, but my point is that people can then write function utilising the standard Walker
as they do for io.Reader
and the like. For example, one can pass w.All
to xiter.Filter
(but the cost of doing so is in doubt, as one can write a cheaper Inspect(x, filterfunc)
in such scenario). If you have any argument for/against the iter.Walker
, please feel free to comment below. (Again, I will file a separate issue for iter.Walker
if required.)
Comment From: seankhliao
See the discussion in https://github.com/golang/go/issues/61897 We expect iterators to primarily be defined as push style iterators.