iteratorgo

What is most idiomatic way to create an iterator in Go?


One option is to use channels. Channels are like iterators in a way and you can iterate over them using range keyword. But when you find out you can't break out of this loop without leaking goroutine the usage becomes limited.

What is the idiomatic way to create iterator pattern in go?

Edit:

The fundamental problem with channels is they are a push model. Iterator is is a pull model. You don't have to tell iterator to stop. I'm looking for a way to iterate over collections in a nice expressive way. I would also like to chain iterators (map, filter, fold alternatives).


Solution

  • Channels are useful, but closures are often more suitable.

    package main
    
    import "fmt"
    
    func main() {
        gen := newEven()
        fmt.Println(gen())
        fmt.Println(gen())
        fmt.Println(gen())
        gen = nil // release for garbage collection
    }
    
    func newEven() func() int {
        n := 0
        // closure captures variable n
        return func() int {
            n += 2
            return n
        }
    }
    

    Playground: http://play.golang.org/p/W7pG_HUOzw

    Don't like closures either? Use a named type with a method:

    package main
    
    import "fmt"
    
    func main() {
        gen := even(0)
        fmt.Println(gen.next())
        fmt.Println(gen.next())
        fmt.Println(gen.next())
    }
    
    type even int
    
    func (e *even) next() int {
        *e += 2
        return int(*e)
    }
    

    Playground: http://play.golang.org/p/o0lerLcAh3

    There are tradeoffs among the three techniques so you can't nominate one as idiomatic. Use whatever best meets your needs.

    Chaining is easy because functions are first class objects. Here's an extension of the closure example. I added a type intGen for integer generator which makes it clear where generator functions are used as arguments and return values. mapInt is defined in a general way to map any integer function to an integer generator. Other functions such as filter and fold could be defined similarly.

    package main
    
    import "fmt"
    
    func main() {
        gen := mapInt(newEven(), square)
        fmt.Println(gen())
        fmt.Println(gen())
        fmt.Println(gen())
        gen = nil // release for garbage collection
    }
    
    type intGen func() int
    
    func newEven() intGen {
        n := 0
        return func() int {
            n += 2
            return n
        }
    }
    
    func mapInt(g intGen, f func(int) int) intGen {
        return func() int {
            return f(g())
        }
    }
    
    func square(i int) int {
        return i * i
    }
    

    Playground: http://play.golang.org/p/L1OFm6JuX0