[ generics] Moving forward with the generics design draft

14,177 views
Skip to first unread message

Ian Lance Taylor

unread,
Aug 20, 2020, 8:28:23 PM8/20/20
to golang-nuts, Robert Griesemer
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

David Riley

unread,
Aug 20, 2020, 9:51:41 PM8/20/20
to Ian Lance Taylor, golang-nuts, Robert Griesemer
On Aug 20, 2020, at 20:27, Ian Lance Taylor <ia...@golang.org> wrote:
>
> 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”.

Resounding “yes” from me. I like this a lot. Good choice.

The rest also sounds good (I think; I may be misunderstanding some of the bits about type switches but I think it sounds good), but #1 in particular should hopefully end the bikeshedding because it seems a good all-around compromise to me.


- DavE

Jan Mercl

unread,
Aug 20, 2020, 10:22:42 PM8/20/20
to Ian Lance Taylor, golang-nuts, Robert Griesemer
On Fri, Aug 21, 2020 at 2:28 AM Ian Lance Taylor <ia...@golang.org> wrote:

> 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{}”.

Anyone can write the declaration

type any = interface{}

today and possibly some people already do that. But in my opinion that
would, so far, not pass a code review within the Go project per se.
(It would definitely not pass my code review.)

I don't like it and It makes me sad it is being proposed to become
officially blessed.

Robert Engels

unread,
Aug 20, 2020, 11:20:25 PM8/20/20
to Jan Mercl, Ian Lance Taylor, golang-nuts, Robert Griesemer
I like it. Well done.

> On Aug 20, 2020, at 9:22 PM, Jan Mercl <0xj...@gmail.com> wrote:
> --
> 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...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CAA40n-XDYjhoMXt%3DEP82EPSrrbmM2D4OsgtEwdx38h7HU5DwWw%40mail.gmail.com.

jimmy frasche

unread,
Aug 20, 2020, 11:28:57 PM8/20/20
to Robert Engels, Jan Mercl, Ian Lance Taylor, golang-nuts, Robert Griesemer
To clarify on the type switches, would it have to be used like this:

type C interface {
type X, Y
}

func f(x X) X {
return x
}

func g[T C](v T) T {
switch v {
case X:
// to use v as an X
// we must convert
x0 := X(v)
x1 := f(x0)
// to use x1 as a T
// we must convert back
t := T(x1)
return t
case Y:
return v
}
}

And that the lack of a dedicated syntax to distinguish the case like
.[type] also means that you could no longer write

func h[T comparable](x, a, b T) {
switch x {
case a:
case b:
}
}

Regardless, all of these changes are fantastic!
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/34314A4C-79B4-4913-9404-26EF1267115F%40ix.netcom.com.

burak serdar

unread,
Aug 20, 2020, 11:53:40 PM8/20/20
to Jan Mercl, Ian Lance Taylor, golang-nuts, Robert Griesemer
Is "any" truly an alias for interface{}, or is it only a constraint? I
agree with Jan's point that replacing interface{} with "any" might
unintentionally encourage its use.

Ian Lance Taylor

unread,
Aug 21, 2020, 12:54:47 AM8/21/20
to burak serdar, Jan Mercl, golang-nuts, Robert Griesemer
On Thu, Aug 20, 2020 at 8:52 PM burak serdar <bse...@computer.org> wrote:
>
> On Thu, Aug 20, 2020 at 8:22 PM Jan Mercl <0xj...@gmail.com> wrote:
> >
> > On Fri, Aug 21, 2020 at 2:28 AM Ian Lance Taylor <ia...@golang.org> wrote:
> >
> > > 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{}”.
> >
> > Anyone can write the declaration
> >
> > type any = interface{}
> >
> > today and possibly some people already do that. But in my opinion that
> > would, so far, not pass a code review within the Go project per se.
> > (It would definitely not pass my code review.)
> >
> > I don't like it and It makes me sad it is being proposed to become
> > officially blessed.
>
> Is "any" truly an alias for interface{}, or is it only a constraint? I
> agree with Jan's point that replacing interface{} with "any" might
> unintentionally encourage its use.

Our intent here is that "any" will be available for all code. Yes, we
wouldn't do it if it weren't for its use as a type constraint. But if
we are going to do it for type constraints, there seems to be little
benefit to restricting it to only work as a type constraint.

This is not, of course, a new idea, even in the absence of generics.
For example, https://golang.org/issue/33232. (My comment there was
that we would use interface{} less if we had generics, but of course
when we require type constraints then we actually wind up using it
somewhat more.)

Ian

Ian Lance Taylor

unread,
Aug 21, 2020, 12:56:57 AM8/21/20
to jimmy frasche, Robert Engels, Jan Mercl, golang-nuts, Robert Griesemer
No, the intent is that you would switch on the type parameter itself,
not a value.

func g[T C](v T) T {
switch T {
// the rest is the same
}
}

Thanks for asking.

Ian

burak serdar

unread,
Aug 21, 2020, 1:40:21 AM8/21/20
to Ian Lance Taylor, Jan Mercl, golang-nuts, Robert Griesemer
On Thu, Aug 20, 2020 at 10:54 PM Ian Lance Taylor <ia...@golang.org> wrote:
>
> On Thu, Aug 20, 2020 at 8:52 PM burak serdar <bse...@computer.org> wrote:
> >
> > On Thu, Aug 20, 2020 at 8:22 PM Jan Mercl <0xj...@gmail.com> wrote:
> > >
> > > On Fri, Aug 21, 2020 at 2:28 AM Ian Lance Taylor <ia...@golang.org> wrote:
> > >
> > > > 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{}”.
> > >
> > > Anyone can write the declaration
> > >
> > > type any = interface{}
> > >
> > > today and possibly some people already do that. But in my opinion that
> > > would, so far, not pass a code review within the Go project per se.
> > > (It would definitely not pass my code review.)
> > >
> > > I don't like it and It makes me sad it is being proposed to become
> > > officially blessed.
> >
> > Is "any" truly an alias for interface{}, or is it only a constraint? I
> > agree with Jan's point that replacing interface{} with "any" might
> > unintentionally encourage its use.
>
> Our intent here is that "any" will be available for all code. Yes, we
> wouldn't do it if it weren't for its use as a type constraint. But if
> we are going to do it for type constraints, there seems to be little
> benefit to restricting it to only work as a type constraint.

What worries me is code like this:

func f() any {
int *i
return i
}

func main() {
if f()==nil {
...
}
}

Use of "any" makes it look like f returns an *int and f() is nil, but
it is not, because "any" is interface{}.

I think "any" as a constraint is useful, like "comparable", but "any"
as a type is misleading.

Kurtis Rader

unread,
Aug 21, 2020, 1:54:41 AM8/21/20
to burak serdar, Ian Lance Taylor, Jan Mercl, golang-nuts, Robert Griesemer
On Thu, Aug 20, 2020 at 10:40 PM burak serdar <bse...@computer.org> wrote:
What worries me is code like this:

func f() any {
   int *i
  return i
}

func main() {
   if f()==nil {
    ...
   }
}

Use of "any" makes it look like f returns an *int and f() is nil, but
it is not, because "any" is interface{}.

I think "any" as a constraint is useful, like "comparable", but "any"
as a type is misleading.

Isn't your example just a case of confusing a nil interface with a nil value inside a generic interface? How does requiring writing it as `func f() interface{} {` make the behavior any clearer?

--
Kurtis Rader
Caretaker of the exceptional canines Junior and Hank

Kurtis Rader

unread,
Aug 21, 2020, 2:19:34 AM8/21/20
to burak serdar, Ian Lance Taylor, Jan Mercl, golang-nuts, Robert Griesemer
On Thu, Aug 20, 2020 at 10:53 PM Kurtis Rader <kra...@skepticism.us> wrote:
Isn't your example just a case of confusing a nil interface with a nil value inside a generic interface? How does requiring writing it as `func f() interface{} {` make the behavior any clearer?

And the, possibly canonical, discussion about this: https://groups.google.com/g/golang-nuts/c/wnH302gBa4I/discussion

Bakul Shah

unread,
Aug 21, 2020, 2:22:51 AM8/21/20
to Ian Lance Taylor, golang-nuts, Robert Griesemer
On Aug 20, 2020, at 5:27 PM, Ian Lance Taylor <ia...@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{}”.

Great!


> 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.

Still uncomfortable with this. Will try to expand on this in a separate
email.

> 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.

You seem to be saying a generic function can use operator X only if
if *every* type in the type list implements it. Thus if I have

type foo interface { int; someSLice }

I can't use + and I can't use len(), right?

Axel Wagner

unread,
Aug 21, 2020, 3:38:06 AM8/21/20
to golang-nuts
Just to clarify, the intent is to make the declaration in the spec `type any = interface{}`, not `type any interface{}`, correct? The latter would be more analogous to `error`. Either has certain advantages and disadvantages, I'm not sure which I prefer, but I just want to make sure I understand the plan :)

> But in my opinion that would, so far, not pass a code review within the Go project per se. (It would definitely not pass my code review.)

It wouldn't pass my review either - but the only reason for that is that it trades off the overhead of looking up the definition of `any` for the convenience of typing/reading `interface{}`. With a predeclared identifier that's part of the language, the downsides of this tradeoff vanish.

> Use of "any" makes it look like f returns an *int and f() is nil, but it is not, because "any" is interface{}.

Apart from what others have said in general, the elephant in the room is, of course, `error`. I don't think pre-declaring a name for an interface changes the equation here.

--
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...@googlegroups.com.

David Riley

unread,
Aug 21, 2020, 8:36:08 AM8/21/20
to Ian Lance Taylor, jimmy frasche, Robert Engels, Jan Mercl, golang-nuts, Robert Griesemer
On Aug 21, 2020, at 00:56, Ian Lance Taylor <ia...@golang.org> wrote:
>
> No, the intent is that you would switch on the type parameter itself,
> not a value.
>
> func g[T C](v T) T {
> switch T {
> // the rest is the same
> }
> }
>
> Thanks for asking.

Oh, this clarifies my remaining murkiness about the type switches quite nicely. Thanks!

- Dave

burak serdar

unread,
Aug 21, 2020, 8:57:13 AM8/21/20
to Kurtis Rader, Ian Lance Taylor, Jan Mercl, golang-nuts, Robert Griesemer
The point I was trying to make is that "any" as a constraint is better
than "interface{}" as a constraint, but "interface{}" as a type is
better than "any" as a type.

interface{}, when used as a constraint, doesn't mean than the value
has to be an interface{}, it means the value can be anything.
interface{}, when used as a value, doesn't mean that the value can be
anything, it means that the value is an interface, and you have to get
the value from that interface. Different uses, different identifiers.

I would be more comfortable if "any" was only a constraint instead of
an alias for interface{}. If your code needs it, you can still define
it. But if it is there, people will use it even if they can do without
it.

That said, I think the generics design draft looks great.

Carla Pfaff

unread,
Aug 21, 2020, 9:43:51 AM8/21/20
to golang-nuts
On Friday, 21 August 2020 at 14:57:13 UTC+2 bbse...@gmail.com wrote:
interface{}, when used as a constraint, doesn't mean than the value
has to be an interface{}, it means the value can be anything.
interface{}, when used as a value, doesn't mean that the value can be
anything, it means that the value is an interface, and you have to get
the value from that interface. Different uses, different identifiers.
 
The same is true for "interface{String() string}" as a constraint and "interface{String() string}" as a type.
Does that mean that you want to allow the identifier "fmt.Stringer" only for constraints, but not for types?

Ian Lance Taylor

unread,
Aug 21, 2020, 10:24:36 AM8/21/20
to Axel Wagner, golang-nuts
On Fri, Aug 21, 2020, 12:37 AM 'Axel Wagner' via golang-nuts <golan...@googlegroups.com> wrote:
Just to clarify, the intent is to make the declaration in the spec `type any = interface{}`, not `type any interface{}`, correct? The latter would be more analogous to `error`. Either has certain advantages and disadvantages, I'm not sure which I prefer, but I just want to make sure I understand the plan :)

I've been thinking of a type alias rather than a defined type, but I'm not sure which is best.  It would be interesting to hear whether anybody has a clear preference, and why.

Ian

Ian Lance Taylor

unread,
Aug 21, 2020, 10:28:40 AM8/21/20
to Bakul Shah, golang-nuts, Robert Griesemer
Right.  And, to be clear, that is how the current design draft works before this change.

Ian

roger peppe

unread,
Aug 21, 2020, 10:39:04 AM8/21/20
to Ian Lance Taylor, golang-nuts, Robert Griesemer
This is great. I like the fact that the type parameter list is just a normally formed parameter list now.
I did quite like the fact that you could see defined types with a grep for "type", but that's
not always the case anyway with type blocks, so I'll put that feeling aside.

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.

This is also a great forward-looking change.
 

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.

Ditto.
 

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.

I'm less keen on this restriction. That latter construct has at least one significant
pitfall: if the type parameter is an interface type and x is nil, the type switch won't
choose the correct type. Instead, you'd need to do something like “switch (interface{})(&x).(type)”
and use the pointer type in the type switch (https://go2goplay.golang.org/p/IdIuWkMTaBF).
It might not even be obvious that we're switching on an interface type when we're using
a type parameter in the switch case. For example: https://go2goplay.golang.org/p/5ridyxPoJOZ.

Another issue with this approach is that you may need to use non-type-safe (and somewhat
verbose) code to work around it. Here's an example: https://go2goplay.golang.org/p/A-_mVRxhvl4
This fails because even though we know the type of the value in the type switch, the
compiler can't make the connection between that and the actual type of K, so
it's not possible to use values of the known type where values of type K are expected.
The workaround is to do a dynamic type conversion: https://go2goplay.golang.org/p/PBYDoESWXjW,
but it's not hard to get wrong, resulting in a runtime panic.

I suggest that we allow type switches on non-type-list type parameters and make it clear
that the type matching is exact - there would be no way to use a type switch to
match against a type parameter's underlying type.

  cheers,
    rog.


burak serdar

unread,
Aug 21, 2020, 10:46:22 AM8/21/20
to Carla Pfaff, golang-nuts
All constraints except "any" specify a constraint for the type. A
Stringer constraint will ensure that the type has String() string
method. "any" is a lack of constraint. I think the two concepts are
different. My problem is the attractiveness of "any" as a return type.
I can live with it, but I expect to see more questions raised about
functions returning "any" values behaving weird in a nil-check.


>
> --
> 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...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/4c9a2735-3e22-4568-ac0b-8c6a8b4b8583n%40googlegroups.com.

Chris Hines

unread,
Aug 21, 2020, 11:00:11 AM8/21/20
to golang-nuts
I believe these are fantastic choices.

I have given two presentations to Go meetups about the draft generics design in the last month. The second time was after the square bracket idea was added to the prototype and someone suggested the other parts adopted in item #1. In that second presentation I used the []'s syntax while walking through some examples and found it really helped the readability. In addition I was an instant fan of the [T any] syntax when it was suggested and I am happy to see that adopted. The rest of the tweaks are welcome as well, especially the addition of type switches on type parameters. I look forward to trying that out because I believe it plugs a hole in the design I had felt in my earlier experiments.

Well done and thanks to everyone who has helped provide feedback leading to these improvements.

Chris

Carla Pfaff

unread,
Aug 21, 2020, 1:02:17 PM8/21/20
to golang-nuts
On Friday, 21 August 2020 at 16:46:22 UTC+2 bbse...@gmail.com wrote:
All constraints except "any" specify a constraint for the type. A
Stringer constraint will ensure that the type has String() string
method. "any" is a lack of constraint.

The empty interface / any is a constraint that ensures that the type has at least 0 methods and all of these 0 methods must match the 0 methods of the interface. An empty purse is still a purse, an empty constraint is still a constraint.
 
My problem is the attractiveness of "any" as a return type. 

I don't see why anybody would find it attractive as a return type. People don't use the empty interface because they like it so much, but because Go doesn't have parametric polymorphism / "generics" yet. There are many programming languages that have a named top type and it is rarely abused. Programmers want to write type safe code if they can.

roger peppe

unread,
Aug 21, 2020, 1:09:06 PM8/21/20
to Ian Lance Taylor, Axel Wagner, golang-nuts
My vote is for a type alias. Using a named type doesn't make any difference for type parameter constraints, and I'd prefer it if "[]interface{}" and "[]any" (or other types involving interface{}) weren't incompatible types. "interface{}" is currently a nice universally understood type - I don't think it's worth splitting it into two distinct types.

  cheers,
    rog.




Ian

--
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...@googlegroups.com.

Jan Mercl

unread,
Aug 21, 2020, 1:28:48 PM8/21/20
to Carla Pfaff, golang-nuts
On Fri, Aug 21, 2020, 19:01 'Carla Pfaff' via golang-nuts <golan...@googlegroups.com> wrote:

People don't use the empty interface because they like it so much, but because Go doesn't have parametric polymorphism / "generics" yet.

This argument seems to fail the fmt.Printf and friends reality check.

Axel Wagner

unread,
Aug 21, 2020, 1:31:06 PM8/21/20
to roger peppe, Ian Lance Taylor, golang-nuts
My one concern with making it an alias is error messages.
If the source code says "any", I think so should the error messages. Currently, the compiler forgets aliases too early.

roger peppe

unread,
Aug 21, 2020, 1:45:17 PM8/21/20
to Axel Wagner, Ian Lance Taylor, golang-nuts
On Fri, 21 Aug 2020 at 18:30, Axel Wagner <axel.wa...@googlemail.com> wrote:
My one concern with making it an alias is error messages.
If the source code says "any", I think so should the error messages. Currently, the compiler forgets aliases too early.

I agree that's a concern, but I think that should be a reason to fix error messages when aliases are used, not to use a named type.

Denis Cheremisov

unread,
Aug 21, 2020, 1:54:40 PM8/21/20
to golang-nuts
BTW, I am really glad your proposal is accepted, now the whole thing feels polished and IMO it is time to start building an implementation.

пятница, 21 августа 2020 г. в 20:02:17 UTC+3, Carla Pfaff:

burak serdar

unread,
Aug 21, 2020, 2:15:46 PM8/21/20
to Carla Pfaff, golang-nuts
I disagree. Especially people coming from other languages with a
strong emphasis on DRY tend to overuse interface{}, many times
incorrectly, and generics will not fix that. "any" will make it more
attractive, because it no longer looks like an interface. I agree that
this is a hypothetical problem at this point without any actual
complaints from developers. But defining an alias "any" is easy if you
need it. Providing one predefined is endorsing its use.

>
> --
> 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...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/8e5b46ba-7a3e-4d55-ac97-1e70d06e622dn%40googlegroups.com.

Viktor Kojouharov

unread,
Aug 21, 2020, 3:51:47 PM8/21/20
to golang-nuts
I like all points in the draft change.

My 2 cents around the any alias would be that it shouldn't be a problem. I don't think people are suddently going to start overusing it, since using vaues of type `interface{}` is extremely tedious in Go, and that won't magically change if the type is shorter by a few characters.

One thing that might be of concern regarding using type lists in interfaces is that, if that is allowed outside of generics, then it should be decided beforehand whether a type switch over such values is exhaustive or not. I don't think it will be a breaking change if it is exhaustive for such interfaces, but if it isn't, then changing it later would be. For the record, I think it should b exhaustive.

Russ Cox

unread,
Aug 21, 2020, 4:28:18 PM8/21/20
to Axel Wagner, roger peppe, Ian Lance Taylor, golang-nuts
On Fri, Aug 21, 2020 at 1:30 PM 'Axel Wagner' via golang-nuts <golan...@googlegroups.com> wrote:
My one concern with making it an alias is error messages.
If the source code says "any", I think so should the error messages. Currently, the compiler forgets aliases too early.

Russ Cox

unread,
Aug 21, 2020, 4:54:28 PM8/21/20
to golang-nuts
Hi all,

A few people have raised concerns about "encouraging" use of any instead of interface{}, on the grounds that it is not idiomatic Go. I just wanted to acknowledge that yes, it's not idiomatic Go today, but that's true of essentially every language change. Thinking about future usage is a good consideration, but we define idiomatic by convention, and that convention is heavily influenced by what is and is not in the language. It is OK for the convention to shift, and here it almost certainly would.

Many years ago, there was no rune type in Go. We used int instead, as in:

    runes := []int("hello")

It would certainly not have been idiomatic at the time to instead define

    type rune = int

and then write code like:

    runes := []rune("hello")

The argument against doing this would be that everyone expects to see []int instead, and that introducing your own custom definition here for a standard language concept adds to the conceptual burden for readers by making your code look different from everyone else's, forcing readers to search out the definition of "rune" to understand the code. (It was equally non-idiomatic to define "type error = os.Error" before we had the predefined error type.)

The same argument that applied back then to a custom rune = int or error = os.Error alias applies today to a custom any = interface{} alias: it's not a good thing to make your code gratuitously different from others' code.

But just as "type rune = int" being non-idiomatic did not preclude introducing a standard predefined rune type alias, the fact that "type any = interface{}" is non-idiomatic today does not preclude introducing a standard predefined any type alias. It becomes idiomatic by making it part of the language. Then everyone can use it, and no one's code is gratuitously different. 

It's also true that older Go code using interface{} will look different from newer Go code using "any". But that is true of every language change. New code that omits semicolons looks different from old code that doesn't. New code that writes 0o777 looks different from old code that writes 0777. And so on, for every change we make. This is fundamental to languages evolving: new code and old code look different. (Part of minimizing the need to code switch is providing tools to help update old code to look new; we've done that repeatedly in the past and would undoubtedly do that here as well.)

A few other people have raised concerns about not seeing the word interface and therefore not realizing "any" is an interface type and potentially getting confused. This is also a good consideration, but we already have many interface types that don't use the word interface, most notably the predefined type error, but also io.Reader, http.ResponseWriter, and so on. People learn early on that not all interface types say interface in the name. I don't expect that "any" will not be any harder to learn than the others.

In any (ha!) event, it's probably more important to focus on the larger semantics of generics than this specific color, attractive though it may be.

Best,
Russ

roger peppe

unread,
Aug 21, 2020, 5:08:26 PM8/21/20
to Ian Lance Taylor, golang-nuts, Robert Griesemer
We’re going to simplify the rule for type list satisfaction.  The type

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:

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 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", I don't see that this would necessarily be the case. Thoughts?

jimmy frasche

unread,
Aug 21, 2020, 5:11:48 PM8/21/20
to roger peppe, Ian Lance Taylor, golang-nuts, Robert Griesemer
I'd assume that would fail to compile as you're returning a []T not a []int
> --
> 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...@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.

roger peppe

unread,
Aug 21, 2020, 5:31:01 PM8/21/20
to jimmy frasche, Ian Lance Taylor, golang-nuts, Robert Griesemer
On Fri, 21 Aug 2020 at 22:10, jimmy frasche <soapbo...@gmail.com> wrote:
I'd assume that would fail to compile as you're returning a []T not a []int

If that's the case, then I'm not sure that such a type switch would be very useful. It would tell you what type the values are, but you can't do anything with them because all the values would still be of the original type.

I had assumed that the intention was that within the arm of the type switch, the switched type would take on the specified type.
That would allow (for example) specialising to use underlying machine operations on []T when T is a known type such as byte.

Axel Wagner

unread,
Aug 21, 2020, 5:43:03 PM8/21/20
to roger peppe, jimmy frasche, Ian Lance Taylor, golang-nuts, Robert Griesemer
On Fri, Aug 21, 2020 at 11:30 PM roger peppe <rogp...@gmail.com> wrote:
On Fri, 21 Aug 2020 at 22:10, jimmy frasche <soapbo...@gmail.com> wrote:
I'd assume that would fail to compile as you're returning a []T not a []int

If that's the case, then I'm not sure that such a type switch would be very useful. It would tell you what type the values are, but you can't do anything with them because all the values would still be of the original type.

You can reasonably convert them to their underlying type and *then* use them as such.
That would make it useful while not allowing what you posit.

I had assumed that the intention was that within the arm of the type switch, the switched type would take on the specified type.
That would allow (for example) specialising to use underlying machine operations on []T when T is a known type such as byte.

It would, however, prevent you from calling methods on the type or pass it to a function taking an interface compatible with the constraint.
Also, I shudder to even imagine how this could be put into a spec.
 

Axel Wagner

unread,
Aug 21, 2020, 5:44:03 PM8/21/20
to roger peppe, jimmy frasche, Ian Lance Taylor, golang-nuts, Robert Griesemer
also, of course, you could still use operators with them, while now also knowing the exact semantics of those operators (e.g. in regards to overflow), which might also be useful.

Ian Lance Taylor

unread,
Aug 21, 2020, 5:46:44 PM8/21/20
to Axel Wagner, roger peppe, jimmy frasche, golang-nuts, Robert Griesemer
Yes, there are various such possibilities.

What jimmy frasche said above is correct: nothing changes in the case
of a type switch of a type parameter. The code now knows the type
list element that the type argument matched, but it can't do anything
that it couldn't do anyhow.

Ian

On Fri, Aug 21, 2020 at 2:43 PM Axel Wagner

Bakul Shah

unread,
Aug 21, 2020, 5:49:24 PM8/21/20
to roger peppe, Ian Lance Taylor, golang-nuts, Robert Griesemer
On Aug 21, 2020, at 2:07 PM, roger peppe <rogp...@gmail.com> wrote:
>
> 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
> }

IMHO this should work. This makes sense to me because I think
of generics as merely constrained compile time macros :-) The
switch can removed at compile time by the compiler. [Note that
in this regard "intlike" is *different* from sum types, which are
a runtime concept that Go doesn't have (except for interface{}).]

Axel Wagner

unread,
Aug 21, 2020, 6:04:22 PM8/21/20
to Ian Lance Taylor, roger peppe, jimmy frasche, golang-nuts, Robert Griesemer
On Fri, Aug 21, 2020 at 11:46 PM Ian Lance Taylor <ia...@golang.org> wrote:
Yes, there are various such possibilities.

What jimmy frasche said above is correct: nothing changes in the case
of a type switch of a type parameter.  The code now knows the type
list element that the type argument matched, but it can't do anything
that it couldn't do anyhow.

I think there are two reasonable things that it could be allowed to do in the case, that aren't allowed outside:
1. Convert to the matched type. We have a guarantee that the matched type is either identical or has the same underlying type, both of which would allow a conversion in the language as-is. I feel allowing this conversion would be sufficiently useful (e.g. passing things to `strconv.Itoa` or functions from `math` can be very useful).
2. If the type is not a predeclared type, we could even take this a step further, as the types must be identical - so we might allow treating them as such. This feels natural when viewed from the "type lists are essentially sum types" POV. However, it would treat predeclared types more special than other declared types and so it may be too elaborate to put into the spec. It would also allow what rog suggest - but only in certain corner cases, which feels weird.

The more I think about it, the less I understand the intention behind the type-switch construct introduced. I tend to agree that 1. at least should be possible to make it useful. But even then, it seems like kind of a big change for relatively limited use. What was the motivation behind that change? Is there discussion somewhere, of interesting use-cases this enables?

jimmy frasche

unread,
Aug 21, 2020, 6:12:47 PM8/21/20
to Axel Wagner, Ian Lance Taylor, roger peppe, golang-nuts, Robert Griesemer
I don't want a generic min unless it looks like this:

func Min[T constraints.Ordered](a, b T) T {
switch T {
case float32:
return T(math.Min(float32(a), float32(b)))
case float64:
return T(math.Min(float64(a), float64(b)))
}
if a < b {
return a
}
return b
}

On Fri, Aug 21, 2020 at 3:03 PM Axel Wagner

roger peppe

unread,
Aug 21, 2020, 6:17:29 PM8/21/20
to Ian Lance Taylor, Axel Wagner, jimmy frasche, golang-nuts, Robert Griesemer
On Fri, 21 Aug 2020 at 22:46, Ian Lance Taylor <ia...@golang.org> wrote:
Yes, there are various such possibilities.

What jimmy frasche said above is correct: nothing changes in the case
of a type switch of a type parameter.  The code now knows the type
list element that the type argument matched, but it can't do anything
that it couldn't do anyhow.

I suspect that this would make the new type switch construct very much less useful.
For example, it wouldn't be possible to specialize a vector operation to use machine-specific operations on a vector without using unsafe.

It might be interesting to do a survey of some of the reasons that people would wish to use a type switch on a type parameter and see if the proposed construct would be sufficient.

Have you got an example of how it might be used?

  cheers,
    rog.

roger peppe

unread,
Aug 21, 2020, 6:34:57 PM8/21/20
to Axel Wagner, Ian Lance Taylor, jimmy frasche, golang-nuts, Robert Griesemer
On Fri, 21 Aug 2020 at 23:03, Axel Wagner <axel.wa...@googlemail.com> wrote:
On Fri, Aug 21, 2020 at 11:46 PM Ian Lance Taylor <ia...@golang.org> wrote:
Yes, there are various such possibilities.

What jimmy frasche said above is correct: nothing changes in the case
of a type switch of a type parameter.  The code now knows the type
list element that the type argument matched, but it can't do anything
that it couldn't do anyhow.

I think there are two reasonable things that it could be allowed to do in the case, that aren't allowed outside:
1. Convert to the matched type. We have a guarantee that the matched type is either identical or has the same underlying type, both of which would allow a conversion in the language as-is. I feel allowing this conversion would be sufficiently useful (e.g. passing things to `strconv.Itoa` or functions from `math` can be very useful).

Yes. Without this, I can't see that the type switch is useful for anything at all other than unsafe operations.

2. If the type is not a predeclared type, we could even take this a step further, as the types must be identical - so we might allow treating them as such.
This feels natural when viewed from the "type lists are essentially sum types" POV. However, it would treat predeclared types more special than other declared types and so it may be too elaborate to put into the spec. It would also allow what rog suggest - but only in certain corner cases, which feels weird.

The more I think about it, the less I understand the intention behind the type-switch construct introduced. I tend to agree that 1. at least should be possible to make it useful. But even then, it seems like kind of a big change for relatively limited use. What was the motivation behind that change? Is there discussion somewhere, of interesting use-cases this enables?

As one motivating example, imagine that we had some highly optimized machine code that sums a slice of 32 bit numbers, but we want to provide a generic version: https://go2goplay.golang.org/p/0YvkOcc-eBs 

I suspect that kind of scenario is one significant use case for using a type switch in a generic function, and I don't think that the type switch proposed is sufficient for that.

roger peppe

unread,
Aug 21, 2020, 6:49:21 PM8/21/20
to jimmy frasche, Axel Wagner, Ian Lance Taylor, golang-nuts, Robert Griesemer
On Fri, 21 Aug 2020 at 23:12, jimmy frasche <soapbo...@gmail.com> wrote:
I don't want a generic min unless it looks like this:

func Min[T constraints.Ordered](a, b T) T {
  switch T {
  case float32:
    return T(math.Min(float32(a), float32(b)))
  case float64:
    return T(math.Min(float64(a), float64(b)))
  }
  if a < b {
    return a
  }
  return b
}

I'd really like to be able to write that as:

func Min[T constraints.Ordered](a, b T) T {
  switch T {
  case float32:
    return math.Min(float64(a), float64(b))
  case float64:
    return math.Min(a, b)

  }
  if a < b {
    return a
  }
  return b
}

But then there is the difficulty that the original T is no longer the same as the T inside the type switch.
So this code would print false not true, which would be... somewhat unexpected: https://go2goplay.golang.org/p/d0cJBfYObAY

Perhaps the type switch should allow declaring a new name for the switched type, making it clear that a new type was being declared inside the switch.

Something like:

    switch T1 := T {
    }

but the ":=" operator doesn't seem quite right there, and that still doesn't solve the original problem, because T1 is still incompatible with all the argument values. I guess that one could allow conversion or assignment compatibility between types involving T and T1, but that might complicate the spec too much.

Ian Lance Taylor

unread,
Aug 21, 2020, 7:35:28 PM8/21/20
to Axel Wagner, roger peppe, jimmy frasche, golang-nuts, Robert Griesemer
On Fri, Aug 21, 2020 at 3:03 PM Axel Wagner
<axel.wa...@googlemail.com> wrote:
>
> On Fri, Aug 21, 2020 at 11:46 PM Ian Lance Taylor <ia...@golang.org> wrote:
>>
>> Yes, there are various such possibilities.
>>
>> What jimmy frasche said above is correct: nothing changes in the case
>> of a type switch of a type parameter. The code now knows the type
>> list element that the type argument matched, but it can't do anything
>> that it couldn't do anyhow.
>
>
> I think there are two reasonable things that it could be allowed to do in the case, that aren't allowed outside:
> 1. Convert to the matched type. We have a guarantee that the matched type is either identical or has the same underlying type, both of which would allow a conversion in the language as-is. I feel allowing this conversion would be sufficiently useful (e.g. passing things to `strconv.Itoa` or functions from `math` can be very useful).
> 2. If the type is not a predeclared type, we could even take this a step further, as the types must be identical - so we might allow treating them as such. This feels natural when viewed from the "type lists are essentially sum types" POV. However, it would treat predeclared types more special than other declared types and so it may be too elaborate to put into the spec. It would also allow what rog suggest - but only in certain corner cases, which feels weird.
>
> The more I think about it, the less I understand the intention behind the type-switch construct introduced. I tend to agree that 1. at least should be possible to make it useful. But even then, it seems like kind of a big change for relatively limited use. What was the motivation behind that change? Is there discussion somewhere, of interesting use-cases this enables?

Given a type parameter, there are two interesting pieces of
information. One is what the actual type argument is; we can already
determine that by writing code like "var x T; switch
(interface{})(x).(type) { ... }". The other is which type in the type
list was matched by the type argument. The latter is the purpose of
the type switch suggested here. Without something like that type
switch, there is no straightforward way for a generic function to
determine which type in a type list is matched by a type argument.

See also https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md#identifying-the-matched-predeclared-type
.

I'm not at all wedded to this. We can continue to omit it. It just
seems like adding a capability that does not otherwise exist.

Ian

wilk

unread,
Aug 22, 2020, 2:01:11 AM8/22/20
to golan...@googlegroups.com
On 21-08-2020, Russ Cox wrote:

> A few other people have raised concerns about not seeing the word interface
> and therefore not realizing "any" is an interface type and potentially
> getting confused. This is also a good consideration, but we already have
> many interface types that don't use the word interface, most notably the
> predefined type error, but also io.Reader, http.ResponseWriter, and so on.
> People learn early on that not all interface types say interface in the
> name. I don't expect that "any" will not be any harder to learn than the
> others.

Why not `anyer` then ?

--
Wilk

Volker Dobler

unread,
Aug 22, 2020, 3:00:21 AM8/22/20
to golang-nuts
On Friday, 21 August 2020 20:15:46 UTC+2, burak serdar wrote:
[...] 
> I don't see why anybody would find it attractive as a return type. People don't use the empty interface because they like it so much, but because Go doesn't have parametric polymorphism / "generics" yet. There are many programming languages that have a named top type and it is rarely abused. Programmers want to write type safe code if they can.

I disagree. Especially people coming from other languages with a
strong emphasis on DRY tend to overuse interface{}, many times
incorrectly, and generics will not fix that. "any" will make it more
attractive, because it no longer looks like an interface. I agree that
this is a hypothetical problem at this point without any actual
complaints from developers. But defining an alias "any" is easy if you
need it. Providing one predefined is endorsing its use. 

Adopting Go because people adopted bad habits in other languages
is probably not a good idea. Fanatic DRY is a bad idea in any
language and if somebody wants to footgun himself by premature
DRY ing up he will be as able with interface{}  as with any.

I find a predefined any very sensible.

V.

Viktor Kojouharov

unread,
Aug 22, 2020, 8:23:34 AM8/22/20
to golang-nuts
On Saturday, August 22, 2020 at 12:49:21 AM UTC+2 rog wrote:
On Fri, 21 Aug 2020 at 23:12, jimmy frasche <soapbo...@gmail.com> wrote:
I don't want a generic min unless it looks like this:

func Min[T constraints.Ordered](a, b T) T {
  switch T {
  case float32:
    return T(math.Min(float32(a), float32(b)))
  case float64:
    return T(math.Min(float64(a), float64(b)))
  }
  if a < b {
    return a
  }
  return b
}

I'd really like to be able to write that as:

func Min[T constraints.Ordered](a, b T) T {
  switch T {
  case float32:
    return math.Min(float64(a), float64(b))
  case float64:
    return math.Min(a, b)

  }
  if a < b {
    return a
  }
  return b
}

Kotlin calls such a feature smart-casting, though it works for any type rather than such generic types with type lists. I'd love if Go had such a feature, even if it only applied to switch statements over generic types with type lists in them. This wouldn't be conversion, since the type is the actual type that is matched with the case.

Awh6al

unread,
Aug 22, 2020, 3:06:18 PM8/22/20
to golang-nuts
This is absolutely big improvement to make go generics simple and remove the gap between the old and new syntax :). 

Juliusz Chroboczek

unread,
Aug 23, 2020, 6:00:38 PM8/23/20
to golan...@googlegroups.com
> 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)”.

Could you please give an example of the proposed syntax?

Ian Lance Taylor

unread,
Aug 23, 2020, 11:40:52 PM8/23/20
to Juliusz Chroboczek, golang-nuts
func F[T constraints.Integer]() {
switch T {
case int:
case int8:
}
}

Ian

Denis Cheremisov

unread,
Aug 24, 2020, 1:35:18 AM8/24/20
to golang-nuts
I probably didn't read what you have wrote in the first message carefuly enough. Does it mean something like that will work

    type SomeTypes interface {
    type int, float32, float64
    }

    func Min[T SomeTypes](x, y T) T {
        switch T {
        case int:
            if x < y {
                return x
            }
            return y
        case float32:
            return math.Min(float64(x), float64(y))
        case float64:
            return math.Min(x, y)
        }
    }

Would something like below work as well?

    type Compare[T any] interface {
        Compare(x, y T) int
    }

    type CompareConstraints[T any] {
        type int, int8, …, float64, string, Compare[T]
    }

    func Min[T CompareConstraints]Min(x, y T) bool {
        switch T {
        case int:
            …
        …
        case Compare[T]:
            return x.Compare(y) < 0
        }
    }

понедельник, 24 августа 2020 г. в 06:40:52 UTC+3, Ian Lance Taylor:

roger peppe

unread,
Aug 24, 2020, 2:09:15 AM8/24/20
to Denis Cheremisov, golang-nuts
On Mon, 24 Aug 2020 at 06:35, Denis Cheremisov <denis.ch...@gmail.com> wrote:
I probably didn't read what you have wrote in the first message carefuly enough. Does it mean something like that will work

    type SomeTypes interface {
    type int, float32, float64
    }

    func Min[T SomeTypes](x, y T) T {
        switch T {
        case int:
            if x < y {
                return x
            }
            return y
        case float32:
            return math.Min(float64(x), float64(y))
        case float64:
            return math.Min(x, y)
        }
    }
 
This was discussed above.

I don't believe you can do that. I didn't see any suggestion that knowing the type implies the ability
to convert from the generic type to the known underlying type.

Would something like below work as well?

    type Compare[T any] interface {
        Compare(x, y T) int
    }

    type CompareConstraints[T any] {
        type int, int8, …, float64, string, Compare[T]
    }

    func Min[T CompareConstraints]Min(x, y T) bool {
        switch T {
        case int:
            …
        …
        case Compare[T]:
            return x.Compare(y) < 0
        }
    }

No. As proposed, the type switch doesn't change anything about the type T.

Also, AIUI that type list wouldn't be very useful in practice, because using that interface type in the type list would only allow types whose underlying type is exactly Compare[T], which doesn't include any non-interface types or interface types that have more methods than just Compare.

  cheers,
    rog.

Anderson Queiroz

unread,
Aug 24, 2020, 4:39:47 AM8/24/20
to golang-nuts
I liked the 'any' idea. It always felt to me a bit odd/not so clear interface{} means anything.

Still, interfaces with type parameters for non generic code seems a mix of behaviour and implementation description.

In all, looking forward to see these changes implemented to try them out :)

Juliusz Chroboczek

unread,
Aug 24, 2020, 5:14:28 AM8/24/20
to Ian Lance Taylor, golang-nuts
>> Could you please give an example of the proposed syntax?

> func F[T constraints.Integer]() {
> switch T {
> case int:
> case int8:
> }
> }

Makes perfect sense, thanks.

Richard Oudkerk

unread,
Aug 24, 2020, 7:57:47 AM8/24/20
to golang-nuts
The current proposal says

The rule is that if a type contraint has a single type parameter, and it is used in a function's type parameter list without an explicit type argument, then the type argument is the type parameter being constrained.

That means you can write

    type Comparer[T any] interface {
        Compare(T) int
    }

    func Min[T Comparer](x, y T) bool {
        if x.Compare(y) < 0 {
            return x
        }
        return y
    }

Applying the same rule to type lists and type switches you should be able to write

    type Comparer[T any] interface {
        Compare(T) int
    }

    type CompareConstraints interface {
        type int, int8, …, float64, string, Comparer
    }

    func Min[T CompareConstraints](x, y T) bool {
        switch T {
        case int:
            …
        …
        case Comparer:
            if x.Compare(y) < 0 {
                return x
            }
            return y
        }
    }

But the current proposal does not allow interfaces in type lists.

roger peppe

unread,
Aug 24, 2020, 8:19:46 AM8/24/20
to Richard Oudkerk, golang-nuts
On Mon, 24 Aug 2020 at 12:57, 'Richard Oudkerk' via golang-nuts <golan...@googlegroups.com> wrote:

Applying the same rule to type lists and type switches you should be able to write

    type Comparer[T any] interface {
        Compare(T) int
    }

    type CompareConstraints interface {
        type int, int8, …, float64, string, Comparer
    }

    func Min[T CompareConstraints](x, y T) bool {
        switch T {
        case int:
            …
        …
        case Comparer:
            if x.Compare(y) < 0 {
                return x
            }
            return y
        }
    }

But the current proposal does not allow interfaces in type lists.

FWIW, even if the proposal did allow interfaces in type lists, you wouldn't be able to invoke the Compare method on x, because both:
a) the allowed operations on T are the intersection of all the operations in the type list, and only one of those types has a Compare method
and
b) according to the first post in this thread, methods are specifically excluded from the operations allowed by a type list.
 
--
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...@googlegroups.com.

adon...@google.com

unread,
Aug 24, 2020, 12:57:10 PM8/24/20
to golang-nuts
On Thursday, 20 August 2020 20:28:23 UTC-4, Ian Lance Taylor wrote:
We’re going to settle on square brackets for the generics syntax.

FWIW, the same square-bracket syntax was used by Barbara Liskov's CLU in the mid-1970s, and was, as far as I can tell, the first syntax used for parametric polymorphism in an ALGOL-like language. The Greek prefix notation (e.g. α list) used by its contemporary, ML, appears to have originated in Christopher Strachey's 1968 lecture notes. C++'s angle-bracket templates didn't appear until the 1980s, along with ADAs, which used ordinary parens, as in your earlier proposal.





Ian Lance Taylor

unread,
Aug 24, 2020, 6:35:33 PM8/24/20
to burak serdar, Jan Mercl, golang-nuts, Robert Griesemer
On Thu, Aug 20, 2020 at 9:54 PM Ian Lance Taylor <ia...@golang.org> wrote:
>
> Our intent here is that "any" will be available for all code. Yes, we
> wouldn't do it if it weren't for its use as a type constraint. But if
> we are going to do it for type constraints, there seems to be little
> benefit to restricting it to only work as a type constraint.
>
> This is not, of course, a new idea, even in the absence of generics.
> For example, https://golang.org/issue/33232. (My comment there was
> that we would use interface{} less if we had generics, but of course
> when we require type constraints then we actually wind up using it
> somewhat more.)

I've seen objections that a language change for generics should not
implicitly pull in a change to non-generic code. That seems fair. It
may be the right thing to do, but it should be justified separately.
So we're going to start with "any" only being accepted as a type
constraint, and we can discuss making the name available for all uses
separately, probably on issue 33232. Clearly adding "any" as a name
accepted in type constraints is a step toward defining "any" for all
code, but doing so isn't a requirement for generics.

Ian

Kaveh Shahbazian

unread,
Aug 25, 2020, 5:38:00 PM8/25/20
to golang-nuts
I am excited about sum-types as much as generics themselves. Also, it's nice that any is a keyword restricted to be used inside the type parameter declaration as a type constraint.

Very nice!

---

P.S.
Of-course now the proposal seems to go with brackets. Nevertheless, I wrote this comment on the possibility of using angle brackets for the type parameter declaration. Maybe that way there are more symbols in the language. But they will be less overloaded with (completely) different semantics - assuming it's a practical approach.

Frederik Zipp

unread,
Aug 26, 2020, 12:58:01 AM8/26/20
to golang-nuts
Ian Lance Taylor schrieb am Dienstag, 25. August 2020 um 00:35:33 UTC+2:
I've seen objections that a language change for generics should not
implicitly pull in a change to non-generic code. That seems fair. It
may be the right thing to do, but it should be justified separately.
So we're going to start with "any" only being accepted as a type
constraint, and we can discuss making the name available for all uses
separately, probably on issue 33232.

I understand the motivation, but I hope this artificial restriction is eventually lifted, because I find special cases and exceptions to generality make a language more confusing to learn ("Why can I use this here but not there, even though it's referring to the same thing?"). 

Denis Cheremisov

unread,
Aug 26, 2020, 4:31:10 AM8/26/20
to golang-nuts
> possibility of using angle brackets

Please stop 
  • call these operator signs “brackets”
  • pretending they are good in a role of brackets — they are not
  • spreading this nonsense from C syntax family of languages to saner once — yes, you heard it right. C is known for its chaotic development and lack of careful planning.
  • thinking yet another strange workaround is a good thing

среда, 26 августа 2020 г. в 00:38:00 UTC+3, Kaveh Shahbazian:

Alan Donovan

unread,
Aug 26, 2020, 7:54:49 AM8/26/20
to Denis Cheremisov, golang-nuts
On Wed, 26 Aug 2020 at 04:31, Denis Cheremisov <denis.ch...@gmail.com> wrote:
> possibility of using angle brackets

Please stop 
  • call these operator signs “brackets”
  • pretending they are good in a role of brackets — they are not
  • spreading this nonsense from C syntax family of languages to saner once — yes, you heard it right. C is known for its chaotic development and lack of careful planning.
  • thinking yet another strange workaround is a good thing

On terminology:
US: [brackets], (parens).
UK: (brackets), [square brackets],
I'm sure other locales have their own equally confusing names for these things, but the Go team is based in the US, so you can't fault them for using that dialect.

To the rest of your points, I will only say: arguments are more persuasive than insults.

Message has been deleted

samir.el...@gmail.com

unread,
Aug 31, 2020, 1:25:13 PM8/31/20
to golang-nuts
Great improvements. Well done.

Any intentions to make methods accept additional type arguments ?

On Friday, August 21, 2020 at 2:28:23 AM UTC+2 Ian Lance Taylor 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 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

Ian Lance Taylor

unread,
Aug 31, 2020, 1:34:17 PM8/31/20
to samir.el...@gmail.com, golang-nuts
On Mon, Aug 31, 2020 at 10:25 AM samir.el...@gmail.com
<samir.el...@gmail.com> wrote:
>
> Great improvements. Well done.
>
> Any intentions to make methods accept additional type arguments ?

See https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md#No-parameterized-methods
.

Ian

Nishanth Shanmugham

unread,
Sep 2, 2020, 1:03:03 PM9/2/20
to golang-nuts
Some feedback on requiring any/interface{} explicitly.

> 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

Why is this simplification preferred? If it is intended at simplification for parsers,  I think that on its own it does not warrant the addition of a new predeclared identifier to the language spec. (We should rather do the extra work in the parsers.)

If it is intended to improve readability for readers of generic code, I think that the opposite is happening. Writing `any/interface{}` for a non-constrained type adds more clutter than it contributes to reading uniformity. It is common in languages such as TypeScript and Java for a non-constrained type to be simply written as "T", which in my experience, has been readable and clutter-free.

Axel Wagner

unread,
Sep 2, 2020, 1:50:32 PM9/2/20
to Nishanth Shanmugham, golang-nuts
On Wed, Sep 2, 2020 at 7:02 PM Nishanth Shanmugham <nishanth...@gmail.com> wrote:
If it is intended at simplification for parsers, […] If it is intended to improve readability for readers of generic code

I would argue that this is not a dichotomy. On the contrary: In general, a language that's easier to parse mechanically is also easier to parse by a human. The converse isn't always true, but still, optimizing for mechanical parsers also tends to help humans.
 
I think that the opposite is happening. Writing `any/interface{}` for a non-constrained type adds more clutter than it contributes to reading uniformity. It is common in languages such as TypeScript and Java for a non-constrained type to be simply written as "T", which in my experience, has been readable and clutter-free.

The choice isn't really between writing
[T]/[T Foo],
or writing
[T any]/[T Foo].
It's between the latter and
[type T]/[type T Foo].

So, assuming the first one isn't an option, do you still find the last one preferable over the second one?


On Monday, August 31, 2020 at 11:04:17 PM UTC+5:30 Ian Lance Taylor wrote:
On Mon, Aug 31, 2020 at 10:25 AM samir.el...@gmail.com
<samir.el...@gmail.com> wrote:
>
> Great improvements. Well done.
>
> Any intentions to make methods accept additional type arguments ?

See https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md#No-parameterized-methods
.

Ian

--
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...@googlegroups.com.

Nishanth Shanmugham

unread,
Sep 3, 2020, 6:01:33 AM9/3/20
to Axel Wagner, golang-nuts
After thinking about this more last evening and reading code in the new style, I've changed my mind.
I think my previous feedback was too influenced by the fact that I disliked a new predeclared identifier being introduced, and that the style was different from what I'm used to in TypeScript/Java.

I'm now liking the uniformity and the explicitness.

> In general, a language that's easier to parse mechanically is also easier to parse by a human.

Good point, and I now agree that it's the case here. I also like that requiring any/interface{} explicitly removes the confusion in code such as:

func Print[T, S Stringer]()

because "T, S Stringer" in non-type parameters means that T, S are both type Stringer, but is confusing in type parameters (namely, is T any or Stringer?).

> So, assuming the first one isn't an option, do you still find the last one preferable over the second one?

I prefer the second one, [T any]/[T Foo].

tdakkota

unread,
Sep 4, 2020, 2:45:48 PM9/4/20
to golang-nuts
I'd like to see sum types in Go2 and there are many reasons: 
- It can make using oneOf/anyOf in protobuf or swagger mush easier.
- It can make ast.Node type-safe. 
- With sum-types compiler known maximum size of variant, so it can be allocated on stack, not on heap.

But I don't think that using type list constraint as sum types is good idea.
Type constraints should be known in compile-time, but the sum type variant should be known in run-time.
пятница, 21 августа 2020 г. в 03:28:23 UTC+3, 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

Denis Cheremisov

unread,
Sep 4, 2020, 3:14:47 PM9/4/20
to golang-nuts
> But I don't think that using type list constraint as sum types is good idea.
> Type constraints should be known in compile-time, but the sum type variant should be known in run-time.

It looks like you misunderstand it a bit. Indeed

type Constraint interface {
    type Type₁, Type₂, …, Typeₙ
}

was introduced as a meta-construct. Some people, including me, were not particularly happy about it, as it
cannot be used as a value type and assymetry appears: all interfaces can be used as a constraint yet there
are interfaces that can't. An obvious inconsistency. So, they (the Go team) addressed this with an idea to
utilize such kind of interfaces for runtime values as a sum type.

I really wish they make it into Go together with generics too. Some parts of my code would finally be
straightforward.

пятница, 4 сентября 2020 г. в 21:45:48 UTC+3, tdakkota:

Denis Cheremisov

unread,
Sep 5, 2020, 4:27:29 AM9/5/20
to golang-nuts
Oops, I meant "all interfaces can be used as a constraint yet there are interfaces that can be used as a value's type"

Reply all
Reply to author
Forward
0 new messages