> I'D DO THIS EXCEPT FOR ALL THE PLACES WHERE I'VE SEEN "DON'T DO THIS."
> // Iterator using channels.
> // This is what I want to accomplish, but in a non-channel way.
> // That is, unless channels have overcome the problems mentioned in
> the message links
> // and are now considered the idiomatic go iterator pattern.
I would say that it is fine to use a channel as an iterator as long as
you know what you are doing. It's not the same as using yield. But if
you are just iterating over slices it works fine.
Passing in a function literal also works fine, of course.
You can also write a little iterator type, but it's definitely more
tedious. Untested code:
type Iterator struct {
Store *store
customer int
payment int
}
func Begin(store *Store) *Iterator {
p := &Iterator{store}
for p.customer < len(store.customerList) && len(store.customerList[p.customer].paymentList) == 0 {
p.customer++
}
}
func (p *Iterator) Done() bool {
return p.customer >= len(p.store.customerList)
}
func (p *Iterator) Next() *Payment {
r := p.store.customerList[p.customer].paymentList[p.payment]
p.payment++
if p.payment >= len(p.store.customerList[p.customer]) {
p.customer++
p.payment = 0
for p.customer < len(p.store.customerList) && len(p.store.customerList[p.customer].paymentList) == 0 {
p.customer++
}
}
Use it as in
p := Begin(store)
while !p.Done() {
payment := p.Next()
// ...
}
Ian
WillRubin <endo...@gmail.com> writes:I would say that it is fine to use a channel as an iterator as long as
> I'D DO THIS EXCEPT FOR ALL THE PLACES WHERE I'VE SEEN "DON'T DO THIS."
> // Iterator using channels.
> // This is what I want to accomplish, but in a non-channel way.
> // That is, unless channels have overcome the problems mentioned in
> the message links
> // and are now considered the idiomatic go iterator pattern.
you know what you are doing. It's not the same as using yield. But if
you are just iterating over slices it works fine.
Passing in a function literal also works fine, of course.
You can also write a little iterator type, but it's definitely more
tedious. Untested code:
one crucial difference: if you're using a simple channel iterator and
you want to break half way through the loop, you will generate non-collectable
garbage (the goroutine), whereas closures can be GC'd.
it's not hard to make a channel iterator that can be stopped,
but it requires care and a little more code.
This code will deadlock if the test on k (line 47) to break is the
number of items instead of 3 (try with 'if k == 12'):
the iterator goroutine will be done and the kill <- 1 will block.
Using a select with an empty default clause (so that the send doesn't
block) does the trick:
if k == 3 {
select {
case kill<- 1:
default:
}
break
}
No - if the goroutine isn't ready to receive, the message will be lost. A buffered channel works better, as I outlined above.
the iterator might not have stopped - it may still be inside the loop
but not inside the select statement. that is, this method
may leave the iterator goroutine still around indefinitely
because the kill message may never be received.
you can easily simulate this occurrence by adding a sleep
after the select in the iterator.