Go's range clause
funcmain.com
funcmain.com
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.
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?
GP can just do an old-school `for` loop without a `range` clause (generally everyone learns about `for` before learning about `range`) and this immediately becomes incredibly easy to express.
"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).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).
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.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.
Capturing variables by value has both safety and performance benefits in a multithreaded world, and it's unfortunate that Go chose not to do that.
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)
for i,x := range xs {
go func(x) {
fmt.Println(x)
}(x)
}Interesting how the go syntax makes it easy to explicitly pass in arguments. That is very nice.
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). a := 1
b := []int{2,3,4}
for i, c := range b {
// lots more code
a = 5
c = 6
// now a is 5 and c is 6 as you'd expect
// but also by magic, b[i] is 6
}
// now by magic, b is {6,6,6}The range variable scope is the one big gotcha that's missing from the article. See http://golang.org/doc/faq#closures_and_goroutines for one discussion of the issue.
Having strict control over reference/value semantics is a feature I don't want to miss in a systems language.
Go code is meant to be concise:
- once a Go developer learned that the above is a copy (which she will learn very early on), she won't ever forget, it's just too fundamental
- to modify the slice, just modify the slice and skip the copy with underscore:
for i, _ := range mySlice { mySlice[i] = "foo" }
There we go. Syntax simplicity (without exotic compiler flags or prep directives) and conciseness fully preserved.because if that "something else" actually is an int -- then you'll have a nasty bug at your hands (and not a compiler-catchable one at that).
For example Python has a iterator protocol and language support, and Java has Iterator/Iterable with language support.
for i in range(100):
except with 100 being, normally, not a constant. for i := 0; i < 100; i++ { }
a little more going on... but not much. 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.