type Number interface {
| constraints.Float | constraints.Complex
constraints.Integer
}
iter.Seq[N]) N {
func Sum[N Number](seq
var total Nfor value := range seq {
+= value
total
}return total
}
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.
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.
any](seq iter.Seq[E], f func(E) bool) bool {
func AllFunc[E 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.
IsSubset(iter.Seq[K], iter.Seq[K])
: most flexible, has to convert right argument to #2IsSubset(iter.Seq[K], map[K]V))
: fast, zero-copy, and flexibleIsSubset(map[K]V), iter.Seq[K]
: similar to #1, but converting the right argument may exit early and use less spaceIsSubset(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.