A few thoughts on type parameters

313 views
Skip to first unread message

Ben Hoyt

unread,
Aug 3, 2020, 4:49:03 AM8/3/20
to golang-nuts
I've played with generics a bit more, including the new square
brackets syntax. A few notes (some of the threads have been long, so
apologies if I'm doubling up):

1) I definitely prefer the square brackets syntax. It makes it clear
where you're using type parameters, and IMO it sets them off much more
nicely than parentheses -- especially in method definitions where it
would be parens inside parens.

Related question: why does the current go2go playground allow "func
Ptr[T](v T) *T { return &v }"? Though it turns [T] into [type T] when
you click "Format". Is the former syntax without the "type" keyword
(func Ptr[T]) going to be allowed, even if not encouraged?

2) Speaking of "func Ptr", I'll be really glad when we can write that
simple function generically, rather than all these libraries
implementing this for dozens of different types (e.g., the AWS SDK,
protobufs, a version in our own utility library at work, and I'm sure
we're not the only ones...). See also the proposal to allow the syntax
&T(v) natively to solve this problem:
https://github.com/golang/go/issues/9097 ... I am still a fan of that
proposal, but it'll be much less necessary if this proposal goes
ahead.

3) Defining new container types is fairly straight-forward (here's me
playing with a Map type: https://go2goplay.golang.org/p/y2dGXqsgfEb).
Obviously a basic map type like this isn't needed as Go already has
one, but it'd be similar to build a type-parameterized,
concurrency-safe Map, some kind of ordered dictionary, a set type,
etc. But a couple of things I didn't love:

(a) Needing "interface{}" (on the V type) just feels klunky. I kinda
understand why -- if any of them have a constraint, all of them have
to -- but I wonder if there's a way to avoid this. Maybe "_" instead
of "interface{}"?

(b) When I left off the "comparable" constraint the error message was
a bit cryptic: "invalid map key type K". "But why?", was my immediate
thought. Hopefully it'd be fairly easy to improve that error message.
Maybe something like "type K can't be used as map key; need
'comparable' constraint?"

At work, we've definitely wanted a Set type fairly often; we ended up
defining IntSet, StringSet, and InterfaceSet. So it'll be really nice
to not have to write N set types, or fall back on an interface{}
version. Or maybe there will even be a simple generic set type added
to the stdlib.

4) It seems strange to me that interfaces with type lists are really a
different beast than regular interfaces, and aren't even meaningful as
regular interfaces. (Trying to do that gives the error "interface type
for variable cannot contain type constraints", which is relatively
clear, at least.) As soon as an "interface" has a type list, it's not
really a Go interface anymore (and interfaces with *only* type lists
are not really interfaces at all, just type constraints). This seems
confusing, though I'm not sure what the solution is. Has this been
discussed elsewhere?

5) The ordered Map type shown in the draft design uses a "compare"
function. Won't that mean there's always the performance hit of
calling a function via pointer? Or will the compiler be smart enough
to inline that if you're passing in a function literal (seems
unlikely, as it probably doesn't know it won't change). Then again, I
guess it's only going to be a small performance hit, and is similar to
the "issue" with sort, which isn't actually an issue for most use
cases.

Note that of the above points, #1 and #2 are positives, and #3-5 are
issues. #3a and #3b seem minor or easily fixed, and #5 is probably not
an issue unless it's really performance-sensitive code. But #4 seems
to me like a fairly significant oddity / concern.

A more general question while I'm here: Ian or Robert mentioned on
that recent Go Time podcast something to the effect that people are
pushing generics to its limits. However, most of the examples in the
draft design, and most examples I've seen on the mailing list, are
pretty small examples of simple things. Is there a compilation of
larger or more real-world examples?

Thanks,
Ben

Ian Lance Taylor

unread,
Aug 3, 2020, 1:46:05 PM8/3/20
to Ben Hoyt, golang-nuts
On Mon, Aug 3, 2020 at 1:48 AM Ben Hoyt <ben...@gmail.com> wrote:
>
> I've played with generics a bit more, including the new square
> brackets syntax. A few notes (some of the threads have been long, so
> apologies if I'm doubling up):

Thanks for the extensive feedback.


> Related question: why does the current go2go playground allow "func
> Ptr[T](v T) *T { return &v }"? Though it turns [T] into [type T] when
> you click "Format". Is the former syntax without the "type" keyword
> (func Ptr[T]) going to be allowed, even if not encouraged?

That is an experiment. We don't seem to need the type keyword for
generic functions. But we do need it for generic types, to avoid
confusion with an array type (but only if there is exactly one type
parameter with no constraint). I'm not personally a big fan of
optional syntax, so we will probably make a decision one way or
another.



> (a) Needing "interface{}" (on the V type) just feels klunky. I kinda
> understand why -- if any of them have a constraint, all of them have
> to -- but I wonder if there's a way to avoid this. Maybe "_" instead
> of "interface{}"?

Another possibility is constraints.Any, although that is no shorter
than interface{}. I'm not sure _ is best; currently _ fairly
consistently means "ignore this value," but in this usage it would
mean something different.

Yet another possibility, going back to the syntax question above, is
requiring that a type parameter for a type alway have a constraint,
which would mean that we would never use the "type" keyword for type
parameters, and define a predeclared identifier "any = interface{}".


> (b) When I left off the "comparable" constraint the error message was
> a bit cryptic: "invalid map key type K". "But why?", was my immediate
> thought. Hopefully it'd be fairly easy to improve that error message.
> Maybe something like "type K can't be used as map key; need
> 'comparable' constraint?"

Thanks. Filed https://golang.org/issue/40551.


> 4) It seems strange to me that interfaces with type lists are really a
> different beast than regular interfaces, and aren't even meaningful as
> regular interfaces. (Trying to do that gives the error "interface type
> for variable cannot contain type constraints", which is relatively
> clear, at least.) As soon as an "interface" has a type list, it's not
> really a Go interface anymore (and interfaces with *only* type lists
> are not really interfaces at all, just type constraints). This seems
> confusing, though I'm not sure what the solution is. Has this been
> discussed elsewhere?

It's mentioned very briefly at
https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md#type-lists-in-interface-types
. I can't recall much discussion on this point. Perhaps any
discussion should go in a different thread, though.


> 5) The ordered Map type shown in the draft design uses a "compare"
> function. Won't that mean there's always the performance hit of
> calling a function via pointer? Or will the compiler be smart enough
> to inline that if you're passing in a function literal (seems
> unlikely, as it probably doesn't know it won't change). Then again, I
> guess it's only going to be a small performance hit, and is similar to
> the "issue" with sort, which isn't actually an issue for most use
> cases.

I really don't know what the performance effect of this approach will
be. I agree that inlining doesn't seem too likely here. But for more
performance sensitive uses we could perhaps require that the Key type
have a Compare method, in which case inlining would be more likely
under certain implementation techniques.


> A more general question while I'm here: Ian or Robert mentioned on
> that recent Go Time podcast something to the effect that people are
> pushing generics to its limits. However, most of the examples in the
> draft design, and most examples I've seen on the mailing list, are
> pretty small examples of simple things. Is there a compilation of
> larger or more real-world examples?

There have been several examples posted to this list, and of course
there is https://go.googlesource.com/go/+/refs/heads/dev.go2go/src/cmd/go2go/testdata/go2path/src
. But I can't recall seeing any really large real world examples.

To be clear, pushing generics to its limits is fine too.

Ian

Carla Pfaff

unread,
Aug 3, 2020, 5:13:31 PM8/3/20
to golang-nuts
On Monday, 3 August 2020 at 19:46:05 UTC+2 Ian Lance Taylor wrote:
Yet another possibility, going back to the syntax question above, is
requiring that a type parameter for a type alway have a constraint,
which would mean that we would never use the "type" keyword for type
parameters, and define a predeclared identifier "any = interface{}".

Thanks for considering this possibility. Why I like it:
  • Type parameter lists and function parameter lists have the same structure. Constraint is to type parameter as type is to function parameter.
  • Even more symmetry between declaration and instantiation than before: No "type" keyword at instantiation, no "type" keyword at declaration.
  • Type parameter lists do not suddenly take on a different shape when they are a mixture of constrained and unconstrained type parameters compared to fully unconstrained type parameter lists:
    • Without "type" keyword
      • [A, B any] and [A any, B comparable] // No surprise
    • With "type" keyword
      • [type A, B] and [type A interface{}, B comparable] // A wild interface{} appears!

Ben Hoyt

unread,
Aug 3, 2020, 6:34:12 PM8/3/20
to Ian Lance Taylor, golang-nuts
> That is an experiment. We don't seem to need the type keyword for
> generic functions. But we do need it for generic types, to avoid
> confusion with an array type (but only if there is exactly one type
> parameter with no constraint). I'm not personally a big fan of
> optional syntax, so we will probably make a decision one way or
> another.

Yeah, I agree -- I think having it optional for functions but not for
types is a bit confusing. I do like the fact that you don't need to
write "type" or list the constraints on the type in methods, though --
that would be really verbose.

> Another possibility is constraints.Any, although that is no shorter
> than interface{}. I'm not sure _ is best; currently _ fairly
> consistently means "ignore this value," but in this usage it would
> mean something different.

Yeah, I don't think 'import "constraints"' and then "constraints.Any"
is any (ahem) better. Maybe slightly more self-documenting.

> Yet another possibility, going back to the syntax question above, is
> requiring that a type parameter for a type alway have a constraint,
> which would mean that we would never use the "type" keyword for type
> parameters, and define a predeclared identifier "any = interface{}".

Interesting idea, I kinda like that. At first thought, when there's no
constraints I think "[type T]" is slightly clearer than "[T any]". But
then again, this latter syntax parallels normal types where you always
have to specify it, e.g., "v int", "x interface{}", etc.

I notice in one of the examples in the repo you define a short name:
"type any interface{}". Which at first seems like a good idea, but
then unless "any" is built in or this becomes a well-known idiom, it
won't be as self-documenting. Other people will have to look up the
definition to see "oh, I see, this is just short-hand for an empty
constraint".

> > (b) When I left off the "comparable" constraint the error message was
> > a bit cryptic: "invalid map key type K". "But why?", was my immediate
> > thought. Hopefully it'd be fairly easy to improve that error message.
> > Maybe something like "type K can't be used as map key; need
> > 'comparable' constraint?"
>
> Thanks. Filed https://golang.org/issue/40551.

Thanks!

> It's mentioned very briefly at
> https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md#type-lists-in-interface-types
> . I can't recall much discussion on this point. Perhaps any
> discussion should go in a different thread, though.

Thanks. I might start that as a separate thread, then, and see if
anyone has any thoughts.

> There have been several examples posted to this list, and of course
> there is https://go.googlesource.com/go/+/refs/heads/dev.go2go/src/cmd/go2go/testdata/go2path/src
> . But I can't recall seeing any really large real world examples.

Thanks. I actually didn't know about / had forgotten about this
repository of examples. I realize parts of many of them are in the
draft design, but it's nice to see them all in one place.

-Ben

Dan Kortschak

unread,
Aug 3, 2020, 8:08:06 PM8/3/20
to golan...@googlegroups.com
On Mon, 2020-08-03 at 10:45 -0700, Ian Lance Taylor wrote:
> Another possibility is constraints.Any, although that is no shorter
> than interface{}. I'm not sure _ is best; currently _ fairly
> consistently means "ignore this value," but in this usage it would
> mean something different.

Another possibility would be `...` which at least in the context of
arrays means "fill this in with the information you have elsewhere";
`var a [...]int{1,2,3,4}`. There is no other use of the ellipsis
operator in the type parameters, so this should work.

Dan


Carla Pfaff

unread,
Aug 4, 2020, 12:08:19 AM8/4/20
to golang-nuts
On Tuesday, 4 August 2020 at 00:34:12 UTC+2 ben...@gmail.com wrote:
Which at first seems like a good idea, but then unless "any" is built in or this becomes a well-known idiom, it won't be as self-documenting. Other people will have to look up the definition to see "oh, I see, this is just short-hand for an empty constraint".

I'm sure it would quickly become a well-known idiom, just like people know that "error" is "interface{Error() string}" or "fmt.Stringer" is "interface{String() string}".

Actually the current use of "interface{}" is a bit odd because it is the only case where an interface is commonly used as an anonymous type rather than by an identifier.

I assume that in current Go the empty interface is supposed to be an ugly duckling to discourage its overuse, but in a world with type parameters it will play an important role as the unbounded constraint and it should deserve its own identifier.

And I don't think that giving it a short name would encourage more use of the empty interface as a type for regular function parameters, because my estimate is that people are happy to use "generics" rather than the empty interface in function parameters when they can. Most Go programmers want to be type safe and avoid casting.

Haddock

unread,
Aug 4, 2020, 4:44:21 AM8/4/20
to golang-nuts
>> That is an experiment. We don't seem to need the type keyword for
>> generic functions. But we do need it for generic types,

I'm used for years to look at code with generics in Java. The additional type keyword as proposed for generics in Go in IMHO make reading the declaration containing generics much easier and much less effortful for the eye. You see the type keyword in some declaration and in one short look you have understood what it says without having to stare at it for a while.

ben...@gmail.com

unread,
Aug 4, 2020, 3:29:21 PM8/4/20
to golang-nuts
Which at first seems like a good idea, but then unless "any" is built in or this becomes a well-known idiom, it won't be as self-documenting. Other people will have to look up the definition to see "oh, I see, this is just short-hand for an empty constraint".

I'm sure it would quickly become a well-known idiom, just like people know that "error" is "interface{Error() string}" or "fmt.Stringer" is "interface{String() string}".

That's fair enough -- I think you're right.

Actually the current use of "interface{}" is a bit odd because it is the only case where an interface is commonly used as an anonymous type rather than by an identifier.

Interesting point. My brain usually thinks of "interface{}" as a special concept/syntax (and I suspect I'm not alone), even though I know it's not!

I assume that in current Go the empty interface is supposed to be an ugly duckling to discourage its overuse, but in a world with type parameters it will play an important role as the unbounded constraint and it should deserve its own identifier.

Very well stated, and I agree.

-Ben

Jan Mercl

unread,
Aug 5, 2020, 4:07:59 AM8/5/20
to Carla Pfaff, golang-nuts
On Tue, Aug 4, 2020 at 6:07 AM 'Carla Pfaff' via golang-nuts
<golan...@googlegroups.com> wrote:

> On Tuesday, 4 August 2020 at 00:34:12 UTC+2 ben...@gmail.com wrote:

> I'm sure it would quickly become a well-known idiom, just like people know that "error" is "interface{Error() string}" or "fmt.Stringer" is "interface{String() string}".

I'm sure some people will never write `any` instead of `interface{}`
and I can prove it ;-)

> Actually the current use of "interface{}" is a bit odd because it is the only case where an interface is commonly used as an anonymous type rather than by an identifier.

It's not the only place. It does not happen often, but interface type
literals other than interface{} appear in real code and for good
reasons. It's an overkill to name a thing that will be referenced by
name only once.

> I assume that in current Go the empty interface is supposed to be an ugly duckling to discourage its overuse, but in a world with type parameters it will play an important role as the unbounded constraint and it should deserve its own identifier.

It's not supposed to be anything special whatsoever. Just like number
zero or an empty set is not some kind of an exception. It's just you
cannot have any reasonable set theory without it.

BTW: I assume number 42 to be special, though not for math. Can I have
a nice Unicode point for it, so I can write it nicely all over my
example code?

> Most Go programmers want to be type safe and avoid casting.

Go programmers do avoid casting because Go has no casting. That word
or its stem doesn't even appear in the language specs.

Denis Cheremisov

unread,
Aug 5, 2020, 12:15:31 PM8/5/20
to golang-nuts
>  I think "[type T]" is slightly clearer than "[T any]".

Code with `[T any]` is much easier to read at least for me.

среда, 5 августа 2020 г. в 11:07:59 UTC+3, Jan Mercl:

Tyler Compton

unread,
Aug 5, 2020, 1:40:32 PM8/5/20
to Jan Mercl, Carla Pfaff, golang-nuts
On Wed, Aug 5, 2020 at 1:07 AM Jan Mercl <0xj...@gmail.com> wrote:
It's not supposed to be anything special whatsoever. Just like number
zero or an empty set is not some kind of an exception. It's just you
cannot have any reasonable set theory without it.

I think it's fair to say that `interface{}` is somewhat special in practice because it acts as an escape hatch from the type system when necessary. No other interface is capable of being satisfied by values of all types. I assume that the `error` interface is pre-declared because it's a very common interface used across almost all Go code. If `interface{}` is to become even more common than it is thanks to generics, I think it could be reasonable to follow the same thinking.

Go programmers do avoid casting because Go has no casting. That word
or its stem doesn't even appear in the language specs.

Something tells me you knew Carla was referring to type assertions.
 

Jan Mercl

unread,
Aug 5, 2020, 1:50:37 PM8/5/20
to Tyler Compton, Carla Pfaff, golang-nuts
Hard to say if cast was used instead of "conversion" or "type assertion" or anything else.

That's the problem with using terms the language specs avoid for a reason.

Perhaps that's was the actual message?

jimmy frasche

unread,
Aug 5, 2020, 2:00:59 PM8/5/20
to Tyler Compton, Jan Mercl, Carla Pfaff, golang-nuts
Most of the code I'd write with generics would take a func or
something satisfying an interface to set the operations if necessary.
I'd use type lists as a handy way for the user to default those to
operators. Interface literals and lambdas would be just as useful
here. I probably wouldn't bother writing a min that takes a comparator
but not having min/max has never bothered me as much as not having
things like Tree and Queue.

Type lists do seem like a really good idea but one that isn't quite
fully polished yet. If it's left out of version 1 of generics and
everything else is implemented as is, that would still be incredibly
useful. Even if that only covers 90% of the problem that's still a
lot.

Also +1 on requiring constraints and predeclaring any.
> --
> 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/CAA%3DXfu0c-BqTt6vOjbE9ezur0nNNFCSBhgs%3DPFrEUBPASziinw%40mail.gmail.com.
Reply all
Reply to author
Forward
0 new messages