Iterators and range over functions
What range can loop over since Go 1.22 and 1.23, and why maps.Keys returns a function.
- Write a loop with
rangeover an integer. - Write an
iter.Seq[int]function that stops whenyieldreturns false. - Predict what printing
maps.Keys(m)shows, and fix it withslices.Sorted.
range over an int
Since Go 1.22, range works on a plain integer. for i := range 5 counts 0 to 4. It is the three-part loop with less to get wrong.
package mainimport "fmt"func main() { for i := range 3 { fmt.Println("attempt", i+1) }}What an iterator is
An iterator is a function that produces values one at a time by calling a function you give it, called yield. When yield returns false, the loop has stopped (a break, say), and the iterator must stop too.
Since Go 1.23 you can range over such a function directly. The iter package names the two shapes: iter.Seq[V] gives one value per step, iter.Seq2[K, V] gives two, like a map’s key and value.
package mainimport ( "fmt" "iter")// Countdown yields n, n-1, ... 1.func Countdown(n int) iter.Seq[int] { return func(yield func(int) bool) { for i := n; i > 0; i-- { if !yield(i) { // the loop said stop return } } }}func main() { for v := range Countdown(5) { if v == 2 { break // yield returns false, and Countdown returns } fmt.Println(v) }}Two values per step: iter.Seq2
package mainimport ( "fmt" "iter")// Lines yields each line number with its text.func Lines(lines []string) iter.Seq2[int, string] { return func(yield func(int, string) bool) { for i, l := range lines { if !yield(i+1, l) { return } } }}func main() { for n, text := range Lines([]string{"package main", "", "func main() {}"}) { fmt.Printf("%d: %q\n", n, text) }}The standard library speaks iterators
maps.Keys(m) and maps.Values(m) return an iter.Seq, not a slice, so nothing is copied until you ask. slices.Collect turns any iter.Seq into a slice, and slices.Sorted collects and sorts in one step. slices.All and slices.Values go the other way, turning a slice into an iterator.
package mainimport ( "fmt" "maps" "slices")func main() { stock := map[string]int{"pears": 3, "apples": 5, "figs": 1} keys := slices.Sorted(maps.Keys(stock)) // sorted []string, every run fmt.Println(keys) counts := slices.Collect(maps.Values(stock)) // a []int, in random order fmt.Println(len(counts), "values") for _, k := range keys { fmt.Println(k, stock[k]) }}for i := range ncounts from 0 to n-1 (Go 1.22).- An iterator is a function that calls
yieldfor each value, and stops whenyieldreturns false (Go 1.23). iter.Seq[V]yields one value per step,iter.Seq2[K, V]yields two.maps.Keysreturns an iterator.slices.Collectandslices.Sortedturn one into a slice.
func Fib() iter.Seq[int] that yields Fibonacci numbers forever. Range over it and break after the first value above 1000. Then write Evens(seq iter.Seq[int]) iter.Seq[int] that filters another iterator.Evens(seq iter.Seq[int]) iter.Seq[int] that yields only the even numbers from seq, and stops when yield returns false. Examples: 1 2 3 4 → 2 4, 1 3 5 → (nothing), 0 -2 7 → 0 -2.Tab indents. Press Esc, then Tab, to leave the editor.
- 1Remove the
if !yield(i)check fromCountdown, callyield(i)on its own, and break out of the loop early. - 2Print
maps.Keys(m)directly withfmt.Println. - 3Loop over
slices.All([]string{"a", "b"})and print what you get each step.