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

  1. runtime: Add runtime.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()
}
  1. database/sql: Add database/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.