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? |
Unfortunatly we need to wait to have this functionality golang/go#46477 Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
Unfortunatly we need to wait to have this functionality golang/go#46477 Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
This could be extremely useful for signatures like |
What are the chances this will make it into Go1.21? |
@stephenafamo We have been concentrating on improved type inference this dev. cycle. At the moment improved aliases seem somewhat unlikely to happen for 1.21. |
One case to consider: would this be legal or not?
The spec says "the method receivers must declare the same number of type parameters as present in the generic type definition". |
@alandonovan I think you mean I'd lean towards not allowing that. I argued for allowing type aliases in receiver parameters originally, but now I mostly regret that from an implementation complexity point of view |
@adonovan I've been thinking about that, too. I'm not sure yet what the right approach is. Consider situations where the alias type has fewer type parameters then the aliased type. And what it we allow more type parameters in the alias than in the aliased type. There are lots of open questions here. |
Change https://go.dev/cl/521956 mentions this issue: |
We've been talking about this on the tools team, and think it would be good to land the support for |
Any chance of it going into 1.22 behind a |
This will be part of 1.23. We won't get this done for 1.22. |
This change introduces a new (unexported for now) _Alias type node which serves as an explicit representation for alias types in type alias declarations: type A = T The _Alias node stands for the type A in this declaration, with the _Alias' actual type pointing to (the type node for) T. Without the _Alias node, (the object for) A points directly to T. Explicit _Alias nodes permit better error messages (they mention A instead of T if the type in the source was named A) and can help with certain type cycle problems. They are also needed to hold type parameters for alias types, eventually. Because an _Alias node is simply an alternative representation for an aliased type, code that makes type-specific choices must always look at the actual (unaliased) type denoted by a type alias. The new function func _Unalias(t Type) Type performs the necessary indirection. Type switches that consider type nodes, must either switch on _Unalias(typ) or handle the _Alias case. To avoid breaking clients, _Alias nodes must be enabled explicitly, through the new Config flag _EnableAlias. To run tests with the _EnableAlias set, use the new -alias flag as in "go test -run short -alias". The testing harness understands the flag as well and it may be used to enable/disable _Alias nodes on a per-file basis, with a comment ("// -alias" or // -alias=false) on the first line in those files. The file-based flag overrides the command-line flag. The use of _Alias nodes is disabled by default and must be enabled by setting _EnableAlias. Passes type checker tests with and without -alias flag set. For #25838. For #44410. For #46477. Change-Id: I78e178a1aef4d7f325088c0c6cbae4cfb1e5fb5c Reviewed-on: https://go-review.googlesource.com/c/go/+/521956 Reviewed-by: Matthew Dempsky <mdempsky@google.com> Reviewed-by: Robert Findley <rfindley@google.com> Auto-Submit: Robert Griesemer <gri@google.com> Reviewed-by: Robert Griesemer <gri@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Robert Griesemer <gri@google.com>
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: