Skip to content

proposal: iter: add method to perform delegated yield with returned bool #73792

Description

@DeedleFake

Proposal Details

The blog post about range-over-func gives the following example of a recursive iter.Seq over the values of a binary tree:

func (t *Tree[E]) All() iter.Seq[E] {
    return func(yield func(E) bool) {
        t.push(yield)
    }
}

func (t *Tree[E]) push(yield func(E) bool) bool {
    if t == nil {
        return true
    }
    return t.left.push(yield) &&
        yield(t.val) &&
        t.right.push(yield)
}

I have built recursive iterators of similar patterns on several occasions, including one recently. The secondary method is necessary because iter.Seq does not return a final boolean indicating if it exited early or not. I remember that the original prototype had an extra boolean, but it was completely ignored by the iterator mechanism. However, every time I write a recursive iterator, it requires a function of a similar design, and I've been starting to think that having something like that in the standard library would be nice. It's essentially the equivalent of yield* in Python and similar things in a lot of other languages with generator functions.

For example,

package iter

// Push iterates over seq, calling yield for each value. It returns
// false if yield ever does and returns true otherwise.
//
// Push is primarily useful for building iter.Seq implementations that
// at some point yield all of the values of a different iter.Seq
// instance such as iterators over recursive data structures.
func (seq Seq[T]) Push(yield func(T) bool) bool {
	for v := range seq {
		if !yield(v) {
			return false
		}
	}
	return true
}

and the equivalent for iter.Seq2.

This would change recursive binary tree iterator example to just

func (t *Tree[E]) All() iter.Seq[E] {
	return func(yield func(E) bool) {
		if t == nil {
			return true
		}
		_ = t.left.All().Push(yield) &&
			yield(t.val) &&
			t.right.All().Push(yield)
	}
}

The need for _ = is a bit weird, but overall I'll take that minor awkwardness over rewriting this function all over the place.

I also considered a top-level function, something like func YieldAll[T any](seq iter.Seq[T], yield func(T) bool), but the method feels a bit nicer to me.

Edit: Forgot to mention alternative method names. I also thought of Via(), YieldTo(), and a few others. I'm very much not too picky about the bikeshed's color, though.

Metadata

Metadata

Assignees

No one assigned

    Labels

    LibraryProposalIssues describing a requested change to the Go standard library or x/ libraries, but not to a toolProposal

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions