how to update APIs for generics #48287
Replies: 45 comments 212 replies
-
Would definitely be confusing. Especially if you converted from the former to the latter while debugging something. |
Beta Was this translation helpful? Give feedback.
-
Personally, I don't like the asymmetry this would create between type-parameters and regular parameters. Default values for arguments is something that has often been asked about, but we never did it. To me, the case for default type arguments isn't really stronger than for default regular arguments though. I also don't like introducing a new language feature just for a one-time migration. If the motivator for this feature is just the migration of pre-generics APIs to post-generics APIs, it will become de-facto obsolete in a year or so. I don't like that idea. |
Beta Was this translation helpful? Give feedback.
-
One more data-point: This strategy wouldn't help with |
Beta Was this translation helpful? Give feedback.
-
Using new major versions for stdlib packages, e.g. |
Beta Was this translation helpful? Give feedback.
-
Another approach is to observe that the language supports type aliases to support transitions. So let's use type aliases. type Pool[T any] ...
type Pool = Pool[interface{}] The rules here would be:
This alias rule does not work for functions. We can either say there is no transition for functions, or we can introduce another rule. A function may be defined both with and without type parameters. A reference to the function (not a call) with no type arguments is permitted, and gets the version without type parameters. A call of the function with no type arguments gets the version with type parameters and does type inference as usual. If type inference fails, the call is instead made to the version without type parameters (and may fail if the arguments type are not assignable). That gives us func Min[T constraints.Ordered](a, b T) T { ... }
func Min(a, b float64) float64 { return Min[float64](a, b) } |
Beta Was this translation helpful? Give feedback.
-
It's clearly essential to always have a crazy idea that everybody can reject, so here is one. default Pool Pool[interface{}]
default Min Min[float64] |
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
I'm going to go out on a limb and suggest that we use the Constructor functions for generic containers get the
I think If we do the above, what are the remaining problematic names? |
Beta Was this translation helpful? Give feedback.
-
The idea clearly exists already, as it was mentioned in this thread but I think it's worth its own suggestion as I think it aligns pretty nicely with what we're asking package maintainers to do in general: Don't introduce generics in most of these libraries. /v2 them:
The normal packages should be implementable in terms of the generic version, so if it doesn't compromise performance we could do the |
Beta Was this translation helpful? Give feedback.
-
I support this suggestion, particularly as I see it as having the potential to be generally useful into the future given a minor tweak to the rules. I'd question the need for the parentheses. I understand that they're there to suggest the "maybe" aspect of the default but I don't think they pull their weight, and there's considerable precedent from other languages (e.g. Rust's default type parameters, Python's default function parameters) that they could be omitted without much confusion. That is, I think it would be fine if the examples were written thus:
|
Beta Was this translation helpful? Give feedback.
-
Is this something that could be handled by the It seems like overkill for something like this, and if a clean alternative can be found than that's probably better, but I hadn't seen it discussed anywhere and introducing incompatibilities was one of the primary motivations for adding the directive, if I remember correctly. |
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
A proposal/idea related to various more or less vague "go fix" approaches already mentioned around here and there, but specific enough and subtly different enough to warrant separate mention, given this is clearly a brainstorming discussion:
(obviously, the "go2/" and "go1compat/" prefixes being subject to bikeshedding, and go1.18 subject to change to newer) edit: Notably, AFAIU this is also basically how Rust editions are working, so Rust community leaders could be consulted about any nonobvious pros or cons of this approach. FWIW, from a distance it seems to be working for them well enough that they repeated this "phase transition" a few times already. Also, if adopted, this could possibly also help with other bigger "Go2" changes if needed at some point. edit 2: This also seems to me fairly similar to the |
Beta Was this translation helpful? Give feedback.
-
We could teach As an example:
Benefits:
|
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
Is the plan to keep the generics behaving the same as the original? As implemented as a generic
|
Beta Was this translation helpful? Give feedback.
-
A possible awful idea for rejection: implementation-specific suffixes akin to This builds on the observation:
|
Beta Was this translation helpful? Give feedback.
-
I wonder how much compilation time it would take if the function signatures would also be distinguished by their parameter lists. Preferring a generic implementation over the interface one. |
Beta Was this translation helpful? Give feedback.
{{title}}
{{editor}}'s edit
{{editor}}'s edit
-
Edit: As long as you can add parameters in an alias to a different name ( For example the mathgl packages include 3D math types used by OpenGL bindings / games etc. There are 32 and 64 bit versions of each type, If Go ever permits array size type parameters (#44253), the type alias mechanism could also be used to aid migration in the same way (Vector3 could become an alias for Vector[3]). Another example of math code (mathf.go) in the wild uses the f suffix (sinf, cosf) for 32 bit versions of math functions - type aliases would work there while default parameters wouldn't. For those suggesting library authors break API compatibility, then all packages using those libraries re-writing to use the new API - I think we need to be realistic that this will not rapidly or completely happen. Many tasks currently work fine without generics (for example, 64-bit math code), so many applications and libraries would need to change a large number of files for zero or marginal benefits (changes that may include forking dependencies that are no longer actively maintained). Even new code might continue to use "math" instead of "math/v2", if you are using 64-bit math, the former is shorter, and what's the difference? I think the likely outcome is that users would need to be familiar with multiple ways to do the same thing (across many libraries) for the foreseeable future. Creating such a situation in the name of simplicity seems like a false economy. |
Beta Was this translation helpful? Give feedback.
{{title}}
-
@ianlancetaylor, @griesemer, and I are wondering about different possible plans for updating APIs that would have used generics but currently use interface{}, and we wanted to cast a wider net for ideas.
There exist types and functions that clearly would use generics if we wrote them today. For example:
There are certainly more of these, both in Go repos and everyone else's code.
The question is what should be the established convention for updating them.
One suggestion is to adopt an "Of" suffix, as in PoolOf[T], MinOf[T], and so on.
For types, which require the parameter list, that kind of works.
For functions, which will often infer it, the result is strange:
there is no obvious difference between math.MinOf(1, 2) and math.Min(1, 2).
Any new, from-scratch, post-generics API would presumably not include the Of - Tree[K,V], not TreeOf[K,V] -
so the "Of" in these names would be a persistent awkward reminder of our pre-generics past.
Another possibility is to adopt some kind of notation for default type parameters.
For example, suppose you could write
The (= ...) sets the default for a type parameter.
The rule for types could be that the bracketed type parameter list may be omitted when all parameters have defaults,
so saying List would be equivalent to List[interface{}], which, if we are careful, would be identical to the current code,
making the introduction of a generic List as list.List not a backwards-incompatible change.
The rule for functions could be that when parameters have defaults,
those defaults are applied just before the application of default constant types in the type inference algorithm.
That way, Min(1, 2) stays a float64, while Min(i, j) for i, j of type int, infers int instead.
The downside of this is a little bit more complexity in the language spec,
while the upside is potentially smoother migration for users.
Like the "Of" suffix, an API with type defaults would be a persistent awkward reminder of our pre-generics past,
but it would remind mainly the author of the code rather than all the users.
And users would not need to remember which APIs need an "Of" suffix added.
Are there other options we haven't considered?
Are there arguments for or against these options that haven't been mentioned?
Thanks very much.
Beta Was this translation helpful? Give feedback.
All reactions