Go iterators

Pythonic Go code.

go
Author

A. Coady

Published

January 30, 2025

Pythonic Go code.

The long arc of language convergence continues. With generics and iterators, Go is significantly closer to Python’s reusability and expressiveness.

Take the built-in function sum. Prior to generics, a Go version would have needed a separate implementations for int and float64. But that was the least of the problems. Even with a Number interface, it also would have been restricted to a slice of numbers. Realistically, one never has a slice of numbers. One has a slice of objects with a numeric field, or perhaps a map with numeric values.

Enter iterators. The range over function types are clunkier than Python generators, but to the caller the range interface is the same.

type Number interface {
    constraints.Integer | constraints.Float | constraints.Complex
}

func Sum[N Number](seq iter.Seq[N]) N {
    var total N
    for value := range seq {
        total += value
    }
    return total
}

The standard library includes helpers slices.Values, maps.Keys, and maps.Values. So there is no barrier to making iter.Seq the standard input for iterables.

The Go equivalent of itertools is now inevitable. With one key difference: the lack of generator expressions will make Go more function oriented. slices.ContainsFunc is the equivalent of any; similarly the equivalent of all would likely embed the predicate.

func AllFunc[E any](seq iter.Seq[E], f func(E) bool) bool {
    for value := range seq {
        if !f(value) {
            return false
        }
    }
    return true
}

The big question is whether MapFunc and FilterFunc become standard. They have not been added to the slices package - presumably because iterators were coming - though DeleteFunc already managed to sneak in. Just imagine Python without comprehensions; map and filter would be ubiquitous.

Sort keys

A related area of convergence is comparison functions. Go uses ternary cmp functions, and Python used to. Collective Python experience has shown that while more general, a ternary comparison is rarely needed. Even more so with the ability to reverse and stably sort.

Go does not have the equivalent of Python’s orderable tuples, so a ternary cmp will always have its place. Still, sorting by scalars is extremely common, and Go could add key-based ordering, with iterator support.

Note bound and unbound methods are also first class functions. So models with a standard ordering or identifier could add methods for each. Then pass (*Type).Compare as a cmp function, and (*Type).Key as a key function. The readability of function parameters is even better when referenced by name instead of a lambda.

The key function approach is also simpler and more efficient for comparable use cases. Utilities like slices.CompareFunc could be iter.CompareBy and not repeat logic.

Sets

Go does not have a built-in set type. The options are to use a map explicitly, or via a library which wraps a map and ignores the values. The author’s iterset package demonstrates that a Pythonic approach is now possible. Set operations require either O(1) lookup - which maps already support - or mere iteration. So a custom map type is useful for efficiency, but its methods can support iter.Seq instead, just as Python methods do. This is both more flexible and efficient in most cases.

Take IsSubset as an example. The four interface choices have usage and performance trade-offs.

  1. IsSubset(iter.Seq[K], iter.Seq[K]): most flexible, has to convert right argument to #2
  2. IsSubset(iter.Seq[K], map[K]V)): fast, zero-copy, and flexible
  3. IsSubset(map[K]V), iter.Seq[K]: similar to #1, but converting the right argument may exit early and use less space
  4. IsSubset(map[K]V1), map[K]V2): similar to #2, but could exit early with a size check

Without function overloads, designing the interface for all use cases is a challenge. But in no case is forcing the caller to convert both arguments to map[K]struct{} first an improvement.

Conclusion

Go has just started to include iterator utilities like slices.Sorted{Func}. Iterators are going to have a transformative effect on readability.