Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

iterator

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).

like image 675
Kugel Avatar asked Dec 22 '12 06:12

Kugel


People also ask

Does go have iterators?

Iterator is a behavioral design pattern that allows sequential traversal through a complex data structure without exposing its internal details. Thanks to the Iterator, clients can go over elements of different collections in a similar fashion using a single iterator interface.


2 Answers

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

like image 184
Sonia Avatar answered Oct 22 '22 22:10

Sonia


TL;DR: Forget closures and channels, too slow. If the individual elements of your collection are accessible by index, go for the classic C iteration over an array-like type. If not, implement a stateful iterator.

I needed to iterate over some collection type for which the exact storage implementation is not set in stone yet. This, plus the zillions other reasons to abstract the implementation details from the client, lead me to do some testing with various iteration methods. Full code here, including some implementations that make use of errors as values. Here are the benchmark results:

  • classic C iteration over an array-like structure. The type provides the methods ValueAt() and Len():

    l := Len(collection) for i := 0; i < l; i++ { value := collection.ValueAt(i) } // benchmark result: 2492641 ns/op 
  • Closure style iterator. The collection's Iterator method returns a next() function (a closure over the collection and cursor) and a hasNext boolean. next() returns the next value and a hasNext boolean. Note that this runs much faster than using separate next() and hasNext() closures returning single values:

    for next, hasNext := collection.Iterator(); hasNext; {     value, hasNext = next() } // benchmark result: 7966233 ns/op !!! 
  • Stateful iterator. A simple struct with two data fields, the collection and a cursor, and two methods: Next() and HasNext(). This time the Iterator() method of the collection returns a pointer to a properly initialized iterator structure:

    for iter := collection.Iterator(); iter.HasNext(); {     value := iter.Next() } // benchmark result: 4010607 ns/op 

As much as I like closures, performance wise it's a no-Go. As for design patterns, well, Gophers prefer the term "idiomatic way to do" stuff for good reason. Also grep the go source tree for iterators: with so few files that mention the name, iterators are definitely not a Go thing.

Also check out this page: http://ewencp.org/blog/golang-iterators/

Anyhow, interfaces do not help in any way here, unless you want to define some Iterable interface, but this is a completely different subject.

like image 35
wldsvc Avatar answered Oct 22 '22 23:10

wldsvc