

Go's range clause - peterarmitage
http://www.funcmain.com/for_range

======
peterarmitage
I wrote this after finding a bug in some of my code. Basically, I was
iterating over a slice of inputs, processing each one. On occasion this
processing would reveal new inputs to test, so I appended them to the slice.

    
    
        for i := range input {
            if newValue := test(input); newValue != nil {
                input = append(input, newValue)
            }
        }
    

Of course, this doesn't work, and I figured out (and appreciated) why by
reading Go's spec.

Hopefully this brief guide will be of use to someone.

~~~
waterside81
First off, good write up, I'm sure others will find it useful.

To your specific issue, I thought it was good programming etiquette to never
modify an object you're iterating over, regardless of how the language handles
such a thing. Were my instructors too strict? Is this a common idiom in other
environments?

~~~
peterarmitage
Thank you.

"modify an object" means "change the number of elements", since you obviously
want to be able to manipulate the individual elements of a container as you
iterate over them. The object here is the container, not the elements
themselves.

I can't comment on other languages, but I'd say that guideline is a little too
strict for Go.

The classic implementation of Breadth First Search involves iterating over a
queue as you fill it.

"don't modify the RHS while in a range clause" would be a more suitable
guideline for Go. Note that it's subtley different from "iterating over" \-
indeed, the answer to my bug was to iterate without using the range clause:

    
    
        for i := 0; i < len(input); i++{
            if newValue := test(input); newValue != nil {
                input = append(input, newValue)
            }
        }
    

I now appreciate the difference between this and the range clause - the length
is evaluated every iteration this way. The range clause evaluates it once, at
the beginning - rule (1).

~~~
jamesmiller5
After thinking a bit about your examples it makes me appreciate the keyword:
the range clause's strictness guarantees iterating only on a certain `range`
(self-duh) hence why it's not just called `iterate`.

I found myself making simple mistakes by assuming that range reading on a
synchronous channel would cause the goroutine that is sending to the channel
to become active. Instead, I wanted to use a for-select statements or a
buffered channel because a length guarantee couldn't be made (or so I assume).

------
cdoxsey
Another gotcha with for loops: scoping is a little weird.

    
    
        	xs := []int{1,2,3}
    	for _, x := range xs {
    		go func() {
    			fmt.Println(x)
    		}()
    	}
    

That prints `3 3 3` not `1 2 3`. You can fix it like this:

    
    
            for i := range xs {
    		x := xs[i]
    		go func() {
    			fmt.Println(x)
    		}()
    	}
    

Which seems like it ought to be the default behavior.

~~~
singular
This seems to me to be the same class of error as those that aren't so much a
go thing as a very comma gotcha for closures; you're placing x in the scope of
the for loop and passing it into a series of anonymous functions, so it gets
closed over and referenced as a common variable between each of those
functions; thus before each goroutine has a chance to run the loop has
completed and x == 3. You'll find this behaviour in any language with
closures.

In the second example you're allocating a new local variable on each
iteration, so each individual value gets closed over separately. That's
probably not what you'd want usually, hence that not being default behaviour.

~~~
cdoxsey
Right. I'm just saying I think the scope ought to be different. The 'x' in the
loop should be a new variable each time because its not really 'a common
variable between each of those functions'.

In the

    
    
        for i := 0; i < 10; i++ {}
    

case it's definitely more clear that i should be the same thing between
iterations. (So you can tinker with i inside the loop) It just seems like they
could've done something different for the 'range' for loop.

Javascript is plagued with this same problem (though its even worse because it
doesn't even follow { } blocks)

~~~
singular
After I wrote my reply I played around with some C# and found to my surprise
that foreach _does_ in fact provide a new variable to be captured on each
iteration, so clearly this is a design decision that varies between languages.

------
jbert
I dislike the fact that the value in range over a slice is a copy, rather than
an alias, to the slice value.

It seems to me to be strictly less useful than the alternative (aliasing).

And I also don't really buy the argument that "it is a normal assignment and
so has to copy", since:

    
    
        i, v := range s
    

_isn 't_ a normal assignment. It has special rules to do with looping. Having
the additional rule that v aliases to the entry seems to me to be a full win
(too late to change now I guess).

~~~
peterarmitage
This catches people out, and is something that some people on the Go team have
expressed regret over - unfortunately it is too late to change due to
backwards compatibility promises.

[http://youtu.be/p9VUCp98ay4?t=22m18s](http://youtu.be/p9VUCp98ay4?t=22m18s)

[http://golang.org/doc/go1compat.html](http://golang.org/doc/go1compat.html)

~~~
BarkMore
The Go Team regrets defining the scope of the range variables as the for
statement. I am not aware of any regrets regarding the alias issue.

The range variable scope is the one big gotcha that's missing from the
article. See
[http://golang.org/doc/faq#closures_and_goroutines](http://golang.org/doc/faq#closures_and_goroutines)
for one discussion of the issue.

------
rogerbinns
Does go have a generic iterator interface? When I looked through the docs
range seemed the closest, but all the examples seemed tied to array or an
array of the keys of a dictionary.

For example Python has a iterator protocol and language support, and Java has
Iterator/Iterable with language support.

------
kragen
Strangely, the thing I miss most is

    
    
        for i in range(100):
    

except with 100 being, normally, not a constant.

~~~
cdoxsey
really? what's wrong with:

    
    
        for i := 0; i < 100; i++ { }
    

a little more going on... but not much.

~~~
kragen
The first one is eight tokens, of which at least three are necessary: 100,
for, and i; let's say four. The second one is 13 tokens. That means it has
more than twice as much noise to distract you from the signal, and to get
right when you write the code. As a result, variations like these require more
attention to notice when you're reading the code:

    
    
        for i = 0; i < 100; i++ { }
        for i := 1; i < 100; i++ { }
        for i := 0; j < 100; i++ { }
        for i := 0; i <= 100; i++ { }
        for i := 0; i++; i < 100 { }
    

The first has no counterpart in Python (where the 8-token version comes from)
since Python _always_ has that bug. The others are:

    
    
        for i in range(1, 100):
        i=0; while j < 100: i += 1; ...
        for i in range(101):
        raise TypeError
    

In short, every bit of _extraneous_ information you put into your code
distracts you from the _relevant_ information, and that extraneous information
is _something else you can get wrong_. Golang does a lot better at this than C
does, but it could do better still.

In some cases, where Golang is noisier than Python or Ruby, it's because the
extra redundancy is there to catch errors or encourage you to handle failures
properly. This is not one of those cases.

