The readability this provides is considerable.
The readability this provides is considerable.
Lets say the logic is:
for item := range list {
if condition(item) {
break
}
}
That is nice and readable, but if the thing your iterating is returning items in a batch then this gets far, far uglier. Its possible to end up with one loop that fetches x items at a time, and another that iterates over those items.. etc. But if you switch that to channels you maintain the readability and everything is nice! .. Except, you need a way to cancel the iteration, and it can't be closing the channel or the writer will panic. items, cancelFunc := apiCall()
for item := range items {
if condition(items) {
cancelFunc()
for _ := range items { }
break
}
}
close(items)
Now, this is even worse if you need some kind of finalization of the goroutine that spawns to write to the given channel. Now you have a wait group, or a second channel.. etc. If its possible to error in the middle of iteration then things get complicated as well. topLoop:
for _, batch := range list {
for _, item := range batch {
if condition(item) {
break topLoop
}
}
}
The language also supports a limited goto. Combined with select and context, I've never seen a situation I felt I couldn't adequately describe. type Iterator[T any] interface {
HasNext() bool
GetNext() T
}
And then use it like: for i.HasNext() {
current := i.GetNext()
// do things here
}
It's an extra line, sure, but it's pretty obvious what's going on.Iterator design is another area where sum types would help simplify things a lot. Just one method returning Some/None would be so much better.
It would be better, but since go has MRV and zero values
func Next() (hasNext bool, nextValue T)
works fine, this is not Java or C# where you need to split the protocol into two separate operations.If you're looking for those particular features, I'd recommend adopting go's `, ok` idiom that appears in several places in the language. A type like:
type Iterator[T any] interface {
Next() (T, bool)
}
Lets you write code like this, if that's more to your liking: for current, ok := i.Next(); ok; current, ok = i.Next() {
// do things here
}
Which, really seems like a more go-like way to do this in the language as well (ranging over a function then becomes syntactic sugar for the above).Slower. More complex. Why?!?
Just call your function in a loop.
It's awful, but they do it.
Why would that be slower?
... that's the theory behind it. For more grounded explanations, see [1] & [2].
Thanks for the links!