Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.Sign up
proposal: go 2: (Lightweight) generics in Go using type lists #33604
This proposal contains an idea to define generics in Go (2) using (named) type lists. A possible way to define a type list in Go is sketched below, after which the use of the type list to write generic code is discussed. The second half contains a couple of thoughts on the details of type lists / generics that are less essential to the 'main' idea.
(Lightweight) generics in Go using type lists
A type list is a comma separated list of types enclosed in parenthesis. A type list can be named like normal Go types:
Besides explicit type lists, Go provides two implicit type list:
A type list is used to specify type parameters for generic functions, structs and interfaces:
Note that each named type list accounts for a single type parameter. This is the central theme in this proposal. In particular, it was chosen to lift type parameter definitions out of generic function signature and generic struct/interface definitions to increase their readability while also making them reusable.
Unnamed type lists are allowed as well but will always get their own type parameter:
To use a generic function, struct or interface, the type parameters must be bound to specific types by providing a list of type arguments:
Note that the type arguments are specified in the order of appearance of the type parameters.
A type argument satisfies a type parameter's type list if the type is directly in the list, or if the underlying type is in the list:
As can be expected, type arguments can be omitted whenever they are clear from context:
When "compiling" a generic function or type definition, the compiler checks that the operations used are supported by all types in the type lists specified for the type parameters:
When a generic function or type is instantiated in a program, the compiler checks that the type arguments satisfy the type parameters:
Because the compiler only needs to check whether a type is an explicit list, or in a limited set of built-in implicit type lists, type argument validation should be quick and the error messages clear to the programmer that writes the code that uses the generics.
Using the compiler provided implicit type lists and package author provide explicit type lists, many useful gerenic functions and types can be written. The idea of contract/concepts as found in other languages are explicitly avoided: generic code must either be constrainted to the Golang type 'contracts' (), (==) (and possibly (struct)), or just provide an explicit list.
To get to a specification many details need to be worked out, and I've probably overseen many and it might turned out the whole idea doesn't work. That being said, below is a list of additional thoughts.
Unexported type parameters
It is well possible that the type parameter for an exported type is used only in unexported fields:
A solution for this problem could be to allow 'ghost fields' that are always exported by the compiler. A ghost field does not consume any bits and cannot be addressed, but is used to express existence of unexported type parameters:
Using ghost fields, the type parameters order can also be kept the same if even the field order changes.
Type parameter type assertion
When writing generic code at some point it will because necessary to branch code depending on specific type arguments. The normal Go type assertion could possibly be reused for this, which has slightly different semantics in the generic code:
Generic type methods
A named type list used as type parameter in a generic method body are bound the same type if the type list is used in the type definition:
Because instantition of generic types are different types, method specialisation for subsets of the parameter type list could be allowed:
Whether is useful is to be seen. It could always scraped as part of a first version. An alternative way could be so allow some kind of static_if that can be used for conditional compilation. However, both ideas are very advanced, and the impliciation are hard to oversee at this point.
Implicit type list (struct) and others
For marshalling and orm code it would be beneficial to express that a type parameter must be of kind struct:
Implicit type lists for other innate Go types are not as necessary, as most can expressed directly:
In type declarations, the right side is always a valid type.
Not to mention that now generic functions do not look like generic functions anymore. When I see
This was something that the original generics draft was explicitly trying to avoid, to quote the original draft:
This however doesn't mean that it's a bad idea, but it may reflect that it isn't a problem that Go wants to solve.
I do think that this brings some interesting ideas though. Having things like
Thanks for taking the time to read this.
I am not aware that
Well you have to lookup non-generic type definitions too when use a function. So going to the definition of T you'd see it's a typelist and you'd immediately see the types that are allowed. The fact that that makes
Thanks for mentioning this, I wasn't aware of this. I wholeheartedly agree with this; I also rather keep generics as 'lightweight' as possible.
What I was saying is that in
This is actually a fair point
While it's a simpler syntax, it's not as clear. For instance:
The syntax doesn't make it clear the difference between "t1, t2, t3, and the return value are all (int, string) and are the same type" and "t1, t2, t3 and the return value are all (int, string) but are all different types". In the syntax shown by the draft and the blog post, it is very clear:
This syntax very clearly shows that all 4 must be the same type, since it illustrates that T is a parameter to the function definition.
While I agree that the explicit type parameter in you example makes it somewhat clearer that all T must be of the same type, I think in practise it is really of not such a big deal. For example, take a look at the documentation of the builtin functions (https://golang.org/pkg/builtin)[https://golang.org/pkg/builtin]: the documentation only expresses that Type must be of the same type in the type definition but never mentions is any further with function definition. Have you ever looked at any of those functions and got confused whether 'Type' got be different types? The thing with generics is that when you use it you already have an idea of the types you want to use, so for
I don't think there is a risk of confusion about if T may be
I think that what I should have originally gone for was not the clarity of function definitions being parametric, but the idea of
It's already partially confusing because
Granted I'm just arguing about syntax at this point, the problems you are solving and the way you solve them are really what matter, which is something I've addressed (and you've responded to) already. So I'm not sure if I have any other feedback
If I understand you correctly, you mean
right? This is indeed a drawback, as are for example type parameters that are used only in non-exported fields. For structs, please see the section 'Unexported type parameters' in my proposal for an idea that might alleviate these issues for structs. For functions this problem is probably less of an issue because changing the order of arguments affects calling code directly anyway (in contrast to changing struct members). That being said, it is a sort of a weakness in the proposal that comes with not explicitly stating the type parameters with every declaration.
I'd like to take the opportunity to explain that an inspiration for this proposal is how the builtin in functions are documented in go. I though a bit about how I would like to see generics used and I imagined packages likes 'stats or 'linalg', that provide functions on T where T is float32,float64 and possible the complex types. Now if the function definitions in those package where like:
The type paremeter signature
As another example, for a package 'channels' that would provide channel algorithms, T is just any type:
And in package 'containers' we could have two type parameters:
The pattern here is that any package with generic functions/types will (or maybe should) only use a couple of type parameters kinds (contracts) anyway to keep things simple and in the spirit and practicality of Go. When using the package (ie not being the author), you should be able to quickly learn and easily lookup the one or two type parameter kinds that are used in the package.