On Fri, 21 Aug 2020 at 01:28, Ian Lance Taylor <i...@golang.org> wrote:
> 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. Here's one interesting implication of this: it allows us to do type conversions that were not previously possible. For example, if we have "type I int", we can use a type switch to convert some type []I to type []int: https://go2goplay.golang.org/p/-860Zlz7-cn func F[type T intlike](ts []T) []int { switch T { case int: return ts } return nil } It seems to me that this kind of thing will allow us to perform a similar conversion (convert some part of the type to its underlying type) on any type. In the early days of Go, the spec allowed this kind of conversion <https://github.com/golang/go/issues/809> as a normal type conversion. I wonder if it might be reasonable to revert to those more relaxed semantics. I think they're potentially useful, for example, when dealing with named types obtained from modules with two different major versions without incurring copies. Although in the above-linked issue Robert talks about runtime costs such as "possibly re-mapping method tables <https://github.com/golang/go/issues/809#issuecomment-66051363>", I don't see that this would necessarily be the case. Thoughts? -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CAJhgacjL7p7qck%3DSO0Nz9f%2BKZw6MNcgkD5REXwSNK7_fCTXYQg%40mail.gmail.com.