Skip to content

Instantly share code, notes, and snippets.

@dgryski
Created August 21, 2020 13:50
Show Gist options
  • Save dgryski/4aca5a5a661bd8597647177d5f800c39 to your computer and use it in GitHub Desktop.
Save dgryski/4aca5a5a661bd8597647177d5f800c39 to your computer and use it in GitHub Desktop.
From: Ian Lance Taylor
After many discussions and reading many comments, we plan to move
forward with some changes and clarifications to the generics design
draft.
1.
We’re going to settle on square brackets for the generics syntax.
We’re going to drop the “type” keyword before type parameters, as
using square brackets is sufficient to distinguish the type parameter
list from the ordinary parameter list. To avoid the ambiguity with
array declarations, we will require that all type parameters provide a
constraint. This has the advantage of giving type parameter lists the
exact same syntax as ordinary parameter lists (other than using square
brackets). To simplify the common case of a type parameter that has
no constraints, we will introduce a new predeclared identifier “any”
as an alias for “interface{}”.
The result is declarations that look like this:
type Vector[T any] []T
func Print[T any](s []T) { … }
func Index[T comparable](s []T, e T) { … }
We feel that the cost of the new predeclared identifier “any” is
outweighed by the simplification achieved by making all parameter
lists syntactically the same: as each regular parameter always has a
type, each type parameter always has a constraint (its meta-type).
Changing “[type T]” to “[T any]” seems about equally readable and
saves one character. We’ll be able to streamline a lot of existing
code in the standard library and elsewhere by replacing “interface{}”
with “any”.
2.
We’re going to simplify the rule for type list satisfaction. The type
argument will satisfy the constraint if the type argument is identical
to any type in the type list, or if the underlying type of the type
argument is identical to any type in the type list. What we are
removing here is any use of the underlying types of the types in the
type list. This tweaked rule means that the type list can decide
whether to accept an exact defined type, other than a predeclared
type, or whether to accept any type with a matching underlying type.
This is a subtle change that we don’t expect to affect any existing
experimental code.
We think that this definition might work if we permit interface types
with type lists to be used outside of type constraints. Such
interfaces would effectively act like sum types. That is not part of
this design draft, but it’s an obvious thing to consider for the
future.
Note that a type list can mention type parameters (that is, other type
parameters in the same type parameter list). These will be checked by
first replacing the type parameter(s) with the corresponding type
argument(s), and then using the rule described above.
3.
We’re going to clarify that when considering the operations permitted
for a value whose type is a type parameter, we will ignore the methods
of any types in the type list. The general rule is that the generic
function can use any operation permitted by every type in the type
list. However, this will only apply to operators and predeclared
functions (such as "len" and "cap"). It won’t apply to methods, for
the case where the type list includes a list of types that all define
some method. Any methods must be listed separately in the interface
type, not inherited from the type list.
This rule seems generally clear, and avoids some complex reasoning
involving type lists that include structs with embedded type
parameters.
4.
We’re going to permit type switches on type parameters that have type
lists, without the “.(type)” syntax. The “(.type)” syntax exists to
clarify code like “switch v := x.(type)”. A type switch on a type
parameter won’t be able to use the “:=” syntax anyhow, so there is no
reason to require “.(type)”. In a type switch on a type parameter
with a type list, every case listed must be a type that appears in the
type list (“default” is also permitted, of course). A case will be
chosen if it is the type matched by the type argument, although as
discussed above it may not be the exact type argument: it may be the
underlying type of the type argument. To make that rule very clear,
type switches will not be permitted for type parameters that do not
have type lists. It is already possible to switch on a value “x”
whose type is a type parameter without a type list by writing code
like “switch (interface{})(x).(type)” (which may now be written as
“switch any(x).(type)”). That construct is not the simplest, but it
uses only features already present in the language, and we don’t
expect it to be widely needed.
These changes will soon be implemented in the experimental design on
the dev.generics branch, and in the go2go playground. Some of them
already work. We will update the design draft accordingly.
We welcome any comments. Thanks for all the help that so many people
have provided so far.
Ian & Robert
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment