-
Notifications
You must be signed in to change notification settings - Fork 17.5k
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
spec: generics: permit type parameters on aliases #46477
Comments
If this proposal were accepted, would the following code be valid? type A[T any] int
type B[U comparable] = A[U] I.e. would it be possible to define an alias which tightens the constraints of the aliased type? IMO the example in the value domain is more analogous to defining a new named type, which already behaves as expected: type A[T comparable] int
type B[U any] A[U] // ERROR: U does not satisfy comparable Aliasing seems more analogous to function value assignment, for which we don't redeclare parameters: func F(x int) {}
var G = F I think you're right about how this could be implemented, but I wonder if it is conceptually coherent. Specifically, I wonder about whether we should think of the declaration as parameterizing the type, or as defining a parameterized type, and whether it still makes sense to call the example with additional restrictions above an alias. I'll also note that as you point out, our decisions with respect to the go/types API have real consequences for how easy it would be to relax this restriction on aliases in the future, so it is good to talk about this now. Thanks for raising this issue! |
Yes. I would expect that the type checker would see
Note that But this is also why I suggest still allowing |
There is a reason why we didn't do this in the first place.
|
I seem to recall @rogpeppe raising a similar point in various conversations. |
Note that we do allow function value assignment to strengthen (but not weaken) a type via assignability, which IMO is analogous to strengthening type constraints on a type declaration. Consider this program: package main
import "context"
func cancel() {}
type thunk func()
var f = cancel
var g context.CancelFunc = cancel In that program, The declaration |
It looks like it could fall out of the definition but, to be explicit, partial application would also be useful:
|
@griesemer If we proceed with this proposal, I think it could be a nice convenience to keep And yes, the deviating from the norm of requiring instantiation is what threw me off. I had written some code that was working under the assumption that if I only started from non-generic declarations, then I would never see a non-instantiated type. But that doesn't hold for the |
The example from #46477 (comment) made the analogy of function parameters with type parameters (which makes sense). In that analogy, we don't allow changing function parameter types when assigning [example], i.e. we don't support covariant function assignment. |
Sure, but pretty much the entire point of type parameters is to support variance in types. 😉 |
What is the use case for permitting parameters on type aliases? |
Going through my notes I remember now why we didn't go this route in the first place: Note that an alias is just an alternative name for a type, it's not a new type. Introducing a smaller set of type arguments (as suggested above), or providing stronger type constraints seems counter that idea. Such changes arguably define a new type and then one should do that: declare a new defined type, i.e., leave the In summary, I am not convinced anymore that this is such a good idea. We have explored the generics design space for the greater part of two years and the devil really is in the details. At this point we should not introduce new mechanisms until we have collected some concrete experience. I suggest we put this on hold for the time being. |
@griesemer I don't see what the refactoring case is for changing the constraints of a type. As you say, an alias is just an alternative name for a type, but an alternative name with altered constraints is a subtler concept that I struggle to see the use for. I may be missing something. A concrete example of when you'd use this would be useful. |
Under this proposal, you can do more with parameterized type aliases than just change the constraints. E.g., see #46477 (comment) for using type parameters to provide default arguments to other generic types. I called out the constraint change to clarify the semantics, not because I expect that's something people are likely to do in practice. I anticipate analogous to how we added type aliases to facilitate large-scale refactorings while maintaining type identity, we're going to face situations where generic types need to be refactored to add, remove, or change parameters while also maintaining type identity. Having parameterized type aliases would facilitate that. I think if just "declaring a new defined type" was always an adequate solution, we could have skipped adding type aliases too. I think it's fine though if Go 1.18 doesn't have parameterized type aliases. But I at least think we should try to ensure the go/types APIs are forward compatible with adding parameterized type aliases. |
FWIW, I don't follow this argument. We still support variance in types no matter what we decide about this proposal, just like we allow variance in function arguments whether or not we allow covariant assignment of function values. I think we're dipping in and out of the 'meta' realm. The point I was trying to make is that if we're trying to argue by analogy with the value domain, wrapping a function is more like defining a new named type (or perhaps more correctly like struct embedding), and aliasing is more like assignment. Since we don't allow covariant assignment for functions, it's arguably a bit inconsistent to allow covariant assignment for "meta functions" (if that's how we think about generic declarations).
Or use embedding, which might be more analogous to wrapping a function in the value domain.
Independent of whether we relax the restriction on aliases, this proposal indirectly makes the point that it matters whether we think of the "type" as generic or the "declaration" as generic, both in the current APIs and for future extensions of the language. For example, thinking of the type declaration as generic allows relaxing this restriction on aliases. Thinking of the function type as generic allows for generic interface methods and generic function literals. If we put this proposal on hold, we will still need to make API decisions that affect its feasibility. |
[re: value vs type domain analogies] I want to clarify that I made this analogy initially to help explain how I intuit the relationships here. Go's values and types operate sufficiently distinctly and irregularly that I think trying to read too far into the analogy is going to hit rough edges and become more philosophical than actionable. E.g., the value domain has no analog to defined types and type identity, because it's impossible to create a copy of a Go value that's distinguishable from the original. (Emphasis: I'm talking specifically about values here, not variables.) Certainly we should revisit these discussions when it comes time to add dependent types to Go 3 though. :) |
I'm not sure that this is entirely true. What about this, which is currently allowed?
Sometimes defining a new type isn't possible. For example, if a type is specifically mentioned in a type signature, it's not possible to use a new type - you have to use the same type as the original. Also, the fact that all methods are lost when you define a new type is a real problem and embedding doesn't always work either. For non-generic code, it might usually be possible to define a fully-qualified type alias like An example: Say some package defines an
I want to implement a higher level container in terms of
In the above code, whenever I wish to pass around the
Then we can avoid duplicating the type parameters everywhere. Defining a new type wouldn't be great here - you'd either have to explicitly forward all the methods (if you did In short, I'm fairly sure that generic type aliases are going to be a much requested feature when people start using generics in seriousness, and that they're definitely worth considering now even if they're not implemented, so that the type checker isn't implemented in a way that makes it hard to add them later. |
This proposal has been added to the active column of the proposals project |
I think we have to consider what exactly an alias is in Go language:
Since an alias is an identifier, that is, in essence, a name, it does not make sense for such a name to have type parameters. An alias is simply a name for a type, not a type in itself. And names should not be parameterizable. If it is desirable to define a new generic type based on an other generic type, this should be done using different syntax than a type alias. Therefore I, respectfully oppose this proposal. |
We're discussing amending the Go language spec here, so I think referring to the current wording is somewhat begging the question. If we decide to amend the spec to allow type-parameterized aliases, then I think it's within scope to amend those sentences too.
We have existing syntax for type aliases and for type parameters, and conveniently they're compatible. I don't see why we'd want to use a new syntax for type-parameterized aliases. |
Well, the reason I refer to the current spec is because that explains what the current concept of am alias is. If we were to change the spec, the concept of what an alias is will also change quite radically. An alias will not be just a name for a type anymore. And I feel this will make Go quite a bit harder to learn. Furthermore, I would say that the changing concept of an alias as a name to something else is at least not conceptually backwards compatible. With this proposal an alias is not just a name any more, but also a way to define types. Since the concept of this proposal is different, i feel the syntax should be different as well. Maybe just using := in stead of = for example. |
This would not let you define any new types: it would let you give a name to a subfamily of a family of types. |
What is the concrete benefit that this would bring? |
Change https://go.dev/cl/586955 mentions this issue: |
Change https://go.dev/cl/586956 mentions this issue: |
For #46477. Change-Id: Ic917272cb2cd28dcb39f173b3cdcfb72e52eae2d Reviewed-on: https://go-review.googlesource.com/c/go/+/586955 Reviewed-by: Robert Griesemer <gri@google.com> Reviewed-by: Austin Clements <austin@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Robert Griesemer <gri@google.com>
For #46477. Change-Id: Ia3558f9d2bf43fdd9e3618bd9f800d268e13b367 Reviewed-on: https://go-review.googlesource.com/c/go/+/586956 Reviewed-by: Carlos Amedee <carlos@golang.org> Reviewed-by: Robert Findley <rfindley@google.com> Reviewed-by: Robert Griesemer <gri@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Robert Griesemer <gri@google.com>
For #46477. Change-Id: Ifa47d3ff87f67c60fa25654e54194ca8b31ea5a2 Reviewed-on: https://go-review.googlesource.com/c/go/+/567617 Auto-Submit: Robert Griesemer <gri@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Robert Findley <rfindley@google.com> Reviewed-by: Robert Griesemer <gri@google.com>
The following code snippet causes the Go compiler to panic (https://go.dev/play/p/PBvqgMjEs4S?v=gotip)
|
@griesemer Thanks, I wasn't sure if it was more appropriate to write the issue here or to create a new one. |
@gazerro As this is an accepted proposal that's already implemented (as a GOEXPERIMENT), a separate issue makes it easier to track it, as it's a bug that needs to be fixed. But reporting in the first place is great, so thanks. |
Change https://go.dev/cl/593715 mentions this issue: |
Type parameters on aliases are now allowed after #46477 accepted. Updates #46477 Fixes #68054 Change-Id: Ic2e3b6f960a898163f47666e3a6bfe43b8cc22e2 Reviewed-on: https://go-review.googlesource.com/c/go/+/593715 Reviewed-by: Robert Griesemer <gri@google.com> Reviewed-by: Matthew Dempsky <mdempsky@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Robert Griesemer <gri@google.com>
Change https://go.dev/cl/593797 mentions this issue: |
Type parameters on aliases are now allowed after #46477 accepted. Updates #46477 Fixes #68054 Change-Id: Ic2e3b6f960a898163f47666e3a6bfe43b8cc22e2 Reviewed-on: https://go-review.googlesource.com/c/go/+/593715 Reviewed-by: Robert Griesemer <gri@google.com> Reviewed-by: Matthew Dempsky <mdempsky@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Robert Griesemer <gri@google.com> Reviewed-on: https://go-review.googlesource.com/c/go/+/593797 Reviewed-by: Michael Pratt <mpratt@google.com>
Type parameters on aliases are now allowed after golang#46477 accepted. Updates golang#46477 Fixes golang#68054 Change-Id: Ic2e3b6f960a898163f47666e3a6bfe43b8cc22e2 Reviewed-on: https://go-review.googlesource.com/c/go/+/593715 Reviewed-by: Robert Griesemer <gri@google.com> Reviewed-by: Matthew Dempsky <mdempsky@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Robert Griesemer <gri@google.com>
For the record, I think it's a mistake to publish
There's also the issue I commented on here, which applies to |
This issue is currently labeled as early-in-cycle for Go 1.24. |
Change https://go.dev/cl/601115 mentions this issue: |
Change https://go.dev/cl/601235 mentions this issue: |
The typechecker is assuming that alias instances cannot be reached from a named type. However, when type parameters on aliases are permited, it can happen. This CL changes the typechecker to propagate the correct named instance is being expanded. Updates #46477 Fixes #68580 Change-Id: Id0879021f4640c0fefe277701d5096c649413811 Reviewed-on: https://go-review.googlesource.com/c/go/+/601115 Auto-Submit: Robert Griesemer <gri@google.com> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Robert Griesemer <gri@google.com> Auto-Submit: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Change https://go.dev/cl/601116 mentions this issue: |
…type with generic alias The typechecker is assuming that alias instances cannot be reached from a named type. However, when type parameters on aliases are permited, it can happen. This CL changes the typechecker to propagate the correct named instance is being expanded. Updates #46477 Fixes #68580 Change-Id: Id0879021f4640c0fefe277701d5096c649413811 Reviewed-on: https://go-review.googlesource.com/c/go/+/601115 Auto-Submit: Robert Griesemer <gri@google.com> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Robert Griesemer <gri@google.com> Auto-Submit: Cuong Manh Le <cuong.manhle.vn@gmail.com> Reviewed-on: https://go-review.googlesource.com/c/go/+/601116
This change caused objectpath to treat Alias nodes more like Named types: as first-class nodes, with type parameters, and a destructuring operation (Alias.Rhs(), opRhs, encoded 'a') access the RHS type. A number of historical bugs made this trickier than it should have been: - go1.22 prints Alias wrongly, requiring a workaround in the test. - aliases.Enabled is too expensive to call in the decoder, so we must trust that when we see an opRhs and we don't have an alias, it's because !Enabled(), not a bug. - legacy aliases still need to be handled, and order matters. - the test of parameterized aliases can't be added until the GOEXPERIMENT has gone away (soon). Updates golang/go#46477 Change-Id: Ia903f81e29fb7dbb6e17d1e6a962fad73b3e1f7b Reviewed-on: https://go-review.googlesource.com/c/tools/+/601235 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Alan Donovan <adonovan@google.com> Reviewed-by: Tim King <taking@google.com> Commit-Queue: Alan Donovan <adonovan@google.com>
Change https://go.dev/cl/603935 mentions this issue: |
Change https://go.dev/cl/604615 mentions this issue: |
The generics proposal says "A type alias may refer to a generic type, but the type alias may not have its own parameters. This restriction exists because it is unclear how to handle a type alias with type parameters that have constraints."
I propose this should be relaxed and type aliases allowed to have their own type parameters. I think there's a clear way to handle type aliases with constrained type parameters: uses of the type alias need to satisfy the constraints, and within the underlying type expression those parameters can be used to instantiate other generic types that they satisfy.
I think it's fine to continue allowing
type VectorAlias = Vector
as in the proposal, but this should be considered short-hand fortype VectorAlias[T any] = Vector[T]
. More generally, for generic typeB
with type parameters[T1 C1, T2 C2, ..., Tn Cn]
, thentype A = B
would be the same astype A[T1 C1, T2 C2, ..., Tn Cn] = B[T1, T2, ..., Tn]
.In particular, something like this would be an error:
As justification for this, analogous code in the value domain would give an error:
I suspect if
TParams
is moved fromNamed
toTypeName
and type instantiation is similarly changed to start from theTypeName
instead of theType
, then this should work okay./cc @griesemer @ianlancetaylor @findleyr @bcmills
The text was updated successfully, but these errors were encountered: