"Interfaces" with type lists are a strange beast

523 views
Skip to first unread message

Ben Hoyt

unread,
Aug 3, 2020, 7:01:44 PM8/3/20
to golang-nuts
Per Ian's suggestion on the other thread I started
(https://groups.google.com/g/golang-nuts/c/u9jqLPhEYO0/m/tnqezci8AwAJ),
I'm breaking out this topic into a separate thread:

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.

Ian noted that this is mentioned very briefly at
https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md#type-lists-in-interface-types
but that he can't recall much discussion on this point.

That section in the draft design notes starkly that "They may not be
used as ordinary interface types." And then:

"This restriction may be lifted in future language versions. An
interface type with a type list may be useful as a form of sum type,
albeit one that can have the value nil. Some alternative syntax would
likely be required to match on identical types rather than on
underlying types; perhaps type ==. For now, this is not permitted."

That seems a little far-fetched to me, almost like a justification of
what we know to be odd / confusing in this proposal.

In terms of a "solution" for this, one that I'm sure has been thought
about: what about keeping type constraints and interfaces completely
separate? They are half the time anyway (when there are type lists),
so why not make them separate all the time.

I realize this may be heading back towards contracts (though without
the funky syntax for operators). I think the "Featherweight Go" paper
says "we don't need two different concepts here", but the paper
doesn't seem to discuss "type lists" at all (and type lists seem very
important to this proposal, and definitely are to this immediate
discussion).

Anyway, instead of saying "interface" you'd say "constraint":

// this isn't really an interface, so don't call it one:
type SignedInteger constraint {
type int, int8, int16, int32, int64
}

// this also can't be used as in interface, so don't call it one
type ComparableHasher constraint {
comparable
Hash() uintptr
}

// yes, this duplicates the Stringer interface for use as a type constraint
type Stringable constraint {
String() string
}

I realize in the design draft the "Stringer" interface is used as a
type constraint heavily, but that seems like a bit of an example/toy.
In real-world code, how likely is it that we'll be using lots of
existing interfaces as constraints? To me it seems like that won't be
common, but I don't have much to go on.

-Ben

Axel Wagner

unread,
Aug 3, 2020, 8:10:37 PM8/3/20
to Ben Hoyt, golang-nuts
On Tue, Aug 4, 2020 at 1:01 AM Ben Hoyt <ben...@gmail.com> wrote:
In terms of a "solution" for this, one that I'm sure has been thought
about: what about keeping type constraints and interfaces completely
separate? They are half the time anyway (when there are type lists),
so why not make them separate all the time.

They used to be. That's essentially what contracts used to be in the previous iteration of the design.
Of course they also used a completely different syntax and way to define the constraints, but this separation was one of the major changes between the original contracts design and this new draft. At the time I (and others) have argued against this separation:

I'm still strongly in favor of not having it. In particular, there is a large section of conceptual overlap between interfaces and constrained type-parameters. And at least as far as function parameters go, an interface parameter can always be re-written into a constrained type-parameter. At least as long as we live in this overlap, it seems very confusing to have two separate names for what is very much the same thing - specifying a subset of valid types to use. It also has technological downsides, because if you already have an `io.Reader` interface, you also need to have an equivalent contract (or whatever we call this constraint kind) and vice-versa, if a concept is more naturally translated one way or another.

So, just as it can be argued that certain kinds of constraints are very different from interfaces, so it's confusing to conflate them, it can also be argued that many (if not most) constraints are exactly the same thing as interfaces, so it's confusing to separate them. AIUI, the evolution of the design then basically progressed from "so maybe we should allow interfaces as constraints as well as contracts, to simplify things" to "it turns out we don't really need contracts as a separate concept after all, so it is cleaner to leave them out".

IMO the discussion about this is purely based on the idea of type-lists which exists purely so as to support operators on type-parameters. And personally I feel a better way to solve this solution would be to just drop that from the design. IMO, the utility of this isn't really worth the cost. Especially given that there's no way to write a function to work with *either* methods *or* operators, so the actual practical utility is pretty small. Arguably you need two copies of any function working with - one for "primitel types" using operators and one for "composite types" using methods. That seems… inelegant to say the least and I'm not convinced that it's better than to require people to add a method to "primitive types" to use them in generic code. And IMO a more orthogonal solution to allowing use with "primitive types" would be some form of operator overloading or endowing predeclared types with suitable methods - either would allow us to actually write generic code that could work with both primitive and composite types, without needing us to introduce a separate way to specify constraints.

That being said, I know that the people driving the design consider being able to write generic code using operators non-optional, so I know that this likely won't happen. But I do think it's important to allow using interfaces as constraints and I do think that any benefit of separating the concepts would probably vanish, as long as you allow that - being able to have both seems to me the prototypical compromise, in that it combines the downside of both ideas. So, personally, I still strongly believe that re-using interfaces is the right design.

I realize in the design draft the "Stringer" interface is used as a
type constraint heavily, but that seems like a bit of an example/toy. 
In real-world code, how likely is it that we'll be using lots of
existing interfaces as constraints?

I think that's very likely. So far, in the 6 weeks I've been thinking about how I'd use generics in Go, I have not considered a single use-case that uses operators - and they all either used no constraints at all or pre-existing interfaces or `comparable` (but even there, only for use as map-keys, not for the actual comparison operator).

I also don't think "pre-existing" is the right lense. More interesting is: How many constraints would also be useful as types? And I'd argue that probably every constraint that is implemented mostly by composite types (for example any abstract data structure, which is a huge chunk of use-cases for generics) would also be useful as a type in its own right (and obviously can't be used with operators then).
 
If you want a constraint to also be useable as a type, you either end up
a) having to write down two declarations which are identical, except one says "interface" and one "constraint" - which seems confusing, because if the declarations are identical, why do you both of them? Or
b) making it possible to constrain type-parameters with interfaces as well. In which case you have this entirely separate concept for something that ends up being sort of an edge-case, as in the general case (i.e. no type-lists), interfaces would be strictly more useful.

PS: Just for posterity, I do think this has been discussed at least a little bit:
(maybe others, this is just a very cursory search)

To me it seems like that won't be
common, but I don't have much to go on.

-Ben

--
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/CAL9jXCHa5%2B4LreE7acP_r3QEBMGKN6qzgzkLFv4VXsW5aoXfcw%40mail.gmail.com.

Ben Hoyt

unread,
Aug 3, 2020, 8:45:35 PM8/3/20
to Axel Wagner, golang-nuts
Thanks for the thoughtful response.

> I'm still strongly in favor of not having it. In particular, there is a large section of conceptual overlap between interfaces and constrained type-parameters. And at least as far as function parameters go, an interface parameter can always be re-written into a constrained type-parameter. At least as long as we live in this overlap, it seems very confusing to have two separate names for what is very much the same thing - specifying a subset of valid types to use. It also has technological downsides, because if you already have an `io.Reader` interface, you also need to have an equivalent contract (or whatever we call this constraint kind) and vice-versa, if a concept is more naturally translated one way or another.

You make some very good points. I'm inclined to agree (leaving type
lists aside for now).

> IMO the discussion about this is purely based on the idea of type-lists which exists purely so as to support operators on type-parameters. And personally I feel a better way to solve this solution would be to just drop that from the design. IMO, the utility of this isn't really worth the cost. Especially given that there's no way to write a function to work with *either* methods *or* operators, so the actual practical utility is pretty small. Arguably you need two copies of any function working with - one for "primitel types" using operators and one for "composite types" using methods. That seems… inelegant to say the least and I'm not convinced that it's better than to require people to add a method to "primitive types" to use them in generic code.

I also really don't like the fact you'll need two different versions
in a lot of cases (eg: Min for builtin types, and Min for complex
types). Given this, I think it's fair enough to want to drop type
lists from the design.

> And IMO a more orthogonal solution to allowing use with "primitive types" would be some form of operator overloading or endowing predeclared types with suitable methods - either would allow us to actually write generic code that could work with both primitive and composite types, without needing us to introduce a separate way to specify constraints.

Ian has said that adding operator overloading would be a "big change
to the language", which is true. But so is adding generics, so we
could try to do it "while we're at it", as it seems like these two
features (using interfaces as type constraints, and operator
overloading via methods/interfaces) would work really well together.

Alternatively, like you suggest, we could just drop type lists and
punt that problem for now. (I realize it doesn't allow using operators
in generic functions.) It seems much better to go with a restricted
feature set than to go with this whole new thing (type lists) that
just doesn't fit in very well. Go often punts useful features that
don't seem to fit, like handle {} for errors, or try(). We could
always add them in later once people have more experience with "basic
generics", or take a different track like operator overloading, or
maybe something completely different.

> I think that's very likely. So far, in the 6 weeks I've been thinking about how I'd use generics in Go, I have not considered a single use-case that uses operators - and they all either used no constraints at all or pre-existing interfaces or `comparable` (but even there, only for use as map-keys, not for the actual comparison operator).

Yeah, if we dropped type lists, "comparable" is the one constraint I
think we'd still need to be useful, because it enables you to use type
constraints for map keys, as well as for the likes of a slices.Equal()
function. So what about simplifying the proposal to only allow
interface constraints or the single built-in "comparable" constraint?

At least in my usage, being able to write type-safe generic containers
and channel functions would be a great start, and more important than
being able to write a Min() or an Add() function that works across
various built-in types.
Thanks! Appreciate those links. I had seen Brent Mills'
"Orthogonalizing Type Lists" a while back, but I'll read it again.

-Ben

Randall O'Reilly

unread,
Aug 3, 2020, 9:57:20 PM8/3/20
to Ben Hoyt, golang-nuts
FWIW, the "generic types" alternative syntax proposal: https://github.com/golang/go/issues/39669 includes an explicit distinction between generic interfaces and regular interfaces, and an earlier iteration of this idea included a new keyword, e.g., `prototype`, that would signal this distinction more clearly. The current proposal instead just states that a generic interface must include a type list, and if there are no constraints, it is just a "fully generic" type list: `type type`, to signal that it is a generic interface.

In any case I do think that, conceptually, for the user's benefit and mental model of what is going on, it would be useful overall to have a clear distinction between generic and non-generic types, while also preserving the shared aspects as much as possible. Interfaces *are* a form of generic-ness after all.

One additional idea would be that generic interfaces can embed regular interfaces, as a way to share code while also maintaining the clear conceptual distinction:

type GenericStringer interface {
Stringer // include standard Stringer interface
type type // makes it a generic interface
}

If a separate keyword such as `prototype` were used to further distinguish from regular interfaces, it would be simpler and clearer:

type GenericStringer prototype {
Stringer // prototypes can embed interfaces, but not the other way around
}

- Randy

Ian Lance Taylor

unread,
Aug 4, 2020, 9:31:33 PM8/4/20
to Axel Wagner, Ben Hoyt, golang-nuts
On Mon, Aug 3, 2020 at 5:10 PM 'Axel Wagner' via golang-nuts
<golan...@googlegroups.com> wrote:
>
> IMO the discussion about this is purely based on the idea of type-lists which exists purely so as to support operators on type-parameters. And personally I feel a better way to solve this solution would be to just drop that from the design. IMO, the utility of this isn't really worth the cost. Especially given that there's no way to write a function to work with *either* methods *or* operators, so the actual practical utility is pretty small. Arguably you need two copies of any function working with - one for "primitel types" using operators and one for "composite types" using methods. That seems… inelegant to say the least and I'm not convinced that it's better than to require people to add a method to "primitive types" to use them in generic code. And IMO a more orthogonal solution to allowing use with "primitive types" would be some form of operator overloading or endowing predeclared types with suitable methods - either would allow us to actually write generic code that could work with both primitive and composite types, without needing us to introduce a separate way to specify constraints.
>
> That being said, I know that the people driving the design consider being able to write generic code using operators non-optional, so I know that this likely won't happen. But I do think it's important to allow using interfaces as constraints and I do think that any benefit of separating the concepts would probably vanish, as long as you allow that - being able to have both seems to me the prototypical compromise, in that it combines the downside of both ideas. So, personally, I still strongly believe that re-using interfaces is the right design.

Yes, I think that the ability to write generics using operators is not
optional. As I've said before, there are some functions that must be
possible to write using any plausible generics mechanism. One of them
is the Min function that returns the smaller of two values of some
ordered type. If we can't write that trivial function, our generics
mechanism is not usable for practical code.


Defining operators for every operation that applies to a builtin type
in Go is surprisingly hard. I think we wind up needing something like
40 different methods. That is not a simple approach.


I agree that it would be inelegant to have to write every function
twice, once with operators and once with methods. But it's not yet
entirely clear to me that that is what will happen. I suspect that in
Go it will often be more natural to pass in an appropriate function
rather than to require a method. Admittedly sort.Interface works
differently, but that is because it additionally requires Swap and Len
methods that are not required in generic code. A generic sort in Go
seems more likely to use a comparison function than to require a
method, and similarly for other typical examples. It would be
interesting to hear about counter-examples where methods are more
appropriate.

Anyhow, if I'm right, that reduces the problem to: is there a simple
way to get an appropriate function for a primitive type. And, of
course, there is:

func Less(type T constraints.Ordered)(a, b T) bool { return a < b }

Now I can call sort with primitives.Less(int32) or whatever. And I
think we are going to be able to define type inference such that in
common cases it will work to pass simply primitives.Less.

And of course for those who really like methods we could even have

package methods
type lesser(type T) interface { Less(T) bool }
func Less(type T lesser)(a, b T) bool { return a.Less(b) }

and now you pass methods.Less to the function in question.

Ian

Ben Hoyt

unread,
Aug 4, 2020, 10:32:02 PM8/4/20
to Ian Lance Taylor, Axel Wagner, golang-nuts
> Yes, I think that the ability to write generics using operators is not
> optional. As I've said before, there are some functions that must be
> possible to write using any plausible generics mechanism. One of them
> is the Min function that returns the smaller of two values of some
> ordered type. If we can't write that trivial function, our generics
> mechanism is not usable for practical code.

I don't think that's quite true. You can write it, it's just not as
nice to use with builtin types:

func Min[type T Lesser](a, b T) T {
if a.Less(b) {
return a
}
return b
}

To use it with (for example) int, you need to write a wrapper type, for example:

type Int int
func (i Int) Less(j Int) bool { return i < j }

And then call it like so:

Min(Int(2), Int(3))

It's not pretty, but it definitely works
(https://go2goplay.golang.org/p/i6Q3tF-QwgH). This was kind of the
situation with sort.Sort (until sort.Slice came along), so it doesn't
seem any worse than that. Or you could instead define a generic type
"Ord" instead of Int:

type Ord[type T Ordered] T
func (i Ord[T]) Less(j Ord[T]) bool { return i < j }

And use it like so:

Min(Ord[int](2), Ord[int](3))

Or if type inference were improved to handle that case:

Min(Ord(2), Ord(3))

It's not pretty, partly because you get an Int/Ord result instead of a
plain int, but not terrible either.

So it seems to me it's not a case of "we can't write that trivial
function" -- it just requires a bit of type conversion. So either way
-- with your approach or this approach -- it's klunky for one of the
cases:

1) Assuming type lists and a function which takes a comparator func
like "func Min[type T](a, b T, less func(x, y T) bool) T" -- in this
case you have the klunkiness of having to import (or define)
"primitives" and specify the comparison func even when it's obvious:
Min(2, 4, primitives.Less)
2) With no type lists and a function which takes a "lesser" like "func
Min[type T Lesser](a, b T) T" -- in this case you have the klunkiness
of having to use wrapper types, like we did with sort.Sort back in the
day.

> Defining operators for every operation that applies to a builtin type
> in Go is surprisingly hard. I think we wind up needing something like
> 40 different methods. That is not a simple approach.

Perhaps, but presumably a lot of them wouldn't be common, and we could
start with just 2 or 3 or 4. It seems like "comparable" is essential
(even in the current design with type lists), but even just adding
"lesser" or similar on top of that would go a long way: sorting,
sorted containers, min, etc. Then again, I guess as soon as you let
one or two in, people will clamour for them all...

> package methods
> type lesser(type T) interface { Less(T) bool }
> func Less(type T lesser)(a, b T) bool { return a.Less(b) }
>
> and now you pass methods.Less to the function in question.

That's a neat trick.

-Ben

Ian Lance Taylor

unread,
Aug 5, 2020, 12:06:33 AM8/5/20
to Ben Hoyt, Axel Wagner, golang-nuts
OK, fair enough.

I'll be more specific: I think it's necessary that it be possible to
write a Min function whose body is something similar to

if a < b {
return a
}
return b

I think that any approach to generics that requires more complex code
would simply be rejected by the community of Go programmers.


> So either way
> -- with your approach or this approach -- it's klunky for one of the
> cases:
>
> 1) Assuming type lists and a function which takes a comparator func
> like "func Min[type T](a, b T, less func(x, y T) bool) T" -- in this
> case you have the klunkiness of having to import (or define)
> "primitives" and specify the comparison func even when it's obvious:
> Min(2, 4, primitives.Less)
> 2) With no type lists and a function which takes a "lesser" like "func
> Min[type T Lesser](a, b T) T" -- in this case you have the klunkiness
> of having to use wrapper types, like we did with sort.Sort back in the
> day.

To be clear, when I discussed a function, I was only discussing that
in contrast to a type with a required method. I did not mean to
suggest that a function would be suitable for use with a primitive
type that supports operators.

Ian

Robert Engels

unread,
Aug 5, 2020, 12:19:59 AM8/5/20
to Ian Lance Taylor, Ben Hoyt, Axel Wagner, golang-nuts
The operator support is what makes things so complicated. Why not define a “numeric” type that has methods for all the operators that is used with generics, similar to Number in Java. Then it is unified. If you are writing specialized math performant code you would probably use concrete types anyway.

People complained about the boxing of primitives in Java but it was rarely a problem in actual use - and then people fell back to specialized structures anyway - which is what would happen here.

> On Aug 4, 2020, at 11:06 PM, Ian Lance Taylor <ia...@golang.org> 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/CAOyqgcWqoACJp97ZZhKecNVg-OdxVoUxNX_kZusp2-xmZRwnmw%40mail.gmail.com.

Ian Lance Taylor

unread,
Aug 5, 2020, 5:31:03 PM8/5/20
to Robert Engels, Ben Hoyt, Axel Wagner, golang-nuts
On Tue, Aug 4, 2020 at 9:19 PM Robert Engels <ren...@ix.netcom.com> wrote:
>
> The operator support is what makes things so complicated. Why not define a “numeric” type that has methods for all the operators that is used with generics, similar to Number in Java. Then it is unified. If you are writing specialized math performant code you would probably use concrete types anyway.
>
> People complained about the boxing of primitives in Java but it was rarely a problem in actual use - and then people fell back to specialized structures anyway - which is what would happen here.

Numbers are not the only primitive types in Go.

Ian

Robert Engels

unread,
Aug 5, 2020, 9:28:47 PM8/5/20
to Ian Lance Taylor, Ben Hoyt, Axel Wagner, golang-nuts
True, but cant Comparable be implemented by all built types? And Number be implemented by numeric types? Sure, you can’t use the operators in generic code but given most generic code is collections based it seems not a big loss.

> On Aug 5, 2020, at 4:30 PM, Ian Lance Taylor <ia...@golang.org> wrote:

Ian Lance Taylor

unread,
Aug 5, 2020, 11:05:58 PM8/5/20
to Robert Engels, Ben Hoyt, Axel Wagner, golang-nuts
On Wed, Aug 5, 2020 at 6:28 PM Robert Engels <ren...@ix.netcom.com> wrote:
>
> True, but cant Comparable be implemented by all built types? And Number be implemented by numeric types? Sure, you can’t use the operators in generic code but given most generic code is collections based it seems not a big loss.

The more you look into the details the more complex it becomes. For
example, integer types support & and | but floating point types do
not. Integer and floating point types support < but complex types do
not. String types support + but not -. We have to choose between
reflecting the language as it is, which requires a lot of new names
all of which must be learned by every Go programmer, or leaving out
the capability to write certain kinds of generic functions, which is
OK for many people but frustrating for some.

Or we can use type lists which doesn't add any new names and is sure
to work for all cases, not only today but for any new primitive types.

Ian

Robert Engels

unread,
Aug 5, 2020, 11:53:20 PM8/5/20
to Ian Lance Taylor, Ben Hoyt, Axel Wagner, golang-nuts
I understand your point, but I think a few minor corrections Make a difference - it does not matter that String supports + and not - , a string would not be a Number. String concatenation is not addition.

There are also several ways to implement “ordering” with complex numbers, even between complex and rational - it’s all a matter of definition. There is also the possibility to make complex not a Comparable (compile time failure).

You write the generic code using methods not operators in all cases.

How you define the methods gets tricky - no default casting in Go - but I think you have that problem regardless.

It may be limiting in some cases but all of the common generic code is pretty simple - mainly collections (with predicate filters).

You just don’t see a lot of code where the generic type is a number or a string and the code does mathematical ops on the type.

> On Aug 5, 2020, at 10:05 PM, Ian Lance Taylor <ia...@golang.org> wrote:

Ian Lance Taylor

unread,
Aug 6, 2020, 2:53:55 PM8/6/20
to Robert Engels, Ben Hoyt, Axel Wagner, golang-nuts
On Wed, Aug 5, 2020 at 8:52 PM Robert Engels <ren...@ix.netcom.com> wrote:
>
> I understand your point, but I think a few minor corrections Make a difference - it does not matter that String supports + and not - , a string would not be a Number. String concatenation is not addition.

My point wasn't that a string is a number. My point was that the
current design draft permits writing a function that uses + and works
with both strings and numbers. If we adopt something along the lines
of what you are suggesting, we must either define a name for "types
that support +" or we must say "you can't write a generic function
that uses + and works with both strings and numbers."


> There are also several ways to implement “ordering” with complex numbers, even between complex and rational - it’s all a matter of definition. There is also the possibility to make complex not a Comparable (compile time failure).

In Go, the types complex64 and complex128 do not support the < <= >= >
operators. That is what I mean when I say that the complex types are
not ordered. I'm not sure it matters that it is possible to define
some ordering on complex numbers; the point is that the language
defines no such ordering, so if you need to use ordering operators you
can't use complex types.


> You write the generic code using methods not operators in all cases.

Ah, I didn't understand that. I think that is a non-starter. I think
it is a requirement that people be able to write (and read) Min as

if a < b {
return a
}
return b

Saying that you must write this as, e.g.,

if a.Less(b) {
return a
}
return b

means that the generic language is not the normal language. That adds
a massive layer of complexity to using generics: you can no longer
write ordinary Go code for generic functions, you have to write in
this alternative language that is harder to write and harder to read.
You also have to remember a bunch of names for the methods that
correspond to the operators. The design draft works very hard to
avoid these issues.

In particular, I think that making that requirement would be adding
much more complexity to the language than we get by adding type lists.

Ian

Robert Engels

unread,
Aug 6, 2020, 3:11:08 PM8/6/20
to Ian Lance Taylor, Ben Hoyt, Axel Wagner, golang-nuts
We’ll probably agree to disagree there. Java has a lot of generic code written and it’s never been a problem (using methods). Rarely can you write code that treats + the same no matter if passed a string or numeric.

Even operators like < with strings don’t really make a lot of sense because different collations are used.

I think having a higher bar for Go generic implementations is fine - writing generic code properly is harder than regular Go - there’s much more to resin about.

> On Aug 6, 2020, at 1:53 PM, Ian Lance Taylor <ia...@golang.org> wrote:

Axel Wagner

unread,
Aug 6, 2020, 3:11:53 PM8/6/20
to Ian Lance Taylor, Robert Engels, Ben Hoyt, golang-nuts
On Thu, Aug 6, 2020 at 8:53 PM Ian Lance Taylor <ia...@golang.org> wrote:
My point wasn't that a string is a number.  My point was that the
current design draft permits writing a function that uses + and works
with both strings and numbers. 

Is there a need for that? I can't really imagine one.

That being said, in general I agree with you that type-lists allow you to be more deliberate about the semantics of the operators.
I would actually come from the other direction and say that both `string` and `int` have `+`, but both have clearly different meanings. And even `uint`, `int` and `float64` have subtle differences in how `+` works (for example: `float64` can have `a+b==a && b != 0`).
So, given that `+` has different meanings for different types, listing them explicitly gives more control over the behavior you intend.

Ian Lance Taylor

unread,
Aug 6, 2020, 3:17:45 PM8/6/20
to Robert Engels, Ben Hoyt, Axel Wagner, golang-nuts
On Thu, Aug 6, 2020 at 12:10 PM Robert Engels <ren...@ix.netcom.com> wrote:
>
> We’ll probably agree to disagree there. Java has a lot of generic code written and it’s never been a problem (using methods). Rarely can you write code that treats + the same no matter if passed a string or numeric.
>
> Even operators like < with strings don’t really make a lot of sense because different collations are used.
>
> I think having a higher bar for Go generic implementations is fine - writing generic code properly is harder than regular Go - there’s much more to resin about.

I hope that is not the case.

Also, it's important that it be easy to read generic code. We can put
extra burdens on writers of generic code if necessary, but we must
make the burden on readers of generic code as small as we possibly
can.

And again: is the complexity from requiring methods rather than
operators really less than the complexity of using type lists?

Ian

Ian Lance Taylor

unread,
Aug 6, 2020, 3:18:52 PM8/6/20
to Axel Wagner, Robert Engels, Ben Hoyt, golang-nuts
On Thu, Aug 6, 2020 at 12:11 PM Axel Wagner
<axel.wa...@googlemail.com> wrote:
>
> On Thu, Aug 6, 2020 at 8:53 PM Ian Lance Taylor <ia...@golang.org> wrote:
>>
>> My point wasn't that a string is a number. My point was that the
>> current design draft permits writing a function that uses + and works
>> with both strings and numbers.
>
>
> Is there a need for that? I can't really imagine one.

Probably not. But every exception is in itself a piece of complexity
that people have to learn. As a general guideline, the fewer
exceptions, the better.


> That being said, in general I agree with you that type-lists allow you to be more deliberate about the semantics of the operators.
> I would actually come from the other direction and say that both `string` and `int` have `+`, but both have clearly different meanings. And even `uint`, `int` and `float64` have subtle differences in how `+` works (for example: `float64` can have `a+b==a && b != 0`).
> So, given that `+` has different meanings for different types, listing them explicitly gives more control over the behavior you intend.

Makes sense. Thanks.

Ian

burak serdar

unread,
Aug 6, 2020, 3:45:39 PM8/6/20
to Ian Lance Taylor, Robert Engels, Ben Hoyt, Axel Wagner, golang-nuts
On Thu, Aug 6, 2020 at 1:17 PM Ian Lance Taylor <ia...@golang.org> wrote:
>
> On Thu, Aug 6, 2020 at 12:10 PM Robert Engels <ren...@ix.netcom.com> wrote:
> >
> > We’ll probably agree to disagree there. Java has a lot of generic code written and it’s never been a problem (using methods). Rarely can you write code that treats + the same no matter if passed a string or numeric.
> >
> > Even operators like < with strings don’t really make a lot of sense because different collations are used.
> >
> > I think having a higher bar for Go generic implementations is fine - writing generic code properly is harder than regular Go - there’s much more to resin about.
>
> I hope that is not the case.
>
> Also, it's important that it be easy to read generic code. We can put
> extra burdens on writers of generic code if necessary, but we must
> make the burden on readers of generic code as small as we possibly
> can.
>
> And again: is the complexity from requiring methods rather than
> operators really less than the complexity of using type lists?

There are things you can do with type lists that you cannot do with
operators. You can limit a function to run on unsigned numbers, for
instance. I think it also makes it explicit to the reader that
a.Add(b) is possibly more complicated than a+b.
> --
> 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/CAOyqgcWuPNgz%2B1Yz71_xpq6sHEw77EXYhcmSFwQAwE7iZhV5bw%40mail.gmail.com.

Robert Engels

unread,
Aug 7, 2020, 9:54:54 PM8/7/20
to burak serdar, Ian Lance Taylor, Ben Hoyt, Axel Wagner, golang-nuts
I’d really like to see an example of generic code that takes both string and numeric types that uses operators. Sorting/searching is one but as I already said the built in string operators are not sufficient for collation cases.

Even generic code that “only works on unsigned types”.

More than 90% of all generic code is collections. Operators are not needed for these.

> On Aug 6, 2020, at 2:45 PM, burak serdar <bse...@computer.org> wrote:

burak serdar

unread,
Aug 7, 2020, 10:07:15 PM8/7/20
to Robert Engels, Ian Lance Taylor, Ben Hoyt, Axel Wagner, golang-nuts
On Fri, Aug 7, 2020 at 7:54 PM Robert Engels <ren...@ix.netcom.com> wrote:
>
> I’d really like to see an example of generic code that takes both string and numeric types that uses operators. Sorting/searching is one but as I already said the built in string operators are not sufficient for collation cases.
>
> Even generic code that “only works on unsigned types”.
>
> More than 90% of all generic code is collections. Operators are not needed for these.

More than 90% of all generic *Java* code is for collections, because
the way generics are implemented it is almost impossible to use them
for anything else in Java.

Ian Lance Taylor

unread,
Aug 8, 2020, 12:17:06 PM8/8/20
to Robert Engels, burak serdar, Ben Hoyt, Axel Wagner, golang-nuts
On Fri, Aug 7, 2020 at 6:54 PM Robert Engels <ren...@ix.netcom.com> wrote:
>
> I’d really like to see an example of generic code that takes both string and numeric types that uses operators. Sorting/searching is one but as I already said the built in string operators are not sufficient for collation cases.
>
> Even generic code that “only works on unsigned types”.
>
> More than 90% of all generic code is collections. Operators are not needed for these.

I don't think I have anything useful to add to what I've said already
on this topic.

I believe that being able to write a Min function in ordinary Go is an
absolute requirement for generics in Go. Full stop.

It would be great to hear about any fatal problems that type lists
have. It would be great to hear about alternative approaches that
support operators. I don't think it's useful to debate whether we
need to be able to use operators in generic code.

Robert Engels

unread,
Aug 8, 2020, 12:43:57 PM8/8/20
to Ian Lance Taylor, burak serdar, Ben Hoyt, Axel Wagner, golang-nuts
Understood. Even if you keep operators they could be mapped to certain built in interface methods. C++ has operator loading, Java does not (except for auto-boxing) It seems Go generics are trying to play in the middle and I think the end result is going to lead to confusing code, but we shall see.

> On Aug 8, 2020, at 11:16 AM, Ian Lance Taylor <ia...@golang.org> wrote:
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CAOyqgcWoeG%3DC68c-kr10ED7u-jFasx14vhzhuwCOmpN-uNWuTw%40mail.gmail.com.

robert engels

unread,
Aug 9, 2020, 11:54:38 AM8/9/20
to Ian Lance Taylor, burak serdar, Ben Hoyt, Axel Wagner, golang-nuts
On second thought, that would lead to ‘operator overloading’ and the abuses it invites - so oh well - I guess we write duplicate methods based on types - which is pretty much what you can do now - write a base implementation using interface{} and then a small wrapper struct that types it. Given that, based on the current proposal, I go back to the position that Go doesn’t need generics.

李晓辉

unread,
Aug 12, 2020, 11:43:51 AM8/12/20
to golang-nuts
Maybe `type list` equals to `sum type`

type SignedInteger interface {
       type int, int8, int16, int32, int64
}

can replaced by 

type SignedInteger int|int8|int16|int32|int64


func max[type I SignedInteger](a I, b I) I {
       if a > b { return a }
       return b
}


roger peppe

unread,
Aug 13, 2020, 6:04:55 AM8/13/20
to 李晓辉, golang-nuts
On Wed, 12 Aug 2020 at 16:43, 李晓辉 <lixiao...@gmail.com> wrote:
Maybe `type list` equals to `sum type`

type SignedInteger interface {
       type int, int8, int16, int32, int64
}

can replaced by 

type SignedInteger int|int8|int16|int32|int64

Yes, I've had that thought too (and mentioned it as feedback), but there's one wrinkle: what about the "underlying type" behaviour of generics?
That is, although matching on the underlying type instead of the actual type is useful for type parameters, it's less intuitive and/or useful
when used as a normal type. I think I'd be surprised if a value of type "int|uint" was not exactly one of those two types.

For the record, I've made a somewhat detailed suggestion for sum types along these lines here.

  cheers,
    rog.

Peter McKenzie

unread,
Aug 13, 2020, 6:50:34 PM8/13/20
to golang-nuts


On Sunday, August 9, 2020 at 4:17:06 AM UTC+12, Ian Lance Taylor wrote:
On Fri, Aug 7, 2020 at 6:54 PM Robert Engels <ren...@ix.netcom.com> wrote:
>
> I’d really like to see an example of generic code that takes both string and numeric types that uses operators. Sorting/searching is one but as I already said the built in string operators are not sufficient for collation cases.
>
> Even generic code that “only works on unsigned types”.
>
> More than 90% of all generic code is collections. Operators are not needed for these.

I don't think I have anything useful to add to what I've said already
on this topic.

I believe that being able to write a Min function in ordinary Go is an
absolute requirement for generics in Go.  Full stop.

I think that's a sound requirement.  I suspect you're meaning a Min function that just handles the builtin integer types, but even one that handles floats as well is quite nice: https://go2goplay.golang.org/p/j9AYrSzrpE7

It would be very nice to see that type switch on T that you mention in the draft. (For one thing, it should allow replacing the bug prone "default" with "case float32, float64:" in my example.)

Overall I think this latest draft is a big step forward. The [] syntax seems nicer too. Thanks for sticking with it.

 
> >> To unsubscribe from this group and stop receiving emails from it, send an email to golan...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages