Search code examples
goiterator

How to iterate through Iterators manually?


The signatures of iterators are conveniently defined in the upcoming iter package:

type Seq[V any] func(yield func(V) bool)
type Seq2[K, V any] func(yield func(K,V) bool)

And then we can iterate over it.

// All returns an iterator
for v := range myType.All() {
    // do something with v
}

But is there any way to do:

iterator := myType.All()
v := iterator.Next()

?

This is useful, for example to stop iterating from outside of the iterator.

Links:


Solution

  • This is useful, for example to stop iterating from outside of the iterator.

    You can stop iterating using the break statement. This should be enough in most cases.

    If you still want to "manually" iterate over the sequence, you may convert the default, "push" style iterator to a "pull" style iterator using iter.Pull().

    The "pull" style iterator returns 2 functions: next() and stop(), former can be used to obtain the next value of the sequence, the latter to stop iterating (you must call it because the iterator construct may use internal resources which is only released if you call stop()).

    Example using it:

    s := []string{"one", "two", "three", "four", "five", "six"}
    
    iterator := slices.Values(s)
    
    next, stop := iter.Pull(iterator)
    defer stop()
    
    for i := 0; i < 3; i++ {
        v, valid := next()
        if valid {
            fmt.Println(v)
        }
    }
    

    This will output (try it on the Go Playground):

    one
    two
    three
    

    Note that next() not only returns the next value, it also returns a bool indicating whether the value is valid (e.g. if you reached the end of the sequence, the returned value isn't valid).

    If you're sure the returned value is valid (or you don't care if it's valid), you can simplify the above code by omitting the second returned value e.g. using gox.First() (disclaimer: I'm the author):

    s := []string{"one", "two", "three", "four", "five", "six"}
    
    iterator := slices.Values(s)
    
    next, stop := iter.Pull(iterator)
    defer stop()
    
    for i := 0; i < 3; i++ {
        fmt.Println(gox.First(next()))
    }
    

    This will output the same (try it on the Go Playground).

    Personally I don't think this code is simpler, using a for .. range with break is simple enough and still idiomatic. If you use it in many places, you can of course create utility functions that return the first (or some) elements of a sequence, e.g.:

    func Peek[V any](seq iter.Seq[V]) (v V) {
        for v = range seq {
            return v
        }
        return
    }
    

    And using it is like this (try it):

    iterator := slices.Values(s)
    
    fmt.Println(Peek(iterator))