Repository: go
Issue: proposal: spec: improve for-loop ergonomics
Labels: LanguageChange,Proposal,LanguageChangeReview

Motivation
----------

Go's for-loops encourage difficult-to-read code.

1. The Go 1 loop syntax sets the wrong defaults. The syntax is optimized for three-part [`ForClause`](https://golang.org/ref/spec#ForClause) loops, but `range` loops are far more common (by a ratio of nearly 4:1 in the code I sampled) and arguably ought to be viewed as the “default”.
   
	* The three-part `ForClause` form is nearly always used for iterating one variable over sequential integers. That puts the interesting part — the condition — in the middle, where it is the hardest to find.
	
	  (For the rare other cases, it is always possible to express a three-part `ForClause` as an equivalent one-part `ForClause` with an extra scope block. Loops that use `continue` require care, but `continue` in a three-part non-integer loop is especially rare.)
	
	* Nothing else in the language has a three-part form, and the existence of the three-part `for` loop precludes a more useful two-part alternative (for APIs such as `io.Reader`), because it would be too easy to confuse a two-part loop with a three-part one.

2. The `range` keyword is confusing to newcomers.

	* In set theory, [_range_](http://mathworld.wolfram.com/Range.html) means "image" or "codomain", but the single-value version of a Go 1 `range` loop instead iterates over the _domain_ of the slice, map, or array. That makes the single-value form confusing, especially when the index and element types are mutually assignable (https://play.golang.org/p/c-lWoTI_Z-Y) or when the value is used as an `interface{}` (https://play.golang.org/p/cqZPSHZtuwH).

	* In some other programming languages (such as Python), `range` refers to a sequence of points in a numerical interval, evoking [line segment range](http://mathworld.wolfram.com/LineSegmentRange.html) or [statistical range](http://mathworld.wolfram.com/StatisticalRange.html). In contrast, the Go `range` keyword doesn't have anything to do with numerical intervals, except to the extent that slice indices happen to be intervals.

	* The fact that `range` modifies the semantics of `:=` and `=` is surprising. The only other Go operator that modifies the semantics of another operator is `=` itself, which (beyond the `, ok` idiom) modifies the semantics of the index operator (`[]`) for map assignments. (I think we should fix that too; see https://github.com/golang/go/issues/20660#issuecomment-367781440.)
	   
	  It is rarely useful to have a `range` loop assign to existing variables, and we could address that use-case more cleanly with a `finally` or `else` keyword anyway.

3. Eliminating the `range` keyword would allow us to fix variable capture (https://github.com/golang/go/issues/20733) in a way that does not unexpectedly change the semantics of `for`-loops written in the Go 1 style. (That is, old-style loops would no longer compile, instead of successfully compiling to something different from before.)

----

Proposal
--------

1. Remove the `range` keyword and the three-part loop form.

2. Make the `range` form of the `for` loop more concise, and add a two-part form and optional `else` block.

	* For the one-part form:
	
		* If the first part is of the form `x : z` or `x, y : z`, it introduces new variables `x` and `y` (as applicable), which take the value of each successive element of `z`. The one-variable form can be used only for channels and numeric intervals (see `interval` below). The two-variable form can be used only for maps, slices, strings, and arrays.

		* Otherwise, the first part must be a boolean expression and specifies the `Condition` of the loop.

	* The new two-part form parallels the two-part form of `switch`. The first part is an arbitrary statement (usually a short variable declaration) to be evaluated before every iteration, and the second part is the `Condition`:
	  
	  `for x, err := f(); err == nil {`

	* An `else` block may follow a loop that has with a `Condition`. Control transfers to the `else` block when the condition is false (like `else` in Python loops). The variables declared in the first part of the two-part form remain in scope for the `else` block.

		* (If we don't like the way `else` reads, we could drop that part entirely, or use some other keyword — such as `finally` — and/or tweak the semantics, for example by also transferring control to the block in case of a `break`.)

3. Add a built-in pseudofunction `interval` to replace the vast majority of existing 3-part loops.

	* `interval(m, n)` returns a container that iterates over `[m, n)` by increments of `1`.
	
	* `interval(m, n, step)` returns a container that iterates from `m` (inclusive) to `n` (exclusive) by `step`.

----

Examples
--------

### Simple conditions

Loops with just a `Condition` remain the same as in Go 1.

```go
for a < b {
	a *= 2
}
```

----

```go
for len(h) > 0 {
	x := heap.Pop(h)
	f(x.(someType))
}
```

### Ranges

Range loops lose a little bit of boilerplate, and gain a closer resemblance to for-each loops in other languages with C-like syntax (such as C++ and Java).

```go
for i, s := range a {
	g(i, s)
}
```

becomes

```go
for i, s : a {
	g(i, s)
}
```

----

(from https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating)

```go
b := a[:0]
for _, x := range a {
	if f(x) {
		b = append(b, x)
	}
}
```

becomes

```go
b := a[:0]
for _, x : a {
	if f(x) {
		b = append(b, x)
	}
}
```

### Intervals

Simple numeric intervals move the limit closer to the end of the line (where it is easier to find), and in some cases drop the need for redundant variables.

```go
for i, n := 0, f(x); i < n; i++ {
	g(i)
}
```

becomes

```go
for i : interval(0, f(x)) {
	g(i)
}
```

----

```go
for n := runtime.GOMAXPROCS(0); n > 0; n-- {
	go …
}
```

becomes

```go
for n : interval(runtime.GOMAXPROCS(0), 0, -1) {
	go …
}
```

----

(from https://github.com/golang/go/wiki/SliceTricks#reversing, noting that the original goes out of its way
 — and loses some clarity in the process — to avoid re-evaluating `len(a)/2` at each iteration)

```go
for i := len(a)/2-1; i >= 0; i-- {
	opp := len(a)-1-i
	a[i], a[opp] = a[opp], a[i]
}
```

becomes

```go
for i : interval(0, len(a)/2) {
	opp := len(a)-1-i
	a[i], a[opp] = a[opp], a[i]	
}
```

or

```go
for i, _ : a[:len(a)/2] {
	opp := len(a)-1-i
	a[i], a[opp] = a[opp], a[i]	
}
```


### Iterators

Iterator patterns shed boilerplate and/or levels of indentation.

```go
for {
	n, err := r.Read(p)
	if err != nil {
		if err != io.EOF {
			return err
		}
		return nil
	}
	f(p[:n])
}
```

becomes

```go
for n, err := r.Read(p); err == nil {
	f(p[:n])
} else if err != io.EOF {
	return err
}
return nil
```

----

```go
iter := Begin()
for x, ok := iter.Next(); ok; x, ok = iter.Next() {
	f(x)
}
```

becomes

```go
iter := Begin()
for x, ok := iter.Next(); ok {
	f(x)
}
```

### Lists

Loops iterating over certain custom containers (such as linked lists) become a bit more awkward.
(On the other hand, I would argue that they were awkward to begin with — and they could be fixed by a further change to allow types to implement the `range`-like behavior directly.)

```go
for e := l.Front(); e != nil; e = e.Next() {
	f(e)
}
```

becomes

```go
e := l.Front()
for e != nil {
	f(e)
	e = e.Next()
}
```