On 06 December 2012, Jan Mercl said:
> On Thu, Dec 6, 2012 at 6:36 PM, Greg Ward <
gr...@gerg.ca> wrote:
> > a couple of Gophers have independently hit upon an elegant pattern for
> > iterators in Go.
>
> I have to say the obvious: `elegant` is _totally_ subjective.
Fair enough.
> > Say you define a FooList type, which might be
> >
> > type FooList []Foo
> >
> > or
> >
> > type FooList struct {
> > list []Foo
> > }
> >
> > ...or whatever. Your users can't use range, but you want for loops to
> > be easy for them.
>
> In the first case users _can_ use range. In the second they've been
> intentionally disallowed to do that (assuming "user" lives in another
> package). Isn't it just simpler to let the user do what he needs to
> do?
Not all data structures are as simple as a wrapper around a slice. I
would never in a zillion years expect the "range" operator to
recognize my particular implementation of a directed graph, nor for it
to iterate over that graph in topological order. Of *course* I have to
write my own DFS that yields a topo sort. I'm just trying to expose
that functionality to the rest of my app in a nice, clean way.
Currently I'm using a callback:
// Callback function to visit nodes from DFS(). Return a non-nil error
// to abort the traversal and make DFS() return that error. DFS()
// aborted this way does not report dependency cycles.
type DFSVisitor func(id int) error
// Perform a partial depth-first search of the graph, exploring
// ancestors of all nodes in 'start'. For each node visited, call
// visit() just as we're leaving that node -- i.e. calls to visit()
// are in topological order. visit() can abort the search; see
// DFSVisitor for details.
func (self *DAG) DFS(start NodeSet, visit DFSVisitor) error {
[...]
}
That works fine, but it leads to slightly inside-out code, e.g.
visit := func(id int) error {
[...do stuff with the node specified by id...]
}
err := self.dag.DFS(targets, visit)
The closure-as-iterator pattern appeals because it lets you write
something like
iter := dag.Iterator()
for id, ok := iter(); ok; id, ok := iter() {
[...do stuff with the node specified by id...]
}
which I find slightly more readable. I thought it would be worth
playing around to see if I could figure out a way to make this work
despite having a recursive iteration algorithm.
Very droll. For the record, I did not write that code, but I like the
way it lets the data structure hide its inner workings from callers,
but still lets callers use a normal "for" loop. Perhaps a callback
would be more idiomatic Go, but this strikes me as a nice use of the
"comma ok" idiom.
Greg