Updated generics design

1,662 views
Skip to first unread message

Ian Lance Taylor

unread,
Jul 27, 2019, 9:44:53 AM7/27/19
to golang-dev
The updated generics design that I mentioned in my talk at Gophercon
(live blog at https://about.sourcegraph.com/go/gophercon-2019-generics-in-go)
can now be seen at
https://go.googlesource.com/proposal/+/master/design/go2draft-contracts.md
.

Ian

Andrei Tudor Călin

unread,
Jul 27, 2019, 10:15:37 AM7/27/19
to Ian Lance Taylor, golang-dev
Very exciting!

Looking at the DotProduct example, I was wondering if something along
the lines of https://play.golang.org/p/cSmLAZ63dgA was considered.

https://gist.github.com/rogpeppe/0a87ef702189689201ef1d4a170939ac is
the only somewhat related work I could find on the topic.

Thanks.

--
You received this message because you are subscribed to the Google Groups "golang-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-dev/CAOyqgcUOsyW9ozzvSOcXi8T0ajgxQF0Dcx6m7aJGLHofQ8Eetg%40mail.gmail.com.


--
Andrei Călin

Ian Lance Taylor

unread,
Jul 27, 2019, 10:50:24 AM7/27/19
to Andrei Tudor Călin, golang-dev
On Sat, Jul 27, 2019 at 7:15 AM Andrei Tudor Călin <ma...@acln.ro> wrote:
>
> Very exciting!
>
> Looking at the DotProduct example, I was wondering if something along
> the lines of https://play.golang.org/p/cSmLAZ63dgA was considered.

Yes, we considered that approach. It's likely not impossible. But it
gets complicated. Consider IndexByte:

func IndexByte (type T Sequence) (s T, b byte) int {
for i := 0; i < len(s); i++ {
if s[i] == b {
return i
}
}
return -1
}

which works with this contract in the latest design:

contract Sequence(T) {
T string, []byte
}

In IndexByte values of type T need to support len, they need to
support the slice/string index operation, and the result of the index
operation needs to be comparable to byte. If you are listing
operators as you suggest, you have to decide either to list all
operators, or to let some operators imply others. If you list all
operators, you need to figure out how to say "this type can be used
with len" and "this type has an index operator that returns byte." If
you let some other operators imply others, then you are adding
complexity to the system, as everybody needs to understand the
implication rules.

As I say, it's likely not impossible. But it seems complex.

Ian

Akhil Indurti

unread,
Jul 27, 2019, 1:35:53 PM7/27/19
to golang-dev
I am really digging this updated design! I just have a couple of questions:

1. Is there for optimizing away top-level type switches inside a function declaration to effectively be specialization (in the case where each implementation is compiled separately)? For example,

contract Float(F) {
F float32, float64
}

func Sqrt(type F Float)(v F) F {
switch v.(type) {
case float32:
return sqrtf32(v)
case float64:
return sqrtf64(v)
}
}

This type switch exhausts all listed types in the contract, and the function has no body outside the type switch. Could a call to Sqrt(float32)(v) be directly transformed into sqrtf32(v)? Or is there a complication created by this idea that I don't see?

2. Is it possible to omit the type argument when the return can be inferred from a type declaration? For instance, using the Float contract from earlier,

func Parse(type F Float)(s string) F { ... }

Instead of writing
f := Parse(float32)("3.14")

is it possible to write
var f float32 = Parse("3.14")

Best,
Akhil

Andy Balholm

unread,
Jul 27, 2019, 1:49:07 PM7/27/19
to Ian Lance Taylor, Andrei Tudor Călin, golang-dev
I really like the idea of listing types instead of listing operators. It was the most important feature of my alternate contract proposal (https://gist.github.com/andybalholm/8165da83c10a48e56590c96542e93ff2). Since operators other than == and != only work on built-in types (and named types based on them), listing the types you can use seems more straightforward than listing the operators you need.

Andy

--
You received this message because you are subscribed to the Google Groups "golang-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.

Burak Serdar

unread,
Jul 27, 2019, 2:03:12 PM7/27/19
to Ian Lance Taylor, golang-dev
Is there a reason to limit listing types in contracts to predefined types:

type S struct {
a int
}

// contract specifies that T must have a field "a"
contract C(T) {
T S
}



>
> Ian
>
> --
> You received this message because you are subscribed to the Google Groups "golang-dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-dev/CAOyqgcUOsyW9ozzvSOcXi8T0ajgxQF0Dcx6m7aJGLHofQ8Eetg%40mail.gmail.com.

Andy Balholm

unread,
Jul 27, 2019, 2:46:25 PM7/27/19
to Burak Serdar, Ian Lance Taylor, golang-dev
That would just mean that T must be of type S.

Andy
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-dev/CAMV2RqpNk%3DHMeYvjBium5U12sfCpaR7_5uVBUvYpPN%3DysCKukw%40mail.gmail.com.

Tristan Colgate

unread,
Jul 27, 2019, 2:59:41 PM7/27/19
to Andy Balholm, Burak Serdar, Ian Lance Taylor, golang-dev
Do struct types in contracts need to be an exact (comparable?) match? Or will embeddings, or individual matching fields xount as satisfying  a contract?

Burak Serdar

unread,
Jul 27, 2019, 3:01:05 PM7/27/19
to Andy Balholm, Ian Lance Taylor, golang-dev
On Sat, Jul 27, 2019 at 12:46 PM Andy Balholm <andyb...@gmail.com> wrote:
>
> That would just mean that T must be of type S.

True. That's not what I had in mind when I wrote the email though, so
let me try again.

Using contracts, is it possible to require that a type has a certain field?

Quoting from the design doc:

"This contract specifies that the type argument must be one of the
listed types (int, int8, and so forth), or it must be a defined type
whose underlying type is one of the listed types."

If you relax this a bit, and require that: if type T satisfies a
contract with a list of types, then a function using T must compile
with each one of those types, then this becomes possible.

The origins of this idea is from a contracts proposal I wrote long ago
using the "like" keyword, with no restriction on the list of types.

https://gist.github.com/bserdar/8f583d6e8df2bbec145912b66a8874b3
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-dev/9720CF67-954E-4DB6-8D39-47ED790A2A4F%40gmail.com.

Axel Wagner

unread,
Jul 27, 2019, 3:27:25 PM7/27/19
to Ian Lance Taylor, golang-dev
Hey,

Thanks for posting this :) I have two follow-up questions about the design:

a) With the `byteseq` contract, what would the type of `x` in
func f(type T byteseq) (v T) { for _, x := range v {} }
be? Is it even allowed? With the prototype implementation it fails type-checking with `cannot range over v (variable of type T)`, but the design draft says any operation that works on both bytes and strings is allowed. If it was allowed, this would seem to imply that multiple versions of the function need to be compiled separately (violating the dual implementation property). I guess disallowing range altogether side-steps that question though.

b) The design draft shows disjunctions that only list types and it shows disjunctions that only list methods. The draft isn't explicit that it's allowed to mix both, but the prototype implementation seems to allow it. I think that's a cool semantic change from the previous version, as it now allows to write functions that truly work on *both* builtin types and user-defined types, e.g.

contract Ord(T) {
    T Less(T) bool, int, int8, … // yadayada
}
func Min(type T Ord) (v []T) T {
    // …
}

If that's intended, I think it would be good to be explicit about that. It would also, I think, be good to show how that would be actually used. I guess you'd have to type-assert, as mentioned for the IOCloser. But then, how would you write code for the other cases? Could you do

func less(type T Ord) (a, b T) bool {
switch a.(type) {
case lesser(T):
    return a.Less(b)
default:
    return a < b
}

Again, thanks for posting this updated draft :)

Ian Lance Taylor

unread,
Jul 27, 2019, 4:58:31 PM7/27/19
to Akhil Indurti, golang-dev
On Sat, Jul 27, 2019 at 10:36 AM Akhil Indurti <aind...@gmail.com> wrote:
>
> 1. Is there for optimizing away top-level type switches inside a function declaration to effectively be specialization (in the case where each implementation is compiled separately)? For example,
>
> contract Float(F) {
> F float32, float64
> }
>
> func Sqrt(type F Float)(v F) F {
> switch v.(type) {
> case float32:
> return sqrtf32(v)
> case float64:
> return sqrtf64(v)
> }
> }
>
> This type switch exhausts all listed types in the contract, and the function has no body outside the type switch. Could a call to Sqrt(float32)(v) be directly transformed into sqrtf32(v)? Or is there a complication created by this idea that I don't see?

It would depend on the exact compiler implementation. Perhaps the
compiler would generate the function twice, once for float32 and once
for float64, in which case, yes, it could be directly transformed. Or
perhaps the compiler would inline the function, in which case, again,
yes, it could be directly transformed. Or perhaps the compiler would
not implement either of those, in which case, no, it would not be
transformed.


> 2. Is it possible to omit the type argument when the return can be inferred from a type declaration? For instance, using the Float contract from earlier,
>
> func Parse(type F Float)(s string) F { ... }
>
> Instead of writing
> f := Parse(float32)("3.14")
>
> is it possible to write
> var f float32 = Parse("3.14")

No, in this case you would have to write the type argument. There is
no provision for pushing the variable declaration into type deduction.

Ian

Ian Lance Taylor

unread,
Jul 27, 2019, 5:00:12 PM7/27/19
to Burak Serdar, Andy Balholm, golang-dev
On Sat, Jul 27, 2019 at 12:01 PM Burak Serdar <bse...@ieee.org> wrote:
>
> On Sat, Jul 27, 2019 at 12:46 PM Andy Balholm <andyb...@gmail.com> wrote:
> >
> > That would just mean that T must be of type S.
>
> True. That's not what I had in mind when I wrote the email though, so
> let me try again.
>
> Using contracts, is it possible to require that a type has a certain field?

With the current design, no, it is not. With last year's design it
was possible, but with this year's design, it is not.

> Quoting from the design doc:
>
> "This contract specifies that the type argument must be one of the
> listed types (int, int8, and so forth), or it must be a defined type
> whose underlying type is one of the listed types."
>
> If you relax this a bit, and require that: if type T satisfies a
> contract with a list of types, then a function using T must compile
> with each one of those types, then this becomes possible.

Right, but at a certain complexity cost that we're currently thinking
we do not want to pay.

Ian

Ian Lance Taylor

unread,
Jul 27, 2019, 5:01:00 PM7/27/19
to Tristan Colgate, Andy Balholm, Burak Serdar, golang-dev
On Sat, Jul 27, 2019 at 11:59 AM Tristan Colgate <tcol...@gmail.com> wrote:
>
> Do struct types in contracts need to be an exact (comparable?) match? Or will embeddings, or individual matching fields xount as satisfying a contract?

In the current design they need to be an identical type. No
embeddings, no individual matching fields.

Ian

Ian Lance Taylor

unread,
Jul 27, 2019, 5:05:30 PM7/27/19
to Axel Wagner, golang-dev
On Sat, Jul 27, 2019 at 12:27 PM Axel Wagner
<axel.wa...@googlemail.com> wrote:
>
> Thanks for posting this :) I have two follow-up questions about the design:
>
> a) With the `byteseq` contract, what would the type of `x` in
> func f(type T byteseq) (v T) { for _, x := range v {} }
> be? Is it even allowed? With the prototype implementation it fails type-checking with `cannot range over v (variable of type T)`, but the design draft says any operation that works on both bytes and strings is allowed. If it was allowed, this would seem to imply that multiple versions of the function need to be compiled separately (violating the dual implementation property). I guess disallowing range altogether side-steps that question though.

In this example the type of x is a value based on the type parameter
T: if T is a string, x is rune, if T is a []byte, x is byte. This is
an unusual case, but it's not fundamentally different from something
like
var x []T


> b) The design draft shows disjunctions that only list types and it shows disjunctions that only list methods. The draft isn't explicit that it's allowed to mix both, but the prototype implementation seems to allow it. I think that's a cool semantic change from the previous version, as it now allows to write functions that truly work on *both* builtin types and user-defined types, e.g.
>
> contract Ord(T) {
> T Less(T) bool, int, int8, … // yadayada
> }
> func Min(type T Ord) (v []T) T {
> // …
> }
>
> If that's intended, I think it would be good to be explicit about that. It would also, I think, be good to show how that would be actually used. I guess you'd have to type-assert, as mentioned for the IOCloser. But then, how would you write code for the other cases? Could you do
>
> func less(type T Ord) (a, b T) bool {
> switch a.(type) {
> case lesser(T):
> return a.Less(b)
> default:
> return a < b
> }
>
> Again, thanks for posting this updated draft :)

That is interesting, although I don't know if we can make work in a
comprehensible way. That function won't compile, because "a < b" is
not supported by all the types in the disjunction. To make it work we
would have to grant special properties to a type switch: within a type
switch case, we permit compiling code based on the case statement.
That might be workable, but we would also have to think about goto
statements. So at least for now we can't do this.

Ian

Burak Serdar

unread,
Jul 27, 2019, 5:19:28 PM7/27/19
to Ian Lance Taylor, Andy Balholm, golang-dev
On Sat, Jul 27, 2019 at 3:00 PM Ian Lance Taylor <ia...@golang.org> wrote:
>
> On Sat, Jul 27, 2019 at 12:01 PM Burak Serdar <bse...@ieee.org> wrote:
> >
> > On Sat, Jul 27, 2019 at 12:46 PM Andy Balholm <andyb...@gmail.com> wrote:
> > >
> > > That would just mean that T must be of type S.
> >
> > True. That's not what I had in mind when I wrote the email though, so
> > let me try again.
> >
> > Using contracts, is it possible to require that a type has a certain field?
>
> With the current design, no, it is not. With last year's design it
> was possible, but with this year's design, it is not.

Personally, I like this much better than last year's design. Thanks
for posting this.

Andy Balholm

unread,
Jul 27, 2019, 5:49:37 PM7/27/19
to Ian Lance Taylor, Burak Serdar, golang-dev
It would be easy to extend this draft to permit specifying that a struct must have a certain field. It could look like this, and be distinguished from a method by the lack of parentheses:

contract linkedListNode(T) {
T Next *T
}

Andy

Andy Balholm

unread,
Jul 27, 2019, 5:52:20 PM7/27/19
to Ian Lance Taylor, Axel Wagner, golang-dev
We currently permit compiling code based on the case statement in a type switch if it has an assignment in the header. But it doesn’t support multiple variables, or contracts instead of types.

Andy
> --
> You received this message because you are subscribed to the Google Groups "golang-dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-dev/CAOyqgcUnDohLsQ9Q%2B%3DWpPEPgdHgFsdsFPOMK3-_2sUHvsP%3D6ow%40mail.gmail.com.

Dan Kortschak

unread,
Jul 27, 2019, 8:05:56 PM7/27/19
to Ian Lance Taylor, golang-dev
Let's say we have a parameterised matrix type (constrained to float32
and float64 in this case) and we want to implement Eigen decomposition.

It seem to me that this section
https://go.googlesource.com/proposal/+/master/design/go2draft-contracts.md#methods-may-not-take-additional-type-arguments
stops us given that obtaining the vectors of the decomposition would
require an additional type parameter (or preferably a way to obtain a
composite-like type from the original type parameter) since the ED of a
generalised matrix may have complex vectors.

Is there a way of obtaining a complex2N from a floatN? Or does this
require a copy from a slice of {r, i floatN} to complex2N?

Dan Kortschak

unread,
Jul 27, 2019, 8:47:33 PM7/27/19
to Ian Lance Taylor, golang-dev
A similar situation exists for a fair bit of the complex portions of
BLAS where there is real scaling of complex values and calculation of
real metrics from complex values, but also the higher precision float32
dot product, Dsdot(n int, x []float32, incX int, y []float32, incY int)
float64.

Dan Kortschak

unread,
Jul 27, 2019, 8:49:37 PM7/27/19
to Ian Lance Taylor, golang-dev
I've obviously flung off on a reverie here. This last one is not
relevant. There being only one of them and all.

Andy Balholm

unread,
Jul 27, 2019, 9:57:10 PM7/27/19
to Dan Kortschak, Ian Lance Taylor, golang-dev
I think the Eigen decomposition would need to be implemented as a function rather than a method.

Andy
> --
> You received this message because you are subscribed to the Google Groups "golang-dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-dev/500986f61dc5c320697e966ad7694409f864fc66.camel%40kortschak.io.

Dan Kortschak

unread,
Jul 28, 2019, 12:03:56 AM7/28/19
to Andy Balholm, Ian Lance Taylor, golang-dev
I'm not sure how this helps. If you have a matrix that is type floatN,
you want a (possibly pair of, or none!) matrix of complex2N. You still
need a way to determine the return type that depends on the input type.

Ian Lance Taylor

unread,
Jul 28, 2019, 12:45:18 AM7/28/19
to Dan Kortschak, golang-dev
On Sat, Jul 27, 2019 at 5:05 PM Dan Kortschak <d...@kortschak.io> wrote:
>
> Let's say we have a parameterised matrix type (constrained to float32
> and float64 in this case) and we want to implement Eigen decomposition.
>
> It seem to me that this section
> https://go.googlesource.com/proposal/+/master/design/go2draft-contracts.md#methods-may-not-take-additional-type-arguments
> stops us given that obtaining the vectors of the decomposition would
> require an additional type parameter (or preferably a way to obtain a
> composite-like type from the original type parameter) since the ED of a
> generalised matrix may have complex vectors.
>
> Is there a way of obtaining a complex2N from a floatN? Or does this
> require a copy from a slice of {r, i floatN} to complex2N?

I don't think there is a way to do that. I think you would have to
write a top-level function that took both the floating point and the
complex type as type parameters.

Ian

Gert Cuykens

unread,
Jul 28, 2019, 2:16:28 AM7/28/19
to golang-dev
Your design is well thought out and covers all possible cases, my only objection I could think of is that function definitions are going to be exponentially harder to read especially for beginners and also error prone where T can shadow other T like this I think?

example: func (r *Receiver(T)) Strings(type T setter)(s []string) ([]T, error) 

Do we really need such a complete design that covers all cases? Can't we have a less efficient but more beginner friendly design like the following example?

contract Float {
float32,
float64,
}

func NewtonSqrt(v Float) Float {
var iterations int
switch v.(type) {
case float32:
iterations = 4
case float64:
iterations = 5
default:
panic(fmt.Sprintf("unexpected type %T", v))
}
}

type MyFloat float32

var G = NewtonSqrt(MyFloat(64))

Viktor Kojouharov

unread,
Jul 28, 2019, 11:36:51 AM7/28/19
to golang-dev
Hi,

Thanks for posting the update. I was wondering whether it would be possible to lift the restriction of using parametrized types before a type is given. I'm thinking of how the current draft can be used to create a list of "mappers" that can be applied on a type, lazilly, and I don't see whether it is possible at all, since instantiation is required. It seems that the different types need to be known beforehand, and a chain of type transformations at the end: T -> U -> V -> W ... seems undoable, unless I'm missing something.

jimmy frasche

unread,
Jul 28, 2019, 12:58:36 PM7/28/19
to Ian Lance Taylor, Axel Wagner, golang-dev
On Sat, Jul 27, 2019 at 2:05 PM Ian Lance Taylor <ia...@golang.org> wrote:
> > b) The design draft shows disjunctions that only list types and it shows disjunctions that only list methods. The draft isn't explicit that it's allowed to mix both, but the prototype implementation seems to allow it. I think that's a cool semantic change from the previous version, as it now allows to write functions that truly work on *both* builtin types and user-defined types, e.g.
> >
> > contract Ord(T) {
> > T Less(T) bool, int, int8, … // yadayada
> > }
> > func Min(type T Ord) (v []T) T {
> > // …
> > }
> >
> > If that's intended, I think it would be good to be explicit about that. It would also, I think, be good to show how that would be actually used. I guess you'd have to type-assert, as mentioned for the IOCloser. But then, how would you write code for the other cases? Could you do
> >
> > func less(type T Ord) (a, b T) bool {
> > switch a.(type) {
> > case lesser(T):
> > return a.Less(b)
> > default:
> > return a < b
> > }
> >
> > Again, thanks for posting this updated draft :)
>
> That is interesting, although I don't know if we can make work in a
> comprehensible way. That function won't compile, because "a < b" is
> not supported by all the types in the disjunction. To make it work we
> would have to grant special properties to a type switch: within a type
> switch case, we permit compiling code based on the case statement.
> That might be workable, but we would also have to think about goto
> statements. So at least for now we can't do this.
>
> Ian

How is that fundamentally different from the example in the draft of
the (Reader or Writer) and Closer contract?

Outside a switch, it would it would be a Closer and inside it's either
a ReadCloser or a WriteCloser.

Similarly, with the Ord contract, outside the type switch it would
only be constrained by the empty contract and inside it either has a
Less method or the operators.

Axel Wagner

unread,
Jul 28, 2019, 2:41:29 PM7/28/19
to jimmy frasche, Ian Lance Taylor, golang-dev
The difference is that you can do a type-assertion on any interface type. So

switch (interface{}(v)).(type) {
case io.Reader:
case io.Closer:
default:
}

is possible on any value v. It would be (presumably) possible to do

switch (interface{}(a)).(type) {
case lesser(T):
    return a.Less(b)
case int:
    return a < b
}

enumerating all possible types as cases. But type-switches match on type-identity, not underlying types, so this fails for non-builtin declared types. I think this is discussed in the proposal as well.

I'm not sure, but I think it's quite possible that the compiler would need to solve SAT to really implement this using `default` - because it means the compiler needs to enumerate all possible assignments that default can take, given that none of the other cases matched. But that's just a suspicion.

Peter Bourgon

unread,
Jul 29, 2019, 12:05:53 AM7/29/19
to Axel Wagner, jimmy frasche, Ian Lance Taylor, golang-dev
Two quick questions after reading the draft:

1. In the section "Types in Contracts" we see defined

contract Ordered(T) {
T int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64, uintptr
float32, float64,
string
}

which is used to implement Smallest(type T Ordered)(s []T) T. But it
seems to me that there are several operations like < supported by
those primitive types, and thus the Ordered contract could also be
used to write an e.g. Sum function which used operator + to add all
the elements in the slice; or an AllEqual function, which used
operator == to return a bool. Am I understanding this correctly?

2. In the section "Aggregates of type parameters in contracts" we
define a Slice contract, and the text says "We can use the Slice
contract to define a function..." but it doesn't seem to me that the
contract is referenced at all in the subsequent block of code, and
therefore am led to believe it doesn't actually need to be defined. Am
I overlooking an implicit rule, or something like this?

Thanks,
Peter.
> --
> You received this message because you are subscribed to the Google Groups "golang-dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-dev/CAEkBMfHohka1w1h-szcxFgejD1v39FhurFPqLhkjVtkJDmtpTA%40mail.gmail.com.

Ian Lance Taylor

unread,
Jul 29, 2019, 1:03:14 AM7/29/19
to Gert Cuykens, golang-dev
On Sat, Jul 27, 2019 at 11:16 PM Gert Cuykens <gert.c...@gmail.com> wrote:
>
> Your design is well thought out and covers all possible cases, my only objection I could think of is that function definitions are going to be exponentially harder to read especially for beginners and also error prone where T can shadow other T like this I think?
>
> example: func (r *Receiver(T)) Strings(type T setter)(s []string) ([]T, error)

Well, I hope they aren't going to be exponentially harder to read. We
are adding an additional optional parameter list to functions, and
marking it with a new keyword. They may well be somewhat harder to
read, but I'm not persuaded that they will be exponentially harder.

Note that your exact example can't occur, as methods are not permitted
to have their own type parameters.


> Do we really need such a complete design that covers all cases? Can't we have a less efficient but more beginner friendly design like the following example?
>
> contract Float {
> float32,
> float64,
> }
>
> func NewtonSqrt(v Float) Float {
> var iterations int
> switch v.(type) {
> case float32:
> iterations = 4
> case float64:
> iterations = 5
> default:
> panic(fmt.Sprintf("unexpected type %T", v))
> }
> }
>
> type MyFloat float32
>
> var G = NewtonSqrt(MyFloat(64))

I don't see how this signature indicates that the type of the result
is the same as the type of the parameter. Or, if it does indicate
that, I don't see how you can write the case where they differ. How
are you going to write a Map function, as in

func Map(type T1, T2)(s []T1, f func(T1) T2) []T2

?

Ian

Ian Lance Taylor

unread,
Jul 29, 2019, 1:06:39 AM7/29/19
to Viktor Kojouharov, golang-dev
On Sun, Jul 28, 2019 at 8:36 AM Viktor Kojouharov <vkojo...@gmail.com> wrote:
>
> Thanks for posting the update. I was wondering whether it would be possible to lift the restriction of using parametrized types before a type is given. I'm thinking of how the current draft can be used to create a list of "mappers" that can be applied on a type, lazilly, and I don't see whether it is possible at all, since instantiation is required. It seems that the different types need to be known beforehand, and a chain of type transformations at the end: T -> U -> V -> W ... seems undoable, unless I'm missing something.

I'm not sure I understand what you mean. It's true that you can't
dynamically instantiate parameterized functions. That is, the type
arguments all have to be known at compile time. I don't see how that
restriction could be lifted. If you mean something different, could
you show an example? Thanks.

Ian

Ian Lance Taylor

unread,
Jul 29, 2019, 1:12:12 AM7/29/19
to jimmy frasche, Axel Wagner, golang-dev
The difference is that that is a single value, presumably using a type
switch exactly like type switches as they exist today.

In the example above we have two values. Even if we write

switch a := a.(type) {

that doesn't change the type of b.

We could perhaps consider a syntax like

func less(type T Ord) (a, b T) bool {
switch T {
case lesser(T):
return a.Less(b)
case contracts.Ordered(T):
return a < b
}

and arrange for the values in a case to take on the switched type.
But that seems very subtle and hard to compile. Let's see what we can
do without such subtleties to start with.

Ian

Ian Lance Taylor

unread,
Jul 29, 2019, 1:17:25 AM7/29/19
to Peter Bourgon, Axel Wagner, jimmy frasche, golang-dev
On Sun, Jul 28, 2019 at 9:05 PM Peter Bourgon <pe...@bourgon.org> wrote:
>
> Two quick questions after reading the draft:
>
> 1. In the section "Types in Contracts" we see defined
>
> contract Ordered(T) {
> T int, int8, int16, int32, int64,
> uint, uint8, uint16, uint32, uint64, uintptr
> float32, float64,
> string
> }
>
> which is used to implement Smallest(type T Ordered)(s []T) T. But it
> seems to me that there are several operations like < supported by
> those primitive types, and thus the Ordered contract could also be
> used to write an e.g. Sum function which used operator + to add all
> the elements in the slice; or an AllEqual function, which used
> operator == to return a bool. Am I understanding this correctly?

Certainly all the ordered types support ==. And it's true that all
the ordered types support the + operator. That said, it is not true
that all the ordered types support the - operator (the exception is
string). And it is not true that all the types that support the +
operator are ordered (the exceptions are complex64 and complex128).
With those provisos, I think you are understanding this correctly.


> 2. In the section "Aggregates of type parameters in contracts" we
> define a Slice contract, and the text says "We can use the Slice
> contract to define a function..." but it doesn't seem to me that the
> contract is referenced at all in the subsequent block of code, and
> therefore am led to believe it doesn't actually need to be defined. Am
> I overlooking an implicit rule, or something like this?

Thanks for noticing that. The example is incorrect. I forgot to
mention the Slice contract. Without mentioning the Slice contract,
the operations len(s) and range s are invalid. I think the example
should look like:

func Map(type S, Element Slice)(s S, f func(Element) Element) S {
r := make(S, len(s))
for i, v := range s {
r[i] = f(v)
}
return r
}

Ian

Ian Lance Taylor

unread,
Jul 29, 2019, 1:20:45 AM7/29/19
to Axel Wagner, jimmy frasche, golang-dev
On Sun, Jul 28, 2019 at 11:41 AM Axel Wagner
<axel.wa...@googlemail.com> wrote:
>
> I'm not sure, but I think it's quite possible that the compiler would need to solve SAT to really implement this using `default` - because it means the compiler needs to enumerate all possible assignments that default can take, given that none of the other cases matched. But that's just a suspicion.

No, it's much simpler. A simple constraint is either a type
constraint or a method constraint (disregarding contract embedding).
You can have a conjunction of general constraints, where each of those
general constraints can be either a simple constraint or a disjunction
of simple constraints. You can't write arbitrary boolean expressions
of simple constraints. That said, the compiler does in general have
to loop over all possible constraint patterns, so one can imagine
machine generated code that will take quite a long time to compile.
But nobody would actually write such code.

Ian

Peter Bourgon

unread,
Jul 29, 2019, 1:23:51 AM7/29/19
to Ian Lance Taylor, Axel Wagner, jimmy frasche, golang-dev
Great, thank you for these clarifications.

One further question that came up in Gopher Slack #go2: presumably
parameterized types allow parameterized interfaces? Assuming so, is
there any functional (i.e. usage) difference between

contract Stream(S, E) {
S Next() (E, error)
}

and

type Stream(type E) interface {
Next() (E, error)
}

?

Axel Wagner

unread,
Jul 29, 2019, 4:36:04 AM7/29/19
to Peter Bourgon, Ian Lance Taylor, jimmy frasche, golang-dev
There is a significant difference in that you can't create a polymorphic value using contracts. This means if you want to pass a func around that can work with *any* stream type providing E's, you need to use a `func (Stream(E))`. If you want to store func's that are restricted to work with a specific stream type, you need to instantiate the Stream-contract. So, both fix E when instantiated, but the contract also fixes S when instantiated. Depending on use-case, one or the other might be more useful.

Brian Hatfield

unread,
Jul 29, 2019, 9:57:09 AM7/29/19
to Axel Wagner, Peter Bourgon, Ian Lance Taylor, jimmy frasche, golang-dev
Ian,

Thank you for posting this updated design. I'm seeing lots of positive reception of it across various internet channels, which is very exciting!

I'm not a PL design expert. I really just like Go. However, something jumped out to me about this proposal, which is syntactically, I have a hard time visually differentiating between type parameters, method receivers, a type cast, function arguments, etc in the examples.

I see commentary in the design document that this was something that was explicitly considered, with notes such as:

We experimented with other syntaxes, such as using a colon to separate the type arguments from the regular arguments. The current design seems to be the nicest, but perhaps something better is possible.

and

When parsing code within a function, such as v := F<T>, at the point of seeing the < it's ambiguous whether we are seeing a type instantiation or an expression using the < operator. Resolving that requires effectively unbounded lookahead. In general we strive to keep the Go parser simple.

I want to strongly encourage the folks working on this proposal to again consider using a differentiated syntax for type parameters. It is very hard to quickly pick out type parameters from other parentheticals in the provided examples. I know there's lots of opportunity for bikeshedding around generics proposals, so I hope you take a moment to reconsider the choice of parenthesis.

If I had a vote, I'd encourage looking again at F<T> style; hopefully there is some reasonable way to achieve this. This syntactic choice is used in a number other languages, and I think it would be helpful to have familiar visual grounding.

Thank you for your consideration,
Brian

--
You received this message because you are subscribed to the Google Groups "golang-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.

Paul Jolly

unread,
Jul 29, 2019, 10:07:44 AM7/29/19
to Brian Hatfield, Axel Wagner, Peter Bourgon, Ian Lance Taylor, jimmy frasche, golang-dev
> I want to strongly encourage the folks working on this proposal to again consider using a differentiated syntax for type parameters. It is very hard to quickly pick out type parameters from other parentheticals in the provided examples. I know there's lots of opportunity for bikeshedding around generics proposals, so I hope you take a moment to reconsider the choice of parenthesis.

A drive-by comment in relation to this. Many people consider it hard
to read Go code without syntax highlighting, others find it easier to
read without any form of highlighting. One possible "solution" here is
for editors/individuals to use alternative highlighting for type
parameters. I personally think there is an elegant simplicity to using
parentheses, consistent with other parts of the language, but I can
sympathise with concerns over readability.

Jan Mercl

unread,
Jul 29, 2019, 10:29:31 AM7/29/19
to Brian Hatfield, Axel Wagner, Peter Bourgon, Ian Lance Taylor, jimmy frasche, golang-dev
On Mon, Jul 29, 2019 at 3:57 PM Brian Hatfield <bmhat...@gmail.com> wrote:

> If I had a vote, I'd encourage looking again at F<T> style; hopefully there is some reasonable way to achieve this.

How do you propose to disambiguate the ambiguous grammar of the F<T> "style"?

Mike Schinkel

unread,
Jul 29, 2019, 11:43:28 AM7/29/19
to golang-dev
First, this latest proposal is brilliant. Thank you to Ian, Robert and the entire Go team for such as well thought-out proposal.

The only thing I can think of to be improved is slightly better syntax in a few cases.

On Monday, July 29, 2019 at 1:03:14 AM UTC-4, Ian Lance Taylor wrote:
> example: func (r *Receiver(T)) Strings(type T setter)(s []string) ([]T, error)

Well, I hope they aren't going to be exponentially harder to read.  We
are adding an additional optional parameter list to functions, and
marking it with a new keyword.  They may well be somewhat harder to
read, but I'm not persuaded that they will be exponentially harder.

I have already felt Go 1 funcs with named return values are hard to read — especially when they require rightward scrolling — so with the addition of type arguments I think it will get even worse.

Given that Go has often deferred to readability over syntax salad I'd like to suggest the team consider the following:

Instead of:


func MapAndPrint(type E, M stringer(M))(s []E, f(E) M) []string {...}
 
How about allowing existings keyword to work in the following context where parans are optional (or not,) and where type come after the parameter list, not before as in the proposal:

func MapAndPrint(s []E, f(E) M) type E, M stringer(M) return []string {...}

With that you could then have gofmt format like so::

func MapAndPrint(s []E, f(E) M)
    type E, M stringer(M)
    return []string {
    ...
}

Adopting this multi-line keyword-based approach would: 
  1. Allow longer function declarations to be written so they do not run off the right side of the editor window and/or the code review page,
  2. Be easier to read, especially for newer Go programmers or for programmers whose eyesight is not perfect,
  3. Resolve the "Lots of irritating silly parentheses" issue in the proposal (at least for the declaration of the func),
  4. Feel lot more Go-like (IMO), and
  5. Resolve one of the very few laments I have about the proposal. :-)

#fwiw -Mike


 

Devon H. O'Dell

unread,
Jul 29, 2019, 11:52:28 AM7/29/19
to Peter Bourgon, Ian Lance Taylor, Axel Wagner, jimmy frasche, golang-dev
One major difference is that you cannot create an object with the contract type. For example,

var s Stream

could only possibly reference the Stream interface; it could not ever mean the contract.

This was asked by someone at Gophercon, and so I do wonder whether this might need to be considered for a FAQ entry. I'm hopeful that this won't result in people resorting to names like IStream and CStream, but I do worry a bit that this will happen.

--dho

--
You received this message because you are subscribed to the Google Groups "golang-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.

Ian Lance Taylor

unread,
Jul 29, 2019, 1:07:57 PM7/29/19
to Axel Wagner, Peter Bourgon, jimmy frasche, golang-dev
On Mon, Jul 29, 2019 at 1:36 AM Axel Wagner
<axel.wa...@googlemail.com> wrote:
>
> There is a significant difference in that you can't create a polymorphic value using contracts. This means if you want to pass a func around that can work with *any* stream type providing E's, you need to use a `func (Stream(E))`. If you want to store func's that are restricted to work with a specific stream type, you need to instantiate the Stream-contract. So, both fix E when instantiated, but the contract also fixes S when instantiated. Depending on use-case, one or the other might be more useful.

Yes.

And, of course, interface values are boxed. Parameterized types are
not. This becomes particularly noticeable if you have a slice or
channel of S/Stream values.

I doubt parameterized interface types will see much use. But it's
quite possible that there are use cases I haven't considered.

Ian

Ian Lance Taylor

unread,
Jul 29, 2019, 1:16:12 PM7/29/19
to Brian Hatfield, Axel Wagner, Peter Bourgon, jimmy frasche, golang-dev
On Mon, Jul 29, 2019 at 6:57 AM Brian Hatfield <bmhat...@gmail.com> wrote:
>
> Thank you for posting this updated design. I'm seeing lots of positive reception of it across various internet channels, which is very exciting!
>
> I'm not a PL design expert. I really just like Go. However, something jumped out to me about this proposal, which is syntactically, I have a hard time visually differentiating between type parameters, method receivers, a type cast, function arguments, etc in the examples.
>
> I see commentary in the design document that this was something that was explicitly considered, with notes such as:
>
>> We experimented with other syntaxes, such as using a colon to separate the type arguments from the regular arguments. The current design seems to be the nicest, but perhaps something better is possible.
>
>
> and
>
>> When parsing code within a function, such as v := F<T>, at the point of seeing the < it's ambiguous whether we are seeing a type instantiation or an expression using the < operator. Resolving that requires effectively unbounded lookahead. In general we strive to keep the Go parser simple.
>
>
> I want to strongly encourage the folks working on this proposal to again consider using a differentiated syntax for type parameters. It is very hard to quickly pick out type parameters from other parentheticals in the provided examples. I know there's lots of opportunity for bikeshedding around generics proposals, so I hope you take a moment to reconsider the choice of parenthesis.
>
> If I had a vote, I'd encourage looking again at F<T> style; hopefully there is some reasonable way to achieve this. This syntactic choice is used in a number other languages, and I think it would be helpful to have familiar visual grounding.

I think the syntactic complexities of F<T> basically preclude that
syntax. I'm certainly open to reviewing a detailed analysis of how it
could be made to work. But just looking again isn't going to help.
I've looked at it for years. To be clear, I believe that it can be
made to work, but only at a significant complexity cost which will
bubble up in error messages and in usage of the language. As just one
aspect of the problem, consider how long C++ programmers had to write
std::vector<std::vector<int> > with an explicit space between the two
'>' characters, and how complex the resolution of that was in the
language spec.

More generally, this is the kind of issue that can only be addressed
by looking at real code. As we try writing code that uses this
syntax, we will discover whether it really is a problem. Maybe it is
a problem, maybe it isn't. It's important to really try it out in
practice. Go is a language that uses parentheses for many different
things. Which is harder to work with: another use of parentheses, or
a brand new syntactic construct? I don't think the answer is obvious.

Ian

Ian Lance Taylor

unread,
Jul 29, 2019, 1:21:18 PM7/29/19
to Mike Schinkel, golang-dev
Thanks for the suggestion. As I said earlier I would like to try
seeing some real code with the current suggested syntax. That is the
only way that we'll discover whether this really is a problem or not.

As to this specific proposal, I'm concerned about scoping. Here you
are introducing type parameter after they are being used. I'm
concerned that that could be confusing if some of the identifiers used
for type parameter names are also defined as types in package scope.
Programmers, and tools, will need to look ahead in the code to see the
definition of the identifier. That seems potentially confusing. It
is unlike any other scoping in the language: currently you can always
tell immediately the set of scopes in which to look for an identifier.

Ian

Mike Schinkel

unread,
Jul 29, 2019, 1:55:35 PM7/29/19
to golang-dev
On Monday, July 29, 2019 at 1:21:18 PM UTC-4, Ian Lance Taylor wrote:
 
As to this specific proposal, I'm concerned about scoping.  Here you
are introducing type parameter after they are being used. ...

Programmers, and tools, will need to look ahead in the code to see the
definition of the identifier.  That seems potentially confusing.  It
is unlike any other scoping in the language: currently you can always
tell immediately the set of scopes in which to look for an identifier.

Fair point. I realized the look-ahead objection only hours after I posted the suggestion.

Given the objection, please consider this as a potential instead:

func MapAndPrint() 
type E, M stringer(M)
params s []E, f(E) M
return []string {
...
}   

I would like to try seeing some real code with the current suggested 
syntax.  That is the only way that we'll discover whether this really is 
a problem or not. 

Understood. 

May I ask what metric will be used to determine if this (or any other aspect of the current proposal) will be considered a problem? 

Without an objective metric it would seem that "as-is" would have the most inertia.

-Mike

Ian Lance Taylor

unread,
Jul 29, 2019, 1:59:41 PM7/29/19
to Mike Schinkel, golang-dev
On Mon, Jul 29, 2019 at 10:55 AM Mike Schinkel <mi...@newclarity.net> wrote:
>
> May I ask what metric will be used to determine if this (or any other aspect of the current proposal) will be considered a problem?
>
> Without an objective metric it would seem that "as-is" would have the most inertia.

I'm skeptical that there is any objective metric for these sort of
syntactic issues. If there is one, that would solve a lot of
problems. The closest we can get is something like "how many people
are confused" and "how often do people misunderstand the code" and
"how many mistakes do people make."

So it's going to be a matter of reading and writing code and seeing
how it feels. That is how the Go language has been designed since the
beginning.

Ian

jimmy frasche

unread,
Jul 29, 2019, 3:36:36 PM7/29/19
to Ian Lance Taylor, Mike Schinkel, golang-dev
The rules for introducing type parameter names for a contract seem a
bit complicated.

I'm not really sure if I understand them correctly. Here is my understanding:

Fix a contract C with n parameters.

When using m = n parameters, it's straightforward:
(type p₀, …, pₘ C)

But there are some examples of m > n parameters in the draft, like:
(type p₀, …, pₘ C(p₀, …, pₙ))
If I'm following that correctly it seems like the rule for this is
that n of the m parameters need to be passed directly to C and that
the m - n leftovers are constrained only by the empty contract.

The case m < n is not covered and is, presumably, a compile-time error.

From what I can tell, this is a compromise stemming from
1. allowing contracts to constrain multiple types
2. not wanting to name the empty contract
3. it being awkward to have to define contracts for unrelated types,
especially without a name for the empty contract

3 may be incorrect, if you can write
contract mapTypesConstraint(K, V) {
comparable(K)
}
where V not being used in the definition leaves it unconstrained. But,
if that's valid, then it's less clear to me why m > n parameters are
allowed at all.

Those rules still require writing contracts like
contract cmp3(S, T, U) {
comparable(S)
comparable(T)
comparable(U)
}

If I'm following the above correctly, why is it this way instead of:
- name the empty contract. For purposes of discussion, I will use "any"
- allow multiple contracts in a parameterization as long the
parameters are unrelated
- disallow passing parameters to contracts within the (type …)
- require m = n for each contract

That way instead of
(type K, V comparable(K))
you would write
(type K comparable, V any)

Using the G contract which provides Node and Edge, you would be able to write
(type Node, Edge G, K comparable, V any)
instead of
contract GandK(Node, Edge, K) {
G(Node, Edge)
comparable(K)
}
(type Node, Edge, K, V GandK(Node, Edge, K))

In a case like
(type S, T, U)
you'd have to write
(type S any, T any, U any)
and
(type S, T, U any)
would need to be an error since any only has one parameter. But the
error would be clear and the rules uniform.

There would still need to be new contracts defined when the types
relied on each other, of course.

As far as I can tell, the downside is introducing another name, which
is certainly to be avoided, but it seems, at least to me, that
introducing a name for the empty contract allows improved ergonomics
and readability.

I hate to bring up so minor a point, but the m > n examples were one
of the harder to understand points in the draft.
> --
> You received this message because you are subscribed to the Google Groups "golang-dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-dev/CAOyqgcUi2Nu6GXigQ58%2BeSeEuzGHGjmQHXti59h7--63F4d38A%40mail.gmail.com.

Mike Schinkel

unread,
Jul 29, 2019, 4:43:46 PM7/29/19
to golang-dev
On Monday, July 29, 2019 at 1:59:41 PM UTC-4, Ian Lance Taylor wrote:
I'm skeptical that there is any objective metric for these sort of
syntactic issues.  If there is one, that would solve a lot of
problems.  The closest we can get is something like "how many people
are confused" and "how often do people misunderstand the code" and
"how many mistakes do people make."

So it's going to be a matter of reading and writing code and seeing
how it feels.  That is how the Go language has been designed since the
beginning.

I have a suggestion for an objective metric, at least for the issue I brought up.  

As I said previously, I find func declaration line length to be problematic when trying to view code with long func declarations.  So we could consider code in the wild with func declarations longer then, say 100 characters? 

Take a look at the attached screenshot from my 27" monitor with the long line examples circled in red to see what I mean. Also see the comments circled in green to see how 
using the proposed syntax would improve it.

To make this objective I scanned the Go 1.12.6 SDK and found well over 600 func declarations that are 100 characters or longer.  I posted the list as well as the bash command used to generate it on this Gist: https://gist.github.com/mikeschinkel/c7f4cd0d3be326fe921d3cb9ee0897e9 .

Note I included funcs with receivers too in func.txt  — even though those would not support generic parameters — to illustrate the problem but also because the same solution would be beneficial even with only params and return values included.


long-func-declarations.png








Axel Wagner

unread,
Jul 29, 2019, 5:03:42 PM7/29/19
to Mike Schinkel, golang-dev
Just to contextualize that number: That's 600 out of ~56000, so a tiny bit over 1%.

--
You received this message because you are subscribed to the Google Groups "golang-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.

Mike Schinkel

unread,
Jul 29, 2019, 5:13:09 PM7/29/19
to golang-dev
I now wish I had not stopped at 100 characters.

If we go to 60 characters or longer — which is still quite long — we are now looking at almost 10k, or more like 18% of the code base in the SDK.
To unsubscribe from this group and stop receiving emails from it, send an email to golan...@googlegroups.com.

Axel Wagner

unread,
Jul 29, 2019, 5:33:58 PM7/29/19
to Mike Schinkel, golang-dev
I'm not saying you're completely wrong about function declarations in Go being sometimes long and/or unreadable. But I think if you're moving the goal post this way, you can no longer claim it's an objective metric. ISTM you are trying to define a metric to support your argument, instead of defining a metric and see where it's taking you.

I for one find 60 characters completely fine for a function declaration. But YMMV of course - which is my point :)

To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-dev/119b2a5c-9095-4fa1-a760-ca5ad5bf94c2%40googlegroups.com.

Mike Schinkel

unread,
Jul 29, 2019, 5:41:33 PM7/29/19
to golang-dev
Somewhat fair point about moving the goal post. Although 100 was picked arbitrarily and greatly exceeds what I am comfortable with.

To support 60 as the goalpost we can look at what many consider to be a good UX for reading lines on a computer, at 50-60 chars (max 75 chars.)

https://baymard.com/blog/line-length-readability

Given this research it would not be unreasonable to objectively pick 50-60 chars rather than 100 chars, or at least 75 chars.

Whatever that case, I bring up the issue of too-long line lengths because it is one that I am constantly finding to be problematic in my usage of Go. 

-Mike

Anca Emanuel

unread,
Jul 29, 2019, 6:07:17 PM7/29/19
to golang-dev
This seems simple enough, but there are heavy implications on libraries, and I think there is so much work to get this right, I'm afraid 10 years of testing and rewrite of standard libraries will not be enough. So I think this is another way to shoot yourself in the foot.
Who want this or that better invent another language.

On Saturday, July 27, 2019 at 4:44:53 PM UTC+3, Ian Lance Taylor wrote:
The updated generics design that I mentioned in my talk at Gophercon
(live blog at https://about.sourcegraph.com/go/gophercon-2019-generics-in-go)
can now be seen at
https://go.googlesource.com/proposal/+/master/design/go2draft-contracts.md
.

Ian

Axel Wagner

unread,
Jul 29, 2019, 6:09:55 PM7/29/19
to Mike Schinkel, golang-dev
AIUI that research considers prose, not code, so I wouldn't transfer its findings to this discussion. It seems pretty natural to me that code is slower to read than prose and has different concerns for legibility.

Anyway, as I said, I wasn't trying to invalidate your experiences. I just think that while, yes, counting the numbers of characters in a function line is an easily and objectively measurable metric, that doesn't say it's a good metric to objectively measure readability. Personally, I find it much more important how hard it is to destructure a line of code, for example - I find 100 characters of Go to be far more readable than 30 characters of APL.

To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-dev/85a1c28b-fd18-41e2-aa74-7f457670f962%40googlegroups.com.

Axel Wagner

unread,
Jul 29, 2019, 6:14:50 PM7/29/19
to Mike Schinkel, golang-dev
To clarify maybe: I think you might well be right that Go code is objectively hard to read and that Go function definitions in particular might get even harder to read with this. But what Ian said is, that that's a hard thing to measure objectively, without actual experience from a broad range of programmers. And line-length is exactly a metric, but not one that is necessarily good at measuring what you want to measure. Even if it correlates (in that the function definitions you find hard to read are exactly the long ones), that doesn't mean that it's causal (as illustrated by my APL example), so it isn't super suited as an optimization criterion.

Mike Schinkel

unread,
Jul 29, 2019, 6:26:09 PM7/29/19
to golang-dev
On Monday, July 29, 2019 at 6:09:55 PM UTC-4, Axel Wagner wrote:
AIUI that research considers prose, not code, so I wouldn't transfer its findings to this discussion.

It is subjective to dismiss this research simply because of code vs. prose. 


That said, it is not appropriate for this one concern to dominate the discussion of this thread. Let us both please end this topic on this thread, out of consideration for others.


-Mike

 

Dan Kortschak

unread,
Jul 29, 2019, 6:48:31 PM7/29/19
to Ian Lance Taylor, golang-dev
It would be possible if it were possible to write something like

contract numeric {
T float32, float64
}

func conv(type T numeric) complex(T,T) {
...
}

Would this be something that could be considered? It massively reduces
the complexity of code that causes interactions between real and
complex values.

Dan

On Sat, 2019-07-27 at 21:45 -0700, Ian Lance Taylor wrote:
> On Sat, Jul 27, 2019 at 5:05 PM Dan Kortschak <d...@kortschak.io>
> wrote:
> >
> > Let's say we have a parameterised matrix type (constrained to
> > float32
> > and float64 in this case) and we want to implement Eigen
> > decomposition.
> >
> > It seem to me that this section
> >
https://go.googlesource.com/proposal/+/master/design/go2draft-contracts.md#methods-may-not-take-additional-type-arguments
> > stops us given that obtaining the vectors of the decomposition
> > would
> > require an additional type parameter (or preferably a way to obtain
> > a
> > composite-like type from the original type parameter) since the ED
> > of a
> > generalised matrix may have complex vectors.
> >
> > Is there a way of obtaining a complex2N from a floatN? Or does this
> > require a copy from a slice of {r, i floatN} to complex2N?
>
> I don't think there is a way to do that. I think you would have to
> write a top-level function that took both the floating point and the
> complex type as type parameters.
>
> Ian

alan...@gmail.com

unread,
Jul 29, 2019, 7:04:00 PM7/29/19
to golang-dev
At one time I favored using square brackets rather than parentheses for enclosing the type parameter(s) but I see from the latest design document that there are some  ambiguities there which I had not previously bargained for.

That being so, I now feel quite happy that parentheses are the best choice. Other kinds of parameters - ordinary or (if there's more than one) return parameters - use parentheses as well so it's perfectly consistent to do the same for type parameters.

In practice I suspect most gophers use IDEs or editors with syntax highlighting (even 'gedit' has this for Go) and, if a different color can be used for contract names (rather than for the type parameters themselves), then this would help the type parameter block to stand out visually from the rest of the declaration when a contract is used. As a keyword itself, 'type' will of course already be in a different color.

Alan

Ian Lance Taylor

unread,
Jul 29, 2019, 7:45:46 PM7/29/19
to jimmy frasche, Mike Schinkel, golang-dev
On Mon, Jul 29, 2019 at 12:36 PM jimmy frasche <soapbo...@gmail.com> wrote:
>
> The rules for introducing type parameter names for a contract seem a
> bit complicated.
>
> I'm not really sure if I understand them correctly. Here is my understanding:
>
> Fix a contract C with n parameters.
>
> When using m = n parameters, it's straightforward:
> (type p₀, …, pₘ C)

Yes, assuming of course that the contract takes parameters in the same
order as the function, which will normally be the case.

> But there are some examples of m > n parameters in the draft, like:
> (type p₀, …, pₘ C(p₀, …, pₙ))
> If I'm following that correctly it seems like the rule for this is
> that n of the m parameters need to be passed directly to C and that
> the m - n leftovers are constrained only by the empty contract.

Yes.

> The case m < n is not covered and is, presumably, a compile-time error.

No. You must pass to the contract exactly as many type parameters as
the contract expects. And you can only pass type parameters that are
declared in the current type parameter list. But you can repeat type
parameters.

contract StringError(T1, T2) {
T1 String() string
T2 Error() string
}

func F(type T StringError(T, T))(v T1) { ... }

In this example the type parameter of F must have both a String and an
Error method.

(I'm not claiming that there is any reason to actually do this. I
doubt there is. It's just the logical consequence of the other
rules.)


> From what I can tell, this is a compromise stemming from
> 1. allowing contracts to constrain multiple types
> 2. not wanting to name the empty contract
> 3. it being awkward to have to define contracts for unrelated types,
> especially without a name for the empty contract
>
> 3 may be incorrect, if you can write
> contract mapTypesConstraint(K, V) {
> comparable(K)
> }
> where V not being used in the definition leaves it unconstrained. But,
> if that's valid, then it's less clear to me why m > n parameters are
> allowed at all.

You're right: passing parameters to the contract in a function
definition is not required for functionality. We've discussed
dropping it and maybe we should.
That all seems workable to me. However, the case where none of the
type parameters have any constraints by far the common case. It's the
case that applies for container types and for slice/map/channel
manipulation functions. So I think that it's better to not require
people to explicitly say "this parameter has no constraints".


> I hate to bring up so minor a point, but the m > n examples were one
> of the harder to understand points in the draft.

Hope this helps explain our thinking.

Ian

Ian Lance Taylor

unread,
Jul 29, 2019, 7:49:51 PM7/29/19
to Dan Kortschak, golang-dev
On Mon, Jul 29, 2019 at 3:48 PM Dan Kortschak <d...@kortschak.io> wrote:
>
> It would be possible if it were possible to write something like
>
> contract numeric {
> T float32, float64
> }
>
> func conv(type T numeric) complex(T,T) {
> ...
> }
>
> Would this be something that could be considered? It massively reduces
> the complexity of code that causes interactions between real and
> complex values.

Let's see what some code looks like before we start adding features
like this. This is a kind of feature that can likely be added later
if it is needed.

Ian

jimmy frasche

unread,
Jul 29, 2019, 8:28:03 PM7/29/19
to Ian Lance Taylor, Mike Schinkel, golang-dev
Ah. That makes this make more sense. I'm not convinced it's the best
way to go, but it does make more sense.

Of course that could also be accomplished by
contract StringOrError(T) {
StringError(T, T)
Sure, but even then how often are you going over, say, 3 parameters?
I'd consider 5 *a lot a lot* in this context.

> > I hate to bring up so minor a point, but the m > n examples were one
> > of the harder to understand points in the draft.
>
> Hope this helps explain our thinking.
>
> Ian

Yes, thank you.

Dan Kortschak

unread,
Jul 29, 2019, 8:47:47 PM7/29/19
to Ian Lance Taylor, golang-dev
OK, but at the moment, I don't see a way to achieve what is needed for
interaction between complex numbers and reals in a way that does not
allow complete freedom to interchange types. Also, this just looks like
a natural way to do it, it composes types nicely and is immediately
obvious in what it is doing (cf the only what to achieve something
similar that has been proposed so far which requires an input type for
the complex and type conversion shims to ensure that that real matches
the complex when the complex is constructed).

jadr2...@gmail.com

unread,
Jul 30, 2019, 8:45:42 AM7/30/19
to golang-dev
Hi!

I started working on a project to create realistic packages on which the code & concepts in the generics proposal can be tested. One of the versions I worked on was a streaming map-filter-reduce implementation: https://github.com/jadr2ddude/exp/tree/08b7ee669b2d2895488404ad1611eaa573ea7fc7/go2/generics/mrf

During the implementation, I came across an ambiguous case.

As a core type, this package uses a parameterized interface type:
// Stream is a continuous sequence of values.
type Stream(type E) interface {
	// Get the next value in the stream.
	// When done, io.EOF signals termination of the stream.
	Next() (E, error)
}

I tried to make an extended version that can be asserted in order to speed up splitting up concurrent work:

type Sub(type E) interface {
	Stream(E)
	SubStream(uint) (Stream(E), error)
}

There is a problem here. How is generic type embedding supposed to work? Here, it seems that it would be interpreted as a function called stream which accepts type E as an argument, rather than a parameterized interface type using E for the parameter.
Is there any way that this ambiguity could be resolved?

Thanks,
Jaden

Axel Wagner

unread,
Jul 30, 2019, 8:51:31 AM7/30/19
to jadr2...@gmail.com, golang-dev
Interesting case.

The grammar should allow disambiguating by encasing in parentheses:

type Sub(type E) interface {
    (Stream(E))
    SubStream(uint) (Stream(E), error)
}

This is because a Type can always be enclosed: https://golang.org/ref/spec#Type
But interface-types don't allow enclosing methods: https://golang.org/ref/spec#InterfaceType

That being said, you can of course also just repeat the method set verbatim.

--
You received this message because you are subscribed to the Google Groups "golang-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.

Joshua Boelter

unread,
Jul 30, 2019, 6:56:32 PM7/30/19
to golang-dev
Thanks for publishing the updated draft and the overview at GopherCon last week. Excited to see progress on generic support.

In the current proposal, it's not clear how to resolve the ambiguity of a function template versus calling a function that returns a function in certain scenarios. Given a type can also be a legal identifier, can this be resolved?

Example below:


func Print(type T)(t T) {
fmt.Print(t)
}

func Print(i int) func(i int) {
return func(i int) { fmt.Print(i) }
}

func main() {
var int int

// ambiguous
Print(int)(int)
var PrintInts = Print(int)
}

It also wasn't clear if parenthessis wrapping resolved the instantiating types in type literals entirely; don't have a counter example here.


In the spirit of a possible solution, have you considered a sentinel for initiating template syntax? I took a crack at updating scanner and token (cmd/compile/internal/syntax/go/scanner/ and go/token/) to produce token.LTEMPL and token.RTEMPL; including arbitrary nesting. This approach allows you to definitively know if you're in the template of a contract, function template  or type instantiation. This is akin the dlang notation that uses '!('. I alsos suspect this might simplify tools/syntax highlighters also as they would know if they are in a template type scope.


For the sake of demonstration; this approach can also disambiguate the various '>*' operators from a closing template if the syntax went that way.

var l Lockable@<Lockable@<int>>

Axel Wagner

unread,
Jul 30, 2019, 7:28:09 PM7/30/19
to Joshua Boelter, golang-dev
I don't think your example is (or should be) valid. By declaring a variable with name `int` you are shadowing the predeclared identifier and thus it's no longer a valid type-name. It's also not valid because you have two Print-functions of course.

But I assume that was kind of your point, that without knowing how Print/int is defined, this can't be disambiguated? AIUI the syntax doesn't disambiguate this and you need type-information to do so. At least that's what I gather from looking at the API in the proof-of-concept implementation of the go/* packages :)

--
You received this message because you are subscribed to the Google Groups "golang-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.

Ian Lance Taylor

unread,
Jul 30, 2019, 7:42:49 PM7/30/19
to Axel Wagner, Joshua Boelter, golang-dev
On Tue, Jul 30, 2019 at 4:28 PM 'Axel Wagner' via golang-dev
<golan...@googlegroups.com> wrote:
>
> I don't think your example is (or should be) valid. By declaring a variable with name `int` you are shadowing the predeclared identifier and thus it's no longer a valid type-name. It's also not valid because you have two Print-functions of course.
>
> But I assume that was kind of your point, that without knowing how Print/int is defined, this can't be disambiguated? AIUI the syntax doesn't disambiguate this and you need type-information to do so. At least that's what I gather from looking at the API in the proof-of-concept implementation of the go/* packages :)

Yes. And that is no different from the current use of T(v), which
could be either a call of a function T with an argument v or a
conversion of the value v to the type T. The parser doesn't care, as
the expressions parse exactly the same either way. Only the
type-checker cares.

Ian
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-dev/CAEkBMfG%3DVYn0xkSLghR-tDAs37qjGj%2BtLJevKdF5nEEuFfOz6A%40mail.gmail.com.

Joshua Boelter

unread,
Jul 30, 2019, 7:56:40 PM7/30/19
to golang-dev
Kudos again to the latest proposal.  Spent a bit of time reading and re-reading the contracts.

Apologies if I missed it in the spec, but was there a rationale to not allow a contract to specify that a type conform to an interface? This would make it easier to declare a contract against interfaces developers are already familiar with or part of existing package.

The example in the spec could be written:

contract IOCloser(S) {
S io.Reader, io.Writer
S io.Closer
}

Regarding constraining to a slice, is this legal?  i.e. can I constrain a type to a slice by specifying it implement the array/slice operator? Or maybe I'm overthinking this, and the Map function just works without any constraint on T?

contract Sliceable(T) {
T []T
}

In turn, allowing me to define functions where I can access the element type T independent of a slice of T?

func Map(type T Sliceable)(s []T, f func(T) T) []T {
r := make([]T, len(s))
for i, v := range s {
r[i] = f(v)
}
return r
}

// Should be SummableSlice; can I constrain to any builtin type that supports '+' ?
func Sum(type T Slice)(s []T) T {
var r T // or *new(T) ?
for i, v := range s {
r = r + v
}
return r
}


It took me a while to internalize the ',' as an 'OR' and each line as an 'AND' in a contract.

// ',' is an implicit OR
contract IOCloser(S) {
S Read([]byte) (int, error), // note trailing comma
Write([]byte) (int, error)
S Close() error
}

Which then led me to below; thoughts on utilizing a boolean syntax to allow a more expressive contract?

contract IOCloser(S) {
S io.ReadCloser || io.WriteCloser
}

// equivalent to above
contract IOCloser(S) {
S (io.Reader && io.Closer) || (io.Writer && io.Closer)
}

subhas newar

unread,
Jul 30, 2019, 11:58:51 PM7/30/19
to Joshua Boelter, golang-dev
how can i write 
spectrum analyzer tab in golang 

--
You received this message because you are subscribed to the Google Groups "golang-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.

Ian Lance Taylor

unread,
Jul 31, 2019, 12:16:13 AM7/31/19
to Joshua Boelter, golang-dev
On Tue, Jul 30, 2019 at 4:56 PM Joshua Boelter <joshua....@gmail.com> wrote:
>
> Kudos again to the latest proposal. Spent a bit of time reading and re-reading the contracts.
>
> Apologies if I missed it in the spec, but was there a rationale to not allow a contract to specify that a type conform to an interface? This would make it easier to declare a contract against interfaces developers are already familiar with or part of existing package.
>
> The example in the spec could be written:
>
> contract IOCloser(S) {
> S io.Reader, io.Writer
> S io.Closer
> }

The question is whether that means that S must have an underlying type
of io.Reader, or whether S must implement io.Reader. And in
particular how should we handle the builtin error interface. It may
be that what you suggest--that we treat an interface type as meaning
that the type argument must implement the interface, rather than that
the type argument must be the interface--makes the most sense.

More generally, there is clearly some degree of overlap between a
contract with one type parameter that only lists methods that do not
mention the type parameter, and an interface type. Of course, they
are different, too: you can't have a value of a contract "type". But
we could in principle decide that you can use an interface as a
contract. I don't know how often that would be useful. But if we
decide to go in that direction, it might affect the case you describe
above.

> Regarding constraining to a slice, is this legal? i.e. can I constrain a type to a slice by specifying it implement the array/slice operator? Or maybe I'm overthinking this, and the Map function just works without any constraint on T?
>
> contract Sliceable(T) {
> T []T
> }

You can write that contract, but essentially no type implements it. I
think the only type that implements it is "type T []T", which is a
peculiar type that can't hold any real values but can hold instances
of itself of different lengths.

What does work along these lines is to use two type parameters.

contract Sliceable(Slice, Element) {
Slice []Element
}

Then, of course, you need two type arguments.

> Which then led me to below; thoughts on utilizing a boolean syntax to allow a more expressive contract?

Let's see if there is any real use for that first.

Thanks for the note.

Ian

jimmy frasche

unread,
Jul 31, 2019, 1:37:42 AM7/31/19
to Ian Lance Taylor, Joshua Boelter, golang-dev
On Tue, Jul 30, 2019 at 9:16 PM Ian Lance Taylor <ia...@golang.org> wrote:
>
> On Tue, Jul 30, 2019 at 4:56 PM Joshua Boelter <joshua....@gmail.com> wrote:
> >
> > Kudos again to the latest proposal. Spent a bit of time reading and re-reading the contracts.
> >
> > Apologies if I missed it in the spec, but was there a rationale to not allow a contract to specify that a type conform to an interface? This would make it easier to declare a contract against interfaces developers are already familiar with or part of existing package.
> >
> > The example in the spec could be written:
> >
> > contract IOCloser(S) {
> > S io.Reader, io.Writer
> > S io.Closer
> > }
>
> The question is whether that means that S must have an underlying type
> of io.Reader, or whether S must implement io.Reader. And in
> particular how should we handle the builtin error interface. It may
> be that what you suggest--that we treat an interface type as meaning
> that the type argument must implement the interface, rather than that
> the type argument must be the interface--makes the most sense.
>
> More generally, there is clearly some degree of overlap between a
> contract with one type parameter that only lists methods that do not
> mention the type parameter, and an interface type. Of course, they
> are different, too: you can't have a value of a contract "type". But
> we could in principle decide that you can use an interface as a
> contract. I don't know how often that would be useful. But if we
> decide to go in that direction, it might affect the case you describe
> above.

If there were a third type of constraint for declaring explicit
convertibility requirements between types, T(S), you could use that
with interfaces to write
contract IOCloser(S) {
io.Reader(S), io.Writer(S)
io.Closer(S)
}


> > Regarding constraining to a slice, is this legal? i.e. can I constrain a type to a slice by specifying it implement the array/slice operator? Or maybe I'm overthinking this, and the Map function just works without any constraint on T?
> >
> > contract Sliceable(T) {
> > T []T
> > }
>
> You can write that contract, but essentially no type implements it. I
> think the only type that implements it is "type T []T", which is a
> peculiar type that can't hold any real values but can hold instances
> of itself of different lengths.
>
> What does work along these lines is to use two type parameters.
>
> contract Sliceable(Slice, Element) {
> Slice []Element
> }
>
> Then, of course, you need two type arguments.
>
> > Which then led me to below; thoughts on utilizing a boolean syntax to allow a more expressive contract?
>
> Let's see if there is any real use for that first.
>
> Thanks for the note.
>
> Ian
>
> --
> You received this message because you are subscribed to the Google Groups "golang-dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-dev/CAOyqgcV8qqjfi6eRn%3DQ3gtT4S_uamZowmU6mkkBj9qM4%2B6jSkA%40mail.gmail.com.

David Golden

unread,
Jul 31, 2019, 8:29:57 AM7/31/19
to Axel Wagner, jadr2...@gmail.com, golang-dev
On Tue, Jul 30, 2019 at 8:51 AM 'Axel Wagner' via golang-dev <golan...@googlegroups.com> wrote:
The grammar should allow disambiguating by encasing in parentheses:

type Sub(type E) interface {
    (Stream(E))
    SubStream(uint) (Stream(E), error)
}


I find that subtle and I prefer my Go to be less subtle.  Perhaps we can reuse the "interface" keyword in that context as it's not currently legal usage or syntax:

type Sub(type E) interface {
    interface(Stream(E))
    SubStream(uint) (Stream(E), error)
}

I think that would be more immediately obvious to a less-proficient or new Go developer (or someone just skimming the code) than knowing whether bare parenthesis are allowed or disallowed for elements in that context. I also see no harm in allowing non-parameterized interfaces with the same syntax.  Note: the second usage, as a function parameter, does not need the disambiguation and is clear enough as is and I would not suggest we allow the use of "interface" there.

Regards,
David

Axel Wagner

unread,
Jul 31, 2019, 8:45:07 AM7/31/19
to David Golden, jadr2...@gmail.com, golang-dev
IMO this creates even more subtle rules.
1. An instantiated generic interface-type *has* to have the interface-keyword
2. A method *must not* have the interface-keyword
3. A non-generic interface-type… well. May have none (for compatibility) at least. Should we forbid it? That seems to create a weird conflict with 1. If we make it optional, that means it's now possible to encounter either and you have to understand why.

IMO at that point just disallowing embedding of instantiated generic types altogether (requiring to spell out the methods) would make more sense.

I think even more problematic than asking how this can be spelled out, is that it will continue to be legal to *write*

type A interface {
    B(T)
    // Whatever
}

in the future, to refer to a method with name B and argument T. Like, even if B is an identifier that is already declared and you don't even use type-parameters in your own declaration, this will continue to be valid. But it means that people might write that *intending* to instead embed an instantiated generic interface and they would then be confused, why they can't use a type as an A or why they can't call method Foo (declared in the generic interface B) on an A. It'll be prudent to come up with good error messages in those cases.

Joshua Boelter

unread,
Jul 31, 2019, 12:33:15 PM7/31/19
to Ian Lance Taylor, golang-dev
On Tue, Jul 30, 2019 at 9:16 PM Ian Lance Taylor <ia...@golang.org> wrote:
On Tue, Jul 30, 2019 at 4:56 PM Joshua Boelter <joshua....@gmail.com> wrote:
>
> Kudos again to the latest proposal.  Spent a bit of time reading and re-reading the contracts.
>
> Apologies if I missed it in the spec, but was there a rationale to not allow a contract to specify that a type conform to an interface? This would make it easier to declare a contract against interfaces developers are already familiar with or part of existing package.
>
> The example in the spec could be written:
>
> contract IOCloser(S) {
> S io.Reader, io.Writer
> S io.Closer
> }

The question is whether that means that S must have an underlying type
of io.Reader, or whether S must implement io.Reader.  And in
particular how should we handle the builtin error interface.  It may
be that what you suggest--that we treat an interface type as meaning
that the type argument must implement the interface, rather than that
the type argument must be the interface--makes the most sense.

I was thinking of this in terms of shorthand to specify the methods (i.e. implements). In the absence of template specialization, I was assuming type assertions to an interface or type would be satisfactory (Reader vs Writer above).  Interesting question on the notion of constraining a template to a specific implementation of an interface. Strikes me as an odd use case that wouldn't actually require templates however.
 
More generally, there is clearly some degree of overlap between a
contract with one type parameter that only lists methods that do not
mention the type parameter, and an interface type.  Of course, they
are different, too: you can't have a value of a contract "type".  But
we could in principle decide that you can use an interface as a
contract.  I don't know how often that would be useful.  But if we
decide to go in that direction, it might affect the case you describe
above.

I do like the notion of a contract and the ability to reuse it. It also make compiler errors consistent by directing the developer to the contract constraint that wasn't met. The inclusion of types vs just methods also seems to justify a consistent approach.

<snip>
 
> Which then led me to below; thoughts on utilizing a boolean syntax to allow a more expressive contract?

Let's see if there is any real use for that first.

The implicit OR/AND tripped me up the first few reads through the syntax. An explicit boolean syntax may improve the readability of a contract and also make clear what may be otherwise inconsistent w/ other languages. The readability was a suprise to me and the comma in the IOCloser was subtle even w/ the comment. Does beg the question of how complex it can/should be (nesting, '!').

C# uses a where syntax that is an aggregate of the comma separated types (AND). Dlang uses an if syntax for concepts (constraints). Rust traits also appear similiar to constraints.  
 
class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
int Foo(T)(T t)
    if (isAddable!(T) && isMultipliable!(T))  

Sebastien Binet

unread,
Jul 31, 2019, 2:30:53 PM7/31/19
to Ian Lance Taylor, golang-dev
hi there,

On Sat, Jul 27, 2019 at 3:44 PM Ian Lance Taylor <ia...@golang.org> wrote:
The updated generics design that I mentioned in my talk at Gophercon
(live blog at https://about.sourcegraph.com/go/gophercon-2019-generics-in-go)
can now be seen at
https://go.googlesource.com/proposal/+/master/design/go2draft-contracts.md

a couple of years ago, in a similar thread, you (or perhaps Russ?) alluded to generics being perhaps a way to solve this problem:

package json

func (dec *Decoder) Decode(v interface{}) error { ... }

there is no way, currently, to ensure at compile-time that user code will in effect call Unmarshal with a pointer to some value:

dec := json.NewDecoder(r)
var v Data
err := dec.Decode(v) // oops.

I've noticed the proposed additional syntax to make sure a method is defined on a pointer-receiver:

+```Go
+contract setter(T) {
+ *T Set(string)
+}
+```
Do you think something like this could be done for ensuring an argument is a pointer?

what would be the proposed contract to make sure "decoder"-like methods could be more compile-time safe?

cheers,
-s

.


Ian

--
You received this message because you are subscribed to the Google Groups "golang-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.

Axel Wagner

unread,
Jul 31, 2019, 2:35:56 PM7/31/19
to Sebastien Binet, Ian Lance Taylor, golang-dev
Wouldn't it be enough to use
func Decode(type T someContract) (v *T) { … }
for that?

Ian Lance Taylor

unread,
Jul 31, 2019, 2:38:53 PM7/31/19
to Sebastien Binet, golang-dev
On Wed, Jul 31, 2019 at 11:30 AM Sebastien Binet <bi...@cern.ch> wrote:
>
> On Sat, Jul 27, 2019 at 3:44 PM Ian Lance Taylor <ia...@golang.org> wrote:
>>
>> The updated generics design that I mentioned in my talk at Gophercon
>> (live blog at https://about.sourcegraph.com/go/gophercon-2019-generics-in-go)
>> can now be seen at
>> https://go.googlesource.com/proposal/+/master/design/go2draft-contracts.md
>
>
> a couple of years ago, in a similar thread, you (or perhaps Russ?) alluded to generics being perhaps a way to solve this problem:
>
> package json
>
> func (dec *Decoder) Decode(v interface{}) error { ... }
>
> there is no way, currently, to ensure at compile-time that user code will in effect call Unmarshal with a pointer to some value:
>
> dec := json.NewDecoder(r)
> var v Data
> err := dec.Decode(v) // oops.
>
> I've noticed the proposed additional syntax to make sure a method is defined on a pointer-receiver:
>
> +```Go
>
> +contract setter(T) {
> + *T Set(string)
> +}
> +```
>
> Do you think something like this could be done for ensuring an argument is a pointer?
>
> what would be the proposed contract to make sure "decoder"-like methods could be more compile-time safe?

The current design draft does not support type parameters for methods.
Still, we could do something like

func JSONDecode(type T)(decoder *json.Decoder, val *T) error {
return decoder.Decode(val)
}

Ian

Sebastien Binet

unread,
Jul 31, 2019, 3:25:49 PM7/31/19
to Ian Lance Taylor, golang-dev
ah, right.

thanks,
-s

jtc...@gmail.com

unread,
Aug 1, 2019, 5:29:27 PM8/1/19
to golang-dev

Why not, e.g.:

 func (type T Ord) less(a, b T) bool { }

It avoids the triple parenthesized clauses, can't conflict with methods, makes the type arguments more prominent, and seem unambigouos to parse.
Downsides are making the function name less prominent and, maybe looking too much like a method. (I think the two-keyword func and type sequence makes the latter not a problem.)

Jonathan

Robert Griesemer

unread,
Aug 1, 2019, 5:51:34 PM8/1/19
to jtc...@gmail.com, golang-dev
In cases where type inference won't work, say for:

   func (type T) new() *T { ... }

(using your suggested notation), one would (presumably) still call this function as

   new(int)()

and then the parameter passing notation doesn't match the function declaration. Right now it does. The type(s) is just an extra parameter, it makes a lot of sense to pass it in "parameter place". One might argue that one should call this via

   int.new()

i.e. the type acts like a receiver of sorts. But that gets problematic if there's more than one type parameter:

   (int, float32).f()

cannot easily fit into the existing syntax.

More generally, if there's one thing we're pretty happy with at the moment it is the syntax for type parameters - it's the one thing we probably have spent most time on actually implementing and testing (and we have tried <>, [], and others).

May I suggest that we don't bikeshed this part of the design at this point - there's bigger fish to fry. This part we can always revisit once everything else is solid. Otherwise we're just going in circles.

Thanks,
- gri


--
You received this message because you are subscribed to the Google Groups "golang-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.

jtc...@gmail.com

unread,
Aug 1, 2019, 6:03:50 PM8/1/19
to golang-dev


On Thursday, August 1, 2019 at 5:51:34 PM UTC-4, gri wrote:
In cases where type inference won't work, say for:

May I suggest that we don't bikeshed this part of the design at this point - there's bigger fish to fry. This part we can always revisit once everything else is solid. Otherwise we're just going in circles.

Point taken, sorry for the drive-by comment.  I really like the proposal and don't want to slow the arrival of generic slice and channel library packages, which for me will be of immediate and great utility.

goo...@shyxormz.net

unread,
Aug 5, 2019, 12:45:43 PM8/5/19
to golang-dev
Hi there, I really like most of the current draft! Some of the syntax, though, keeps me wondering if it really should be left as suggested. Having
thought about and discussed with my colleague, here are my thoughts:

My main gripe lies with the way that methods and types can be mixed in a contract. Just like Andy and Joshua, I, too, would like something
contract IOCloser(S) {
  io
.Reader(S), io.Writer(S)
  io
.Closer(S)
}
. In my opinion, having a contract only specify a list of "types" improves consistency and reduces the need to introduce a new, subtle difference
in syntax, at the cost of a few more keystrokes. Instead of using the syntax in Joshua's design, I think using something close to the draft would
be a bit more consistent:
contract IOCloser1(T) {
  T io
.Reader, io.Writer
  T io
.Closer
}

contract
IOCloser2(T) {
  T
interface {
     
Read([]byte) (int, error)
     
Close() error
   
}, interface {
     
Write([]byte) (int, error)
     
Close() error
   
}
}

contract
IOCloser3(T) {
  T io
.Reader, interface {
     
Write([]byte) (int, error)
   
}
  T
interface {
     
Close() error
   
}
  T
[]byte, string // non-sensical, just to show that T must also be []byte or string
}

In addition, I think that using parentheses to denote the generic types makes the code a bit harder to read, for example in
LookupAsString(MyInt)(m, 0)
, it's a bit off-throwing to encounter something that looks like a function accepting a type and returning another function. "MyInt"
might very well also be confused with a variable that is in scope, especially if it were an unexported type instead. I was
wondering if it might be worthwhile to make it a bit more obvious by having instantiations require the "type" keyword, for example:
func Ranger(type T)() (*Sender(type T), *Receiver(type T)) {
    c
:= make(chan T)
    d
:= make(chan bool)
    s
:= &Sender(type T){values: c, done: d}
    r
:= &Receiver(type T){values: c, done: d}
    runtime
.SetFinalizer(r, r.finalize)
   
return s, r
}

type
Sender(type T) struct {
    values chan
<- T
   
done <-chan bool
}

func
(s *Sender(type T)) Send(v T) bool {
   
select {
   
case s.values <- v:
       
return true
   
case <-s.done:
       
return false
   
}
}

func
(s *Sender(type T)) Close() {
    close
(s.values)
}

type
Receiver(type T) struct {
    values
<-chan T
   
done chan<- bool
}

func
(r *Receiver(type T)) Next() (T, bool) {
    v
, ok := <-r.values
   
return v, ok
}

func
(r *Receiver(type T)) finalize() {
    close
(r.done)
}
With my limited understanding of the parser this might also make it possible to use "<T>" instead of "(T)",
and thus we hopefully wouldn't need to worry about all those cases of ambiguity.

Anyway, these are just my stupid thoughts on this, but if it's not too much of a hassle, please let me know what you think.

Patrick

Ian Lance Taylor

unread,
Aug 5, 2019, 1:00:19 PM8/5/19
to goo...@shyxormz.net, golang-dev
On Mon, Aug 5, 2019 at 9:45 AM <goo...@shyxormz.net> wrote:
>
> Hi there, I really like most of the current draft! Some of the syntax, though, keeps me wondering if it really should be left as suggested. Having
> thought about and discussed with my colleague, here are my thoughts:
>
> My main gripe lies with the way that methods and types can be mixed in a contract. Just like Andy and Joshua, I, too, would like something
> along the lines of https://gist.github.com/andybalholm/8165da83c10a48e56590c96542e93ff2#structural-contracts or
>>
>> contract IOCloser(S) {
>> io.Reader(S), io.Writer(S)
>> io.Closer(S)
>> }
>
> . In my opinion, having a contract only specify a list of "types" improves consistency and reduces the need to introduce a new, subtle difference
> in syntax, at the cost of a few more keystrokes.

Note that if we take this approach it will not be possible for a
contract to say that a type parameter must be an interface type.
Instead, any use of an interface type would be interpreted as meaning
that the type parameter must implement the interface type. Perhaps
that is OK.


> In addition, I think that using parentheses to denote the generic types makes the code a bit harder to read, for example in
>
> LookupAsString(MyInt)(m, 0)
>
> , it's a bit off-throwing to encounter something that looks like a function accepting a type and returning another function. "MyInt"
> might very well also be confused with a variable that is in scope, especially if it were an unexported type instead. I was
> wondering if it might be worthwhile to make it a bit more obvious by having instantiations require the "type" keyword,

Let's see how the current approach works with real code before we
start changing it.

Ian

Zach Easey

unread,
Aug 5, 2019, 1:18:26 PM8/5/19
to golang-dev
I support simplifying contracts to leverage interfaces for behavior, as it lets contracts be more orthogonal with interfaces.
An interface is an interesting type because it has an underlying concrete type. Which determines the behavior of an interface.
With the way implicit interfaces work, this should be OK.


Best,
Zach

Guy Brandwine

unread,
Aug 5, 2019, 5:40:12 PM8/5/19
to Zach Easey, golang-dev
After reading the updated draft, and (trying to keep up with) the correspondence, my first hunch is that we should consider two groups of developers:
1. Generic features/lib developers - those who would write "Sort", "Reduce" , "Filter" , "ToMap" etc, most of the comments and questions here were from "their" perspective (And I myself will later on add some)
2. The "users" of "Sort", "Reduce" , "Filter" , "ToMap", whether in a public library or even in an internal team.
And I believe that the group2 is much larger (in numbers...) than the group1.  

So, when I once wrote a function "MergeSlices" which takes two whatever slices 
mexicoLangs := {ID:5 , Name : "Spanish", ID:1, Name:"English"}
canadaLangs := {ID:1, Name:"English", ID:8 , Name : "French"}
after mergeSlice(mexicoLangs, canadaLangs, "ID") , 
I would get:
{ID:1, Name:"English", ID:5 , Name : "Spanish", ID:8 , Name : "French"}

This was (sadly) done using reflection, but the result was overall good , with some downsides:
1. type checks (that both interface vars are slices and of the same elem type) are done in runtime.
    which also meant autocomplete was only "half" helpful (BTW, I did not see much fmt/vet oriented notes on the draft, and although it is just a draft, I think it may be of an overall impact) 
    which also needed a runtime "and what if the slices do not have 'ID' filed ?
2. minor performance issues.
etc ...
 
In the above case, All and all group2 enjoyed the implementation instead of writing a "double for loop + if == + s :=  append(s ..."  etc per each slice of structs, and the cost of the issues above was not too dear.

I think that considering group2 - what would they benefit from "Generics written 'Sort' , 'Reduce' , 'Filter' , 'ToMap' " etc vs existing solutions (use libs, write your own loop, reflection god forbit etc) would help us:
1. Rate the solution (my gutt feeling is good about this one, but I did not really do my homework yet)
2. See which parts are repeatedly used and based on that decide if some more challenging parts can be detained/excluded for now.
3. See Which parts are still challenging/missing or end up in awkward coding or lowers performance benchmark, and decide if there's anything important missing in order to support good tools created by group1    

Guy





 

--
You received this message because you are subscribed to the Google Groups "golang-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.

Robert Engels

unread,
Aug 5, 2019, 6:02:32 PM8/5/19
to Guy Brandwine, Zach Easey, golang-dev
This is the reason that I proposed a more specific enhancement to the make syntax to allow the existing map and slice syntax with different implementations behind the scenes. The make would have an optional hints that control the implementation used, possibly deferring to an Go implementation using reflection or some other means. 

I still believe the vast majority of usage of generics is in collections (and provided the java based research to back this up). 

I think this solution would be a far better fit for the Go ecosystem. 

Dan Kortschak

unread,
Aug 5, 2019, 8:44:03 PM8/5/19
to golang-dev
I'm following this up because I've not had an answer about how this would be addressed without this addition, and I have received a query off-list about how the complex(T, T) addition would help. I'll partially quote the corresponded for that.

Person wrote:
>
> I don't understand what

>
> contract numeric {
>         T float32, float64
> }
>
> func conv(type T numeric) complex(T,T) {
>         ...
> }
>
> accomplishes. Especially in terms of, say, storing matrices inside
> Eigen.

```
The contract that you quote is correct, but the use of it is not
(without further elucidation).

Here is a complete use example:

// sqrt returns the sqrt of the possibly negative x.
func sqrt(type T numeric)(x T) complex(T, T) {
        // Assume a generic cmplx math package exists.
        return cmplx.Sqrt(complex(T, T))(complex(x, 0))
}

This is necessary because there is no other way to infer the complex
type that corresponds to a float without this operation. You cannot
take a float32 and say, "OK this is going to return a complex64."

In the context of the generalised Eigen decomposition where we have a
float32/float64 input in the general case and have a numeric (in the
sense above) generic Eigen and then have appropriately typed
LeftVectorsTo, Values and VectorsTo methods without changing the method
signatures to take a destination type parameter (and even that is
precluded under the current model).
```

Can someone explain to me how the simple sqrt case above would be implemented under the current model? Would it require a dummy return parameter type to be specified? If it would this is just another moving part to get wrong.

In the case of the Eigen decomposition, from what I understand there is *no way* to do the needed type parameterisation because of the a explicit exclusion of receiver type parameterisation for this context (correct me if I am wrong on this). The addition of a complex(T, T) obviates that need since everything is now derived from the receiver parameter type. I would truly love to see how an efficient generic ED can be implemented under the current model.

Dan

Robert Engels

unread,
Aug 5, 2019, 9:00:45 PM8/5/19
to Dan Kortschak, golang-dev
Dan,

Did you write a proposal anywhere outlining why you need generics for the examples you cite? I only ask because the cmplx package only deals with complex128 for a reason - it’s faster. So in an environment where you have float32 or float64 wouldn’t a interface/callback based design work just as well since they should all be converted to complex128 during calculation phase. 

You really only have 2 types ever to deal with. Seems generics is not required. 
--
You received this message because you are subscribed to the Google Groups "golang-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.

Ian Lance Taylor

unread,
Aug 6, 2019, 12:33:31 AM8/6/19
to Dan Kortschak, golang-dev
On Mon, Aug 5, 2019 at 5:44 PM Dan Kortschak <d...@kortschak.io> wrote:
>
> I'm following this up because I've not had an answer about how this would be addressed without this addition, and I have received a query off-list about how the complex(T, T) addition would help. I'll partially quote the corresponded for that.


You say that your question didn't get an answer, but it seems to me
that it did, in the responses that you quote below.

In particular, let me repeat: "Let's see what some code looks like
before we start adding features like this. This is a kind of feature
that can likely be added later if it is needed."

A lot of floating point code is not worth writing generics for,
because the most efficient implementation differs depending on the
size. When the implementation does not differ, people can write
complex64(F(complex128(c))

Ideal? No. Can be improved later if it is needed? Yes.

I understand that this issue is important for you, but I think we have
to see how important it seems relative to other issues addressed by
generics.

Ian

Viktor Kojouharov

unread,
Aug 6, 2019, 1:23:31 AM8/6/19
to golang-dev
I played around with some code in order to see what it would look like with generics. The following gist contains what the popular bounded pipeline might look like as a generic implementation, as well as what the a user of that code would have to write :

https://gist.github.com/urandom/40ea1b3fa41be88fdbd4fd81c044e02d

Unfortunately, the following example had no need of a specific contact, though it does heavily rely on parametric types, at least for the implementation. And if I understood the draft correctly, a user would not need to specify the types, since they would be inferred from the function arguments. All in all, the user might not even be aware of any additional complexity under the hood, while still reaping the benefits of compile time safety and a bit faster runtime.

Dan Kortschak

unread,
Aug 6, 2019, 8:06:34 PM8/6/19
to Ian Lance Taylor, golang-dev
Mmm. It was a Claytons answer that I asked a clarification on. I got no
response to that request for clarification.

At the moment the design allows construction of parameterised types for
all other compound types; structs, slices and maps. complex64 and
complex128 are compound types, being struct { re, im T } under the
covers. So for every other type that is derived from a collection of
simple types, I can create a generic construction for it, bot for
complex. This makes the absence of a type-parameterised type
constructor for the complex types a special case, rather than the
addition being an extra feature.

It's true that efficient implementations differ for different types in
numerical code. This is handled by type switching, which is in the
proposal. This does not diminish the value of adding support for
generics that would be useful in the numerics space. In LAPACK and BLAS
for example the broad architecture of routines is common between single
and double precision implementations, the inner loop kernels differ. At
the moment we support single and double precision BLAS in Gonum for
both real and complex floats. This must be done by code generation. It
would greatly simplify the code base and help testing if we could just
rely on generics. The situation is similar in LAPACK where our code
does not support single precision but could.

Addressing the comment by others that we should just work in double
precision because it's faster. Sure! I do. However, there is more than
one metric for what makes efficient use of resources. single precision
arithmetic is still commonly used for a variety of reason, one being
that sometimes space is more important than speed. Another that single
precision GPU computation is a thing, particularly in the ML space. We
get requests for handling single precision arithmetic because of these
issues.

Dan

Dan Kortschak

unread,
Oct 4, 2019, 5:56:26 PM10/4/19
to golang-dev
*ping*

Below there's a reasoned argument for the rationale behind including
this feature; it explains the fact that there is no other way to
achieve a solution to the problem and that inclusion of the feature is
actually completely consistent with language as a whole — that not
including it is essentially a special case.

I wrote this because I would like to see what the thinking is around
this. I can't get that if there is no reply.

Dan

Ian Lance Taylor

unread,
Oct 4, 2019, 6:00:36 PM10/4/19
to Dan Kortschak, golang-dev
This is not something that we need to decide today. Let's see what
some code looks like first. This is a kind of issue that can likely
be addressed later if it turns out to be important.

Ian
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-dev/b347eb1176633807ce38a7aca3233ee90295ff60.camel%40kortschak.io.

Dan Kortschak

unread,
Oct 4, 2019, 6:07:03 PM10/4/19
to Ian Lance Taylor, golang-dev
OK. That's the point. There can not be code to do what I'm describing
under the current proposal.

I'm sorry, I'm giving up on this.

Ian Lance Taylor

unread,
Oct 4, 2019, 6:55:20 PM10/4/19
to Dan Kortschak, golang-dev
On Fri, Oct 4, 2019 at 3:06 PM Dan Kortschak <d...@kortschak.io> wrote:
>
> OK. That's the point. There can not be code to do what I'm describing
> under the current proposal.

Yes. I understand that.

I'm saying: we don't have any working generics code at all right now.
We don't know how the design is going to change as we gain more
experience. We don't even know whether the design will be accepted.
We might discover that we need something completely different.

We do not need to hash out every detail of the current design draft
today. We're a long way from that point. A very long way.

Ian

Dan Kortschak

unread,
Oct 4, 2019, 7:35:10 PM10/4/19
to Ian Lance Taylor, golang-dev
That is indeed true, however, failing to have discussions does not move
things forwards.

I wrote my questions, which as I pointed out in a previous mail have
not been answered, to try to get an understanding of people's views.
Not answering, and not making a note of the suggestion means that it is
likely to get lost. In addition to that it leads to tensions and
frustration.

This is not new here; in my view there is a fairly long history of
failure of the Go team to engage in discussions around things that
others can see but fall just over the horizon of the Go team's scope.
In some cases these people's visions have (much) later been seen as
valuable (though in an anonymous way without reference to the original
creator) and adopted by the project, but at the time been dismisses,
ignored or in some cases treated rudely (in one particular case that
comes to mind, really rudely) by the project.

This has been raised before by others and I imagine that it will be
raised again. It's unfortunate since the project (both the people and
the technology) are for the vast majority of the time positive, but the
communication issues that I raise here add an unpleasant friction that
can significantly sour sentiment toward the project, even with people
who have (sometimes years) of involvement in the project and the
ecosystem around it.

I think this is a sad situation.

Dan

Ian Lance Taylor

unread,
Oct 4, 2019, 7:59:17 PM10/4/19
to Dan Kortschak, golang-dev
On Fri, Oct 4, 2019 at 4:34 PM Dan Kortschak <d...@kortschak.io> wrote:
>
> That is indeed true, however, failing to have discussions does not move
> things forwards.
>
> I wrote my questions, which as I pointed out in a previous mail have
> not been answered, to try to get an understanding of people's views.
> Not answering, and not making a note of the suggestion means that it is
> likely to get lost. In addition to that it leads to tensions and
> frustration.
>
> This is not new here; in my view there is a fairly long history of
> failure of the Go team to engage in discussions around things that
> others can see but fall just over the horizon of the Go team's scope.
> In some cases these people's visions have (much) later been seen as
> valuable (though in an anonymous way without reference to the original
> creator) and adopted by the project, but at the time been dismisses,
> ignored or in some cases treated rudely (in one particular case that
> comes to mind, really rudely) by the project.
>
> This has been raised before by others and I imagine that it will be
> raised again. It's unfortunate since the project (both the people and
> the technology) are for the vast majority of the time positive, but the
> communication issues that I raise here add an unpleasant friction that
> can significantly sour sentiment toward the project, even with people
> who have (sometimes years) of involvement in the project and the
> ecosystem around it.
>
> I think this is a sad situation.

I'm sorry you feel that way but I don't understand what we can do to help.

I am not dismissing or ignoring the issue, and I hope that I'm not
being rude. I'm saying that we don't have to pin down every detail of
the generics draft today and that it is premature to try to do so. I
will add that pinning down these details takes time away from work
that at this moment I personally consider to be more important, namely
trying to implement something that people can test. We could spend a
month discussing how to handle complex numbers. I've already spent
ten years discussing details of generics back and forth and over and
under. Unless there is some reason to believe that complex numbers
expose a fatal hole in the design draft--and I haven't yet seen a
reason to believe that--then pinning down the details will be a
discussion that will almost certainly turn out to be wasted time as
the design draft changes.

I understand and accept that your judgement on this issue is
different. I have no intention of stopping you from discussing
complex numbers with anybody else who is interested. And likely that
will lead to something that we can incorporate in the design draft if
it still makes sense as we move forward. But I don't see why it is a
discussion that needs my voice. I'm not saying "no." I'm saying
that, as far as I'm concerned, "we can discuss this later."

Lucio De Re

unread,
Oct 5, 2019, 1:00:56 AM10/5/19
to Dan Kortschak, Ian Lance Taylor, golang-dev
On 8/7/19, Dan Kortschak <d...@kortschak.io> wrote:
>
> At the moment the design allows construction of parameterised types for
> all other compound types; structs, slices and maps. complex64 and
> complex128 are compound types, being struct { re, im T } under the
> covers. So for every other type that is derived from a collection of
> simple types, I can create a generic construction for it, bot for
> complex. This makes the absence of a type-parameterised type
> constructor for the complex types a special case, rather than the
> addition being an extra feature.
>
Dan, I'm answering this without the deeper understanding of the issue
I believe your point deserves, but I note that Ian, in his amazing
politeness, does not address what perhaps needs an external
perspective to note, namely that if complex64 and complex128 are not
addressed directly because of some complicated failure, they certainly
could be if they were unrolled into their underlying representation.

As stated, it is a shot in the dark from me, so all I'm really curious
to hear is whether such a possibility has been given consideration at
all and what would it look like in practice (I suppose I'm talking
"reflection", something I have yet to explore, years down the line).

I'd also like to add that numeric types are open ended. We're probably
never going to have "intrinsic" quaternions (the scary quotes
suggesting that I'm not at all convinced that intrinsic is actually a
valid property to assign to numeric types - and Intel's separate FPUs
have emphasised this since 8088 days), but we may not be very far from
float4096 and we may or may not even stop there.

As Ian explained to me, a while back, a different Go is not on the
table, but incomplete generics for Go are, in my opinion, the only
possible outcome of this quest. We just need to relegate "outside the
Pale" those aspects that, in being abandoned, gain the most for the
ones that remain "inside".

Just an opinion, of course.

Lucio.

Dan Kortschak

unread,
Oct 5, 2019, 2:14:23 AM10/5/19
to Lucio De Re, Ian Lance Taylor, golang-dev
The situation is that if complex numbers were not in the language, the
solution would actually be easier. We do have quaternions (forked from
Matt's April fools joke in 2017) in Gonum. These will be easier to deal
with under the most recent generics proposal than the problems that I'm
talking about with complex values, even though the exact same
fundamental mathematics issues apply, purely because we have generic
access to the field types in the quaternion structs, but don't with the
complex parts of the complex types.

This is essentially what you say in your first sentence.

The thing that I have proposed is to be able to treat the complex types
in much the same way that we can treat structs. It is entirely
consistent with the way the language works and the way that generics
proposal proposes to change it. Not doing this essentially requires a
contorted special casing of complex values in order to exclude them
from being able to be destructured as type parameters the way that
structs can.

I'm not proposing a different Go, I'm asking about the possibility of
an internally consistent Go in the future. In response, I've been told
that there are other ways to do what I'm asking about, but then not had
them shown, ... and then been faced with extended non-response after
querying this (only broken by asking again¸ twice).

Dan

Brendan Tracey

unread,
Oct 5, 2019, 3:02:06 AM10/5/19
to golang-dev

I understand and accept that your judgement on this issue is
different.  I have no intention of stopping you from discussing
complex numbers with anybody else who is interested.  And likely that
will lead to something that we can incorporate in the design draft if
it still makes sense as we move forward.  But I don't see why it is a
discussion that needs my voice.  I'm not saying "no."  I'm saying
that, as far as I'm concerned, "we can discuss this later."

Ian


I think part of the misunderstanding here is your use of the phrase "Let's see what
some code looks like first.". This is often stated in bug reports and feature requests, and often times implicitly means "We're not convinced this is a real problem, please show us real code that has been affected". Under this definition, Dan believes he has shown you a case affected, and a simple solution to improve the proposal. In this case it seems to instead mean "We don't know if we're going to scrap this proposal altogether, so we don't want to consider any changes until we can try it out". Is my understanding correct?
 

Lucio De Re

unread,
Oct 5, 2019, 3:08:36 AM10/5/19
to Dan Kortschak, Ian Lance Taylor, golang-dev
On 10/5/19, Dan Kortschak <d...@kortschak.io> wrote:
>
I wish I could keep more close contact with Go, but my world is a
little too remote. I agree with your sentiments, entirely, but...

> I'm not proposing a different Go, I'm asking about the possibility of
> an internally consistent Go in the future. In response, I've been told
> that there are other ways to do what I'm asking about, but then not had
> them shown, ... and then been faced with extended non-response after
> querying this (only broken by asking again¸ twice).
>
I was the one who proposed a different Go, at the time, and I think
you're heading in the same direction: floating point numbers, variable
and varied length integers and a slew of mathematical constructs and
abstractions (think "time" as an example of a number system with
various "units") just don't fit in with a strongly typed language, the
language intentionally restricts expressing the underlying
abstraction,

My suggestion to Ian, only half-serious, was to remove all "typed"
numerics and invoke an interpreter whenever operations on anything
that is not part of some restricted set of necessary counters, say, or
indexes, something like APL, say (we've moved way past the 1960s,
though, so an interpreter would exceed APL considerably). That's "not
Go".

I happen to like the idea, still. Would it be a lesser Go? The answer,
really, is whether someone who badly wants such a thing or similar, is
prepared and positioned to take a sufficiently large community with
them. It might happen, it sure hasn't happened yet. But then Go had
never happened before, either.

Lucio.

PS: I wish I had more to contribute, but I was never an academic and
in my dotage I would be doing everyone an injustice to present myself
as anything more than a rank amateur.
It is loading more messages.
0 new messages