-
Notifications
You must be signed in to change notification settings - Fork 17.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: spec: generic type constraint for iterables #69364
Comments
(Nitpick: this would have to be a new predeclared name (like Go already has a type that expresses an abstract iterator: func Chain[T any](sequences ...iter.Seq[T]) iter.Seq[T] (Let's ignore for now the issue of variations in the element type T.) To call this function with a sequence of different iterators, you would have to adapt each type to iter.Seq, for example: func f(ch chan int, m map[int]int, slice []int) {
for x := range Chain(
chanElems(ch),
maps.Keys(m),
maps.Values(m),
slices.Values(slice)) {
...
}
}
func chanElems[T any](ch chan T) iter.Seq[T] { ... } This proposal is essentially an objection to the need to explicitly inject each sequence type to an iter.Seq. The maps example makes very clear why this is the wrong choice: there are two different possible single-valued iterators (Keys and Values). The design of Go values explicitness when conversions are nontrivial, so I don't think this feature makes sense. |
You could write a func FlattenFunc[S, T any](s iter.Seq[S], func(S) iter.Seq[T]) iter.Seq[T] Could be called with the existing functions like |
Yes, we have an interface for abstract iterables. The problem is that the built-in types don't implement that interface. @adonovan that example of func FlattenIter[V any](outer iter.Seq[iter.Seq[V]]) iter.Seq[V] {
return func(yield func(V) bool) {
for inner := range outer {
for v := range inner {
if !yield(v) {
return
}
}
}
}
} it's not so easy. @generikvault 's suggestion of having a converter function is a good one, which I'd say 80% solves the problem. It's not as convenient, but then I suppose it's good enough to not be worth adding a new pre-defined type. |
FWIW this is related to relaxing the core type restriction of range statements. |
Go Programming Experience
Experienced
Other Languages Experience
go, c, c++, rust, c#, python, javascript...
Related Idea
Has this idea, or one like it, been proposed before?
Not that I've been able to find.
Does this affect error handling?
No.
Is this about generics?
This is about generic type constraints.
Proposal
The proposal would be to add a new keyword, similar to
comparable
, for iterable types. That is,iterable[T]
would constrain the type to one ofiter.Seq[T]
,map[T]<anything>
, or<-chan T
- that is, anything for whichreflect.ValueOf(y).Seq()
would return a sequence for with the elements can be treated asT
.The go 1.23 iterable support is great! However, the need to use e.g.
maps.Keys
to convert amap[T]any
to aiter.Seq[T]
makes writing certain kinds of common iterator utilities difficult. In many cases, the caller can simply wrap the argument themselves with e.g.slices.Values()
ormaps.Keys()
. However, this adds boilerplate, and is problematic if the iterator in question is nested inside some other type.Take for example the
flatten
(orchain
) adaptor that is common in other languages.In go, you could implement this as several functions, e.g.
However, there's no way to write one implementation that works in all such cases.
Under this proposal,
Flatten
could be implemented asOne could imagine writing a function like
However:
V
to becomparable
in cases where the sequence is not a map.V
in a call.Flatten
, unless its input parameter isiter.Seq[any]
. That means that functions which want to use this converter are also left without type-checking for their input arguments.One important thing to note here about slices:
reflect.Value.Seq()
on a slice will return the indices. This is entirely consistent with the behavior ofrange
, and it would be surprising for it to work otherwise. However, I have a hard time imagining a case where you'd want it to work that way in contexts where you are intending to iterate over some sequence of values where you don't know that the source is a slice. It certainly wouldn't be desirable behavior in the aboveFlatten
example.Language Spec Changes
A new keyword, valid in the scope of a generic type constraint, for specifying that the type in question is iterable.
Informal Change
No response
Is this change backward compatible?
Adding a new keyword is always a potential breaking change. However this keyword would only be valid in the context of a generic type constraint, so it would only affect code which defines a type
iterable
(or whatever we want to name that bikeshed) which is being used as a generic type constraint, which seems unlikely.Orthogonality: How does this change interact or overlap with existing features?
This is essentially fills a gap in the interaction between go1.23 range over functions and the generic type system.
Would this change make Go easier or harder to learn, and why?
Having access to a common library of iterator adaptors like what is found in other languages would help bring new developers up to speed quickly.
On the other hand, the above-mentioned caveats about handling of slice types could be considered a sharp edge.
Cost Description
Mostly implementation complexity. I'm not really sure how one would implement it, since I am not familiar with the implementation details of generics in the compiler. Possibly some "magic" runtime-internal function that works like
IntoIterator
above, injected into every place that a value with the constrained type is used.Changes to Go ToolChain
Just the compiler
Performance Costs
No response
Prototype
No response
The text was updated successfully, but these errors were encountered: