You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
spec: generics: permit type parameters on aliases #46477
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 for type VectorAlias[T any] = Vector[T]. More generally, for generic type B with type parameters [T1 C1, T2 C2, ..., Tn Cn], then type A = B would be the same as type A[T1 C1, T2 C2, ..., Tn Cn] = B[T1, T2, ..., Tn].
In particular, something like this would be an error:
type A[T comparable] int
type B[U any] = A[U] // ERROR: U does not satisfy comparable
type C B[int]
As justification for this, analogous code in the value domain would give an error:
func F(x int) {}
func G(y interface{}) { F(y) } // ERROR: cannot use y (type interface{}) as int
func H() { G(42) }
I suspect if TParams is moved from Named to TypeName and type instantiation is similarly changed to start from the TypeName instead of the Type, then this should work okay.
If this proposal were accepted, would the following code be valid?
typeA[Tany] inttypeB[Ucomparable] =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:
typeA[Tcomparable] inttypeB[Uany] A[U] // ERROR: U does not satisfy comparable
Aliasing seems more analogous to function value assignment, for which we don't redeclare parameters:
funcF(xint) {}
varG=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!
Reacted by Bryan C. Mills, DeedleFake, Beoran, syumai, Simon Law, G-M-twostay and Kent Gruber
I.e. would it be possible to define an alias which tightens the constraints of the aliased type?
Yes. U (type parameter with bound comparable) satisfies the constraint any, so that's a valid type declaration in my mind. But similarly, trying to instantiate B[[]int] would be invalid, because []int does not satisfy comparable, even though it satisfies the underlying any.
I would expect that the type checker would see B[[]int], resolve B to the TypeName and check it against the type parameters, and then reject it as invalid, before proceeding to instantiating/substituting its Type with the type argument []int.
Aliasing seems more analogous to function value assignment, for which we don't redeclare parameters:
Note that var G = F is really shorthand there for var G func(int) = F. You're not allowed to write var G func(interface{}) = F, for example, even if you only ever call G with int arguments.
But this is also why I suggest still allowing type A = B as shorthand for explicitly writing out type parameters for the alias declaration.
Reacted by Bryan C. Mills, AndrewHarrisSPU and Guillaume Raffin
There is a reason why we didn't do this in the first place.
I don't have any principal objections to this proposal. If we accept this, I wonder whether we should still permit the type A = B form as it does deviate from the current design which requires that every use of a generic type requires an instantiation.
I'm inclined to proceed in one of two ways: 1) Disallow (not implement) the form type A = B for Go1.17. It's not crucial and we can always add it later. 2) Implement this proposal instead of permitting type A = B.
Reacted by Bryan C. Mills, Matthew Dempsky, Robert Findley, Beoran, yinchangyu and ziposcar
Aliasing seems more analogous to function value assignment, for which we don't redeclare parameters:
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.
package main
import"context"funccancel() {}
typethunkfunc()
varf=cancelvarg context.CancelFunc=cancel
In that program, var f = cancel is shorthand for var f func() = cancel.
The declaration var g context.CancelFunc = cancel refers to the exact same function value, but with a stronger type (one that is not assignable to thunk).
It looks like it could fall out of the definition but, to be explicit, partial application would also be useful:
type Named[T any] = Map[string, T]
Reacted by Matthew Dempsky, Robert Findley, Bryan C. Mills, Tom Thorogood, محمّد, Eldan, AndrewHarrisSPU, Joe Kralicky, Tempura, Sean Strickland and 20 more
@griesemer If we proceed with this proposal, I think it could be a nice convenience to keep type A = B as short-hand. But as it's not essential, I'd similarly be fine with just removing it altogether. We can always re-add it in the future if appropriate.
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 type A = B form. (Fortunately though, it's not hard to special case this one instance either.)
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.
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.
@neild The same reason for which type aliases were introduced in the first place, which is to make refactoring across package boundaries easier (or possible, depending on use case).
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 = away. I note that @findleyrpointed out just that in the 2nd comment on this proposal.
This would mean that the respective methods also have to be redefined (likely as forwarders) but that seems sensible if the type constraints are narrowed or partially instantiated.
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.
Reacted by DeedleFake, Damien Neil, Robert Findley, Komu Wairagu, Alexey Palazhchenko, Matt Mc, Beoran, Kyle Lemons, U Cirello, Simon Law and 8 more
@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.
@DmitriyMV I think that is clearly a bug. The code is passing an explicit type argument, so there's no way the compiler should say that it can't infer the type argument. Please open a new issue for that. Thanks.
Activity
findleyr commentedon Jun 1, 2021
If this proposal were accepted, would the following code be valid?
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:
Aliasing seems more analogous to function value assignment, for which we don't redeclare parameters:
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!
mdempsky commentedon Jun 1, 2021
Yes.
U(type parameter with boundcomparable) satisfies the constraintany, so that's a valid type declaration in my mind. But similarly, trying to instantiateB[[]int]would be invalid, because[]intdoes not satisfycomparable, even though it satisfies the underlyingany.I would expect that the type checker would see
B[[]int], resolveBto theTypeNameand check it against the type parameters, and then reject it as invalid, before proceeding to instantiating/substituting itsTypewith the type argument[]int.Note that
var G = Fis really shorthand there forvar G func(int) = F. You're not allowed to writevar G func(interface{}) = F, for example, even if you only ever callGwithintarguments.But this is also why I suggest still allowing
type A = Bas shorthand for explicitly writing out type parameters for the alias declaration.griesemer commentedon Jun 1, 2021
There is a reason why we didn't do this in the first place.
I don't have any principal objections to this proposal. If we accept this, I wonder whether we should still permit thetype A = Bform as it does deviate from the current design which requires that every use of a generic type requires an instantiation.I'm inclined to proceed in one of two ways:1) Disallow (not implement) the formtype A = Bfor Go1.17. It's not crucial and we can always add it later.2) Implement this proposal instead of permittingtype A = B.bcmills commentedon Jun 1, 2021
I seem to recall @rogpeppe raising a similar point in various conversations.
bcmills commentedon Jun 1, 2021
@findleyr
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:
In that program,
var f = cancelis shorthand forvar f func() = cancel.The declaration
var g context.CancelFunc = cancelrefers to the exact same function value, but with a stronger type (one that is not assignable tothunk).jimmyfrasche commentedon Jun 1, 2021
It looks like it could fall out of the definition but, to be explicit, partial application would also be useful:
mdempsky commentedon Jun 1, 2021
@griesemer If we proceed with this proposal, I think it could be a nice convenience to keep
type A = Bas short-hand. But as it's not essential, I'd similarly be fine with just removing it altogether. We can always re-add it in the future if appropriate.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
type A = Bform. (Fortunately though, it's not hard to special case this one instance either.)findleyr commentedon Jun 1, 2021
@bcmills
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.
bcmills commentedon Jun 1, 2021
Sure, but pretty much the entire point of type parameters is to support variance in types. 😉
neild commentedon Jun 2, 2021
What is the use case for permitting parameters on type aliases?
griesemer commentedon Jun 2, 2021
@neild The same reason for which type aliases were introduced in the first place, which is to make refactoring across package boundaries easier (or possible, depending on use case).I misread this comment. See below.
griesemer commentedon Jun 2, 2021
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
=away. I note that @findleyr pointed out just that in the 2nd comment on this proposal.This would mean that the respective methods also have to be redefined (likely as forwarders) but that seems sensible if the type constraints are narrowed or partially instantiated.
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.
neild commentedon Jun 2, 2021
@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.
125 remaining items
griesemer commentedon Nov 26, 2024
@dmitshur I'll take care of it. Thanks.
gopherbot commentedon Nov 27, 2024
Change https://go.dev/cl/632055 mentions this issue:
doc/next: document new language feature (alias type parameters)doc/next: document new language feature (alias type parameters)
GoVeronicaGo commentedon Nov 27, 2024
The issue seems to be completed. Please close if it is done griesemer
DmitriyMV commentedon Dec 20, 2024
Following this thread on reddit: is this expected? I know that
A[int]{}compiles down to anstruct{}{}but this happen before or after type inference?ianlancetaylor commentedon Dec 21, 2024
@DmitriyMV I think that is clearly a bug. The code is passing an explicit type argument, so there's no way the compiler should say that it can't infer the type argument. Please open a new issue for that. Thanks.
DmitriyMV commentedon Dec 21, 2024
@ianlancetaylor done: #70948
griesemer commentedon Jan 6, 2025
Note that this is indeed expected. See closing comment in #70948.
meftunca commentedon Jan 14, 2025
findleyr commentedon Jan 14, 2025