Generics and parentheses

20241 views
Skip to first unread message

Robert Griesemer

unread,
Jul 14, 2020, 5:56:01 PM7/14/20
to golang-nuts

We have received a variety of feedback on the generics draft design (blog). Thanks to everyone who took the time to read it, play with generics code in the playground, file issues, and send us their thoughts.


Not unexpectedly, many people raised concerns about the syntax, specifically the choice of parentheses for type parameter declarations and generic type and function instantiations.


A typical computer keyboard provides four easily accessible pairs of single-character symmetrical "brackets": parentheses ( and ), square brackets [ and ], curly braces { and }, and angle brackets < and >. Go uses curly braces to delineate code blocks, composite literals, and some composite types, making it virtually impossible to use them for generics without severe syntactic problems. Angle brackets require unbounded parser look-ahead or type information in certain situations (see the end of this e-mail for an example). This leaves us with parentheses and square brackets. Unadorned square brackets cause ambiguities in type declarations of arrays and slices, and to a lesser extent when parsing index expressions. Thus, early on in the design, we settled on parentheses as they seemed to provide a Go-like feel and appeared to have the fewest problems.


As it turned out, to make parentheses work well and for backward-compatibility, we had to introduce the type keyword in type parameter lists. Eventually, we found additional parsing ambiguities in parameter lists, composite literals, and embedded types which required more parentheses to resolve them. Still, we decided to proceed with parentheses in order to focus on the bigger design issues.


The time has come to revisit this early decision. If square brackets alone are used to declare type parameters, the array declaration


type A [N]E


cannot be distinguished from the generic type declaration


type A[N] E


But if we are comfortable with the extra type keyword, the ambiguity disappears:


type A[type N] E


(When we originally dismissed square brackets, the type keyword was not yet on the table.)


Furthermore, the ambiguities that arise with parentheses appear not to arise with square brackets. Here are some examples where extra parentheses are not needed with square brackets:


using ()                 using []

func f((T(int))          func f(T[int])

struct{ (T(int)) }       struct{ T[int] }

interface{ (T(int)) }    interface{ T[int] }

[](T(int)){}             []T[int]{}


To test this better understanding, and to get a feel for this alternative notation, we will begin to make changes to our prototype implementation such that it accepts either parentheses or square brackets (only one or the other) in a generic Go package. Those changes will first appear as commits to the dev.go2go branch, and eventually in the playground.


If square brackets don't lead to unforeseen issues, we have another fully explored notation to choose from, which will allow us to make a more informed decision.


- gri, iant


PS: For ambiguities with angle brackets consider the assignment


a, b = w < x, y > (z)


Without type information, it is impossible to decide whether the right-hand side of the assignment is a pair of expressions


(w < x), (y > (z))


or whether it is a generic function invocation that returns two result values


(w<x, y>)(z)


In Go, type information is not available at compile time. For instance, in this case, any of the identifiers may be declared in another file that has not even been parsed yet.


gri

unread,
Jul 14, 2020, 6:29:30 PM7/14/20
to golang-nuts
Correction: The last paragraph in the post below should have said: "In Go, type information is not available at parse time". (Of course, type information is available at compile time.)

Harald Weidner

unread,
Jul 14, 2020, 6:31:55 PM7/14/20
to Robert Griesemer, golang-nuts
Hello,

> A typical computer keyboard provides four easily accessible pairs of
> single-character symmetrical "brackets": parentheses ( and ), square
> brackets [ and ], curly braces { and }, and angle brackets < and >. Go uses
> curly braces to delineate code blocks, composite literals, and some
> composite types, making it virtually impossible to use them for generics
> without severe syntactic problems. Angle brackets require unbounded parser
> look-ahead or type information in certain situations (see the end of this
> e-mail for an example). This leaves us with parentheses and square
> brackets.

Another option would be the introduction of a new two-letter
"bracket" operator, for example <: and :> . This could be parsed
without symbol/type information and even without the "type" keyword.

That said, I'm fine with any syntax. Thank you for your work on
generics!

Regards,
Harald

Doug

unread,
Jul 14, 2020, 9:46:20 PM7/14/20
to golang-nuts
The square bracket-based syntax feels much more readable to me, so it's a welcome change!

Based on my dev background I'm still partial to angle brackets and had an idea for disambiguating function calls. We could put the type information as the first data inside the parens, so a generic function invocation would become:
w(<x,y> z)

This may not be the right forum to discuss the idea, but figured I'd throw it out there.

Thanks,
Doug

Watson Ladd

unread,
Jul 14, 2020, 10:44:53 PM7/14/20
to golang-nuts
Guillamets are worth consideration. They are common on European keyboards and avoid all the syntax ambiguities.

While a technical violation of compatibility by adding new reserved characters, in practice I doubt this will be an issue given the semantics of guillamets.

Sincerely,
Watson

David Riley

unread,
Jul 14, 2020, 11:04:50 PM7/14/20
to Robert Griesemer, golang-nuts
On Jul 14, 2020, at 5:54 PM, 'Robert Griesemer' via golang-nuts <golan...@googlegroups.com> wrote:
>
> But if we are comfortable with the extra type keyword, the ambiguity disappears:
>
> type A[type N] E
>
> (When we originally dismissed square brackets, the type keyword was not yet on the table.)
>
> Furthermore, the ambiguities that arise with parentheses appear not to arise with square brackets. Here are some examples where extra parentheses are not needed with square brackets:
>
> using () using []
> func f((T(int)) func f(T[int])
> struct{ (T(int)) } struct{ T[int] }
> interface{ (T(int)) } interface{ T[int] }
> [](T(int)){} []T[int]{}

Just my $0.02: I really like this. Thank you for responding to feedback, and I look forward to trying the new syntax out!


- Dave

Ahmed (OneOfOne) W.

unread,
Jul 14, 2020, 11:21:11 PM7/14/20
to golang-nuts
This feels a little better, but honestly I'm still all for angle brackets or like Watson suggested, guillamets.

fn(T1)(fn2(T2)(fn3(T3)(v))) // 1
fn[T1](fn2[T2](fn3[T3](v))) // 2
fn<T1>(fn2<T2>(fn3<T3>(v))) // 3
fn«T1»(fn2«T2»(fn3«T3»v)))  // 4

To me, with a background in C++ and Typescript and a little bit of Rust, #3 and #4 are just natural and easier to read.

Regards,
Ahmed W.

faifa...@gmail.com

unread,
Jul 14, 2020, 11:31:35 PM7/14/20
to golang-nuts
One way to distinguish between type A[N] E and type A [N]E is to be more space-sensitive and, for example, disallow whitespace between a type name and the opening square bracket when it signifies type parameters.

Dňa utorok, 14. júla 2020 23:56:01 UTC+2 gri napísal(a):

Ian Lance Taylor

unread,
Jul 14, 2020, 11:37:21 PM7/14/20
to Watson Ladd, golang-nuts
On Tue, Jul 14, 2020 at 7:45 PM Watson Ladd <watso...@gmail.com> wrote:
>
> Guillamets are worth consideration. They are common on European keyboards and avoid all the syntax ambiguities.

https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-type-parameters.md#why-not-use

Ian

Ian Lance Taylor

unread,
Jul 14, 2020, 11:39:43 PM7/14/20
to Michal Strba, golang-nuts
On Tue, Jul 14, 2020 at 8:31 PM <faifa...@gmail.com> wrote:
>
> One way to distinguish between type A[N] E and type A [N]E is to be more space-sensitive and, for example, disallow whitespace between a type name and the opening square bracket when it signifies type parameters.

I would be extremely reluctant to make the syntax depend on invisible
whitespace. The syntax already depends on line breaks, but at least
line breaks are very visible. Spacing within a line can be hard to
see and it would be quite easy to make mistakes.

Ian

mac3n

unread,
Jul 15, 2020, 12:03:56 AM7/15/20
to golang-nuts
I'm partial to [], which was used for generics in the Tartan language (1978). For example 

generic func F[Mult: Int](X:Int) y:Int: begin y := Mult * X end

Ian Lance Taylor

unread,
Jul 15, 2020, 12:14:31 AM7/15/20
to Ahmed (OneOfOne) W., golang-nuts
On Tue, Jul 14, 2020 at 8:21 PM Ahmed (OneOfOne) W. <oneo...@gmail.com> wrote:
>
> This feels a little better, but honestly I'm still all for angle brackets or like Watson suggested, guillamets.
>
> fn(T1)(fn2(T2)(fn3(T3)(v))) // 1
> fn[T1](fn2[T2](fn3[T3](v))) // 2
> fn<T1>(fn2<T2>(fn3<T3>(v))) // 3
> fn«T1»(fn2«T2»(fn3«T3»v))) // 4
>
> To me, with a background in C++ and Typescript and a little bit of Rust, #3 and #4 are just natural and easier to read.

The advantage of parentheses is that the language already uses
parentheses for lists in various places. Of course that is also the
disadvantage.

When considering something other than parentheses, I encourage people
to look for objective reasons why one syntax is better than another.
It's going to be different from other aspects of the language. So
what reason would we have for preferring one syntax over another?

For example:

Robert already gave reasons why square brackets are better than angle brackets.

The disadvantage of guillemets is that they are hard to type on many
keyboards. So to me either square brackets or angle brackets would be
better than guillemets.

The disadvantage of a two character sequence such as <: :> is that it
is more typing. So again either square brackets or angle brackets
seem to me to be better.

An example of a reason that square brackets might be a poor choice
would be ambiguous parsing, or cases where the code is harder to read.

It's true that some other languages use angle brackets, but Go already
does many things differently. That is only a minor advantage for
angle brackets. To me at least it does not outweigh the
disadvantages.

In short, please try to provide reasons for a different syntax. "It
looks good" is a valid reason, but please try to explain why it looks
better than square brackets or parentheses.

Thanks.

Ian

robert engels

unread,
Jul 15, 2020, 12:45:41 AM7/15/20
to Ian Lance Taylor, Ahmed (OneOfOne) W., golang-nuts
My opinion is that every major language (no flames please… lots of developers write lots of programs and make money doing it) that supports generics uses < > for generic types, so Go should too - since there is no reason to deviate from this other than to avoid changes to the parser. Seems better to pay this cost once - rather than every Go program that uses generics being harder to read for eternity (especially for those readers that use a lot of languages).
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CAOyqgcX-OXktNtUs0G4Ns0iEr3R2qLPpU7q1%3DrOY93%3DAO16a3g%40mail.gmail.com.

Paul Johnston

unread,
Jul 15, 2020, 1:06:24 AM7/15/20
to golang-nuts
If the generic expression <T> was always attached/constrained to the "type" or "func" keyword (rather than the type or function name), perhaps this would decrease the lookahead problems with lexing?  For example:

type<T> Point struct {
    x, y int
    data T
}

type<R,S> Transformer interface {
    Transform(R) S
}

func<T> Stringify(s []T) string {
}

type<T> Vector []T

burak serdar

unread,
Jul 15, 2020, 1:12:47 AM7/15/20
to Ian Lance Taylor, Ahmed (OneOfOne) W., golang-nuts
On Tue, Jul 14, 2020 at 10:14 PM Ian Lance Taylor <ia...@golang.org> wrote:
>
> On Tue, Jul 14, 2020 at 8:21 PM Ahmed (OneOfOne) W. <oneo...@gmail.com> wrote:
> >
> > This feels a little better, but honestly I'm still all for angle brackets or like Watson suggested, guillamets.
> >
> > fn(T1)(fn2(T2)(fn3(T3)(v))) // 1
> > fn[T1](fn2[T2](fn3[T3](v))) // 2
> > fn<T1>(fn2<T2>(fn3<T3>(v))) // 3
> > fn«T1»(fn2«T2»(fn3«T3»v))) // 4
> >
> > To me, with a background in C++ and Typescript and a little bit of Rust, #3 and #4 are just natural and easier to read.
>
> The advantage of parentheses is that the language already uses
> parentheses for lists in various places. Of course that is also the
> disadvantage.
>
> When considering something other than parentheses, I encourage people
> to look for objective reasons why one syntax is better than another.
> It's going to be different from other aspects of the language. So
> what reason would we have for preferring one syntax over another?
>
> For example:
>
> Robert already gave reasons why square brackets are better than angle brackets.
>
> The disadvantage of guillemets is that they are hard to type on many
> keyboards. So to me either square brackets or angle brackets would be
> better than guillemets.
>
> The disadvantage of a two character sequence such as <: :> is that it
> is more typing. So again either square brackets or angle brackets
> seem to me to be better.


A two-character sequence like <: T :> may be more typing, but the
two-character sequence like [[ T ]] is easier to type than that and
eliminates the ambiguity. To me, it is easier to read as well, but
that's of course subjective.

>
> An example of a reason that square brackets might be a poor choice
> would be ambiguous parsing, or cases where the code is harder to read.
>
> It's true that some other languages use angle brackets, but Go already
> does many things differently. That is only a minor advantage for
> angle brackets. To me at least it does not outweigh the
> disadvantages.
>
> In short, please try to provide reasons for a different syntax. "It
> looks good" is a valid reason, but please try to explain why it looks
> better than square brackets or parentheses.
>
> Thanks.
>
> Ian
>

Bakul Shah

unread,
Jul 15, 2020, 2:06:34 AM7/15/20
to gri, golang-nuts
I don't much like square brackets or angle brackets or guillemets. But here is a different way
of reducing the need for parentheses at least for the common case:

A proposal for fewer irritating parentheses!

One thing to note is that generic functions & types are *different* from
existing things like const, func, type, var. As such they should have their
own declaration marker. For example

gen T   type pair struct { a, b T } // contrast with type pair(type T) ...
gen T,U type W struct { a T; b U } // contrast with type W(type T, U) ...
gen T   func Print(s []T) {...} // print a slice of T

These function/type/method generics are used by *prepending* the type:

var x int pair // a pair of ints
var y (int, int pair) W // here we have to use parentheses
int Print([]int{1,2,3}) // print a slice of ints
qq := int pair pair{{1,2},{3,4}} // a pair of a pair of ints
ww := (int, int) W pair{{1,2},{3,4}}

This use may seem weird if you are used to C/C++. I find it more readable
than having to deal with extra parentheses. "int pair" clearly says a
pair of ints and so on. What is more, if in future types are allowed to be
*inferred* for generic function calls, you can simply drop the type prefix.

If there is still a parsing ambiguity, I'd suggest adding a - as in int-pair.

Additional type syntax rule:

type: ... | type generic-type| (type-list) generic-type

or

type: ... | type "-" generic-type | (type-list) "-" generic-type

FWIW I thought of this four weeks ago (June 16).

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

Fino

unread,
Jul 15, 2020, 2:19:40 AM7/15/20
to golang-nuts
I vote for <: and :> 

type more on traditional keyboard is acceptable for me, since I don't use generics much.

too much parentheses ( and ) in declaration is really hard to read, confused my mind.

square brackets [ and ] should reserve for array and slice.

and, look ahead, we are able to just put <: and :>  on keyboard!

I have made several my own custom keyboard PCB and firmware. Report 2 scan code in one click is easy and simple!  Maximum 6 scancode send once per USB HID protocol. A lot of opensource firmware is there, for ex. https://github.com/qmk/qmk_firmware.  

On touch screen, layout of keyboard can change according to different context. This may happen to desktop keyboard too, with higher cost and more complex physical structure.  

BR fino



Michael Jones

unread,
Jul 15, 2020, 2:26:23 AM7/15/20
to Bakul Shah, gri, golang-nuts
nice. "gen" here is akin to the existing forward declaration of recursive inner functions. it says, "you are about to see something special and you need to know this about it."



--
Michael T. Jones
michae...@gmail.com

Randall O'Reilly

unread,
Jul 15, 2020, 2:54:19 AM7/15/20
to robert engels, Ian Lance Taylor, Ahmed (OneOfOne) W., golang-nuts
Tagging onto this point: while Go deviates from other languages in a number of important ways, syntax at the level of things like parens, brackets, etc is not one of these key differences. It is almost entirely consistent with the "de facto" C-style choices in this respect (to the extent that people mistakenly regard it as just a subset of C). In this context, it does seem a bit of "being different for its own sake", to adopt something *other* than the < > convention for generic types.

Yes, Go should boldly Go where no language has gone before^*, but when it is basically adopting a well-established paradigm widely used in existing languages, doesn't it also make sense to adopt the well-established syntax associated with that paradigm? (although the proposed Go generics differ in some important ways from other languages, the use of type parameters is not one of them.)

While we've been asked to focus on the more "objective" facts of parsing considerations here, the above logic would suggest a different approach: if there is any way to adopt the established conventional syntax and make the parsing work, then that is what should be done. So, essentially, this puts the onus back on the parser programmers to definitively *rule out* the use of < > -- is it really that difficult / costly to do a bit of look-ahead and disambiguate the different use cases?

- Randy

* e.g., see https://github.com/golang/go/issues/39669 for a more radical departure from convention, doing away with the extra parens entirely.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/825D82DD-C595-415D-B0C6-7BE090A015C7%40ix.netcom.com.

Dan Kortschak

unread,
Jul 15, 2020, 3:01:55 AM7/15/20
to golan...@googlegroups.com
How do these deal with non-type determined uses. Say you have (by any
spelling)

gen F,I func PowN(a F, b I) F {...}

How do you specify the type of F or I when say you have untyped
constants. This still requires parens or other markings to indicate the
actual types of F and I. It seems many of the comments here complaining
about brackets and such are ignoring this aspect of the current
proposal.


Dan Kortschak

unread,
Jul 15, 2020, 3:01:55 AM7/15/20
to golan...@googlegroups.com
On Tue, 2020-07-14 at 23:53 -0700, Randall O'Reilly wrote:
> So, essentially, this puts the onus back on the parser programmers to
> definitively *rule out* the use of < > -- is it really that difficult
> / costly to do a bit of look-ahead and disambiguate the different use
> cases?

The absence of arbitrary look-ahead was from memory one of the early
design intentions of the language.


Bakul Shah

unread,
Jul 15, 2020, 3:37:15 AM7/15/20
to Dan Kortschak, golan...@googlegroups.com
As I wrote, parentheses are not needed for the common case of single type
parameter. If you have more than one, you need parentheses but note that this
is a *prefix* and any parsing ambiguities can removed by using a - if needed.
For your example it will be something like

(float64,int)PowN(1.2, 3)

It’s just a syntax change. There is zero semantic difference from the current
proposal. I just tried to get idea across - I can work out a complete set of
syntax rule changes if there is sufficient interest but the gen rule will be
something like

gen-type-decl: gen-prefix type-decl;
gen-func-decl: gen-prefix func-decl;
gen-prefix: “gen” typeX-list
typeX-list: typeX | typeX-list “,” typeX ;
typeX: NAME [constraint] ;

Max

unread,
Jul 15, 2020, 4:32:21 AM7/15/20
to golang-nuts
I think square brackets are better than parentheses for several reasons:

1. fewer parser ambiguities (see the post that started this thread) - thus fewer cases where programmers must remember "oh, this is a special case, I must put additional parentheses somewhere"
2. programmers can distinguish more easily between a template instantiation `T1[T2]` and a function call `T1(T2)`. Index expressions would have the same syntax as single-type template instantiations `T1[T2]`, but I believe they are somewhat less common than function calls in most code
3. Go already uses a similar syntax for builtin types: `map[K]V`. A generic key/value container would look like `Map[K, V]` which is pretty close

In my Go interpreter, I went a step further and implemented the syntax `T1#[T2, T3]` for generics instantiation. It may not be everybody's favorite, but removes all parsing ambiguities.

An alternative syntax, more similar to other proposals I have seen, is `T1:[T2, T3]` - it could be visually better, but I think it introduces ambiguities in `case` and labels marking `goto` destinations - both already use ':' as delimiter.

Nick Craig-Wood

unread,
Jul 15, 2020, 4:47:40 AM7/15/20
to golang-nuts
In my mind the use of [ ] clashes horribly with the array declaration syntax. When I see [int] in particular my brain says, ah array declaration of size int, what, no wait...

This makes [identifier] mean two different things depending on whether indentifier is a const integer or whether identifier is a type. I think the suffix Vs prefix makes it unambiguous but I'm not sure.

I expect I'd get used to it, but I can see this being difficult for beginners.

Jesper Louis Andersen

unread,
Jul 15, 2020, 5:21:24 AM7/15/20
to Michael Jones, Bakul Shah, gri, golang-nuts
On Wed, Jul 15, 2020 at 8:25 AM Michael Jones <michae...@gmail.com> wrote:
nice. "gen" here is akin to the existing forward declaration of recursive inner functions. it says, "you are about to see something special and you need to know this about it."


The real name is "forall", and it presents a binding site of a type-level parameter.

  forall T, type pair struct { a, b T }

This would bring the notation close to Coq.

Other solutions:

Haskell used capitalized types, String, Bool, Int, ... and lowercase names are parameters, so if you used e.g., 't' in the above, it would implicitly be a type parameter. This is out in Go, due to the capitalization already being used.

OCaml and SML uses a quote prefix to notion the parameter, e.g., the above would be written

type 'a pair = { a : 'a; b : 'a }

and likewise

type 'a option = None | Some of 'a

Yet the current proposal is fine! I think keeping the parser simple is an important goal. Solutions which have whitespace-aware parsing are not good solutions, I think, because they break that the graphical characters are the only which have meaning. They also break in subtle ways when you copy-paste code, some times without parse error but different semantics. Solutions based on < and > irks me for the lookahead needed in the parser. That notation was popularized by C++, and I think the argument that we should just copy it is weak. Go already cleaned up lots of small mistakes in the C-like syntax. If we can clean up type-level parametrization in addition, it would be really nice. We can't get any language evolution if people keep being syntax-conservative. New syntax doesn't automatically apply a gaussian blur to your eyes.


Denis Cheremisov

unread,
Jul 15, 2020, 5:22:51 AM7/15/20
to golang-nuts
Great! Those multiple parenthesis were a beat on unredable side indeed, and I always was in the pro-square party as I always found less and greater signs to be unreadable. As much as I disliked Scala in general I liked their generic syntax. This is a really good news Go will have the same.

среда, 15 июля 2020 г. в 00:56:01 UTC+3, gri:

Jesper Louis Andersen

unread,
Jul 15, 2020, 5:24:40 AM7/15/20
to Nick Craig-Wood, golang-nuts
On Wed, Jul 15, 2020 at 10:47 AM Nick Craig-Wood <ni...@craig-wood.com> wrote:
In my mind the use of [ ]  clashes horribly with the array declaration syntax. When I see [int] in particular my brain says, ah array declaration of size int, what, no wait...

This makes [identifier] mean two different things depending on whether indentifier is a const integer or whether identifier is a type. I think the suffix Vs prefix makes it unambiguous but I'm not sure.


You could make roughly the same argument in a lot of places in the language, e.g.:

The use of () clashes horribly with the expression declaration syntax. When I see f(x) in particular, my brain says, "ah! An expression!, what, no wait!"

This makes (Expr) mean two different things depending on whether it is used to disambiguate the expression AST or in a function call!

Randall O'Reilly

unread,
Jul 15, 2020, 5:28:27 AM7/15/20
to robert engels, Ian Lance Taylor, Ahmed (OneOfOne) W., golang-nuts
Sorry to perseverate and belabor the point, but here's an analogy that makes the point perhaps more strongly and succinctly:

Imagine that, for some reason, Go didn't have the array/slice element operator syntax, [ ], and it was now going to be introduced in Go2, but instead of using this established existing convention, parens were going to be used instead of [ ], due to some parser considerations. I suspect many people would feel the same way they feel about the < > syntax: this is such an established convention that it would seem crazy to not adopt it.

If other languages can get their parsers to deal with this syntax, why not Go? Certainly fast parsing is an important feature, but if anything, readability is perhaps even more important than that?

And to specifically address the example from the original email:

> PS: For ambiguities with angle brackets consider the assignment
>
> a, b = w < x, y > (z)
>
> Without type information, it is impossible to decide whether the right-hand side of the assignment is a pair of expressions
>
> (w < x), (y > (z))
>
> or whether it is a generic function invocation that returns two result values
>
> (w<x, y>)(z)

wouldn't gofmt remove the superfluous (z) in this case (and even with a more complex expression inside the parens, as the > operator would have appropriate precedence?), in existing code? And thus, would it not be reasonable to have a convention in this case that if you have an expression that fits the type parameter interpretation, that interpretation is used instead of the alternative? And if you really wanted to write the alternative, you would be forced to make it explicit with the parens as noted:

a, b = (w < x), (y > (z))

This seems like a very rare case, and a small price to pay for following the established convention..

- Randy

Jesper Louis Andersen

unread,
Jul 15, 2020, 5:54:34 AM7/15/20
to Randall O'Reilly, robert engels, Ian Lance Taylor, Ahmed (OneOfOne) W., golang-nuts
On Wed, Jul 15, 2020 at 11:27 AM Randall O'Reilly <rcore...@gmail.com> wrote:
wouldn't gofmt remove the superfluous (z) in this case (and even with a more complex expression inside the parens, as the > operator would have appropriate precedence?), in existing code?  And thus, would it not be reasonable to have a convention in this case that if you have an expression that fits the type parameter interpretation, that interpretation is used instead of the alternative?  And if you really wanted to write the alternative, you would be forced to make it explicit with the parens as noted:

        a, b = (w < x), (y > (z))

This seems like a very rare case, and a small price to pay for following the established convention..


If the parser cannot disambiguate without type information, then neither can gofmt.

In practice, this means you risk opening pandoras box in several ways:

* gofmt is now part of the system, as you cannot safely parse without it.
* gofmt needs to be more powerful than the parser.
* gofmt must alter the semantics of the program, which goes directly against the idea of gofmt.

The really hard part is that you have to nail all parsing problems in a parser. You can't attack problems on a case-by-case basis as it often yields inconsistency.

Languages in the ML family solves problems like the above by having several steps in the chain:

Lex -> Parse -> Elaborate -> Phase split -> Type Check -> ... -> Machine code

Elaboration is the phase where type information is brought in to disambiguate what the parser found. Phase splitting is also interesting in that it splits work into compile-time and runtime phases (since MLs kind of blends those two things together in its syntax). However, this has implications in the complexity of the parser and language. Go seems to lean more toward a Niklaus Wirth'esque simplicity path; though not as stringent as Wirth would.

Michal Strba

unread,
Jul 15, 2020, 8:41:51 AM7/15/20
to Ian Lance Taylor, golang-nuts
Angle brackets are only problematic in expressions in the bodies of functions when specializing a function or a type, right? They are not problematic in signatures and type definitions.

What about using a dot when specializing in bodies?

    func zero<T>() T {
        var z T
        return z
    }

    func main() {
        x := zero.<int>() // requires a dot
    }

This is less weird than it looks, because Go already uses a dot for type assertions and this is a similar thing.

Jan Mercl

unread,
Jul 15, 2020, 8:44:45 AM7/15/20
to Robert Griesemer, Ian Lance Taylor, golang-nuts
On Tue, Jul 14, 2020 at 11:55 PM 'Robert Griesemer' via golang-nuts
<golan...@googlegroups.com> wrote:

> The time has come to revisit this early decision. If square brackets alone are used to declare type parameters, the array declaration

My 2c - Alternative type parameters syntax (ab)using @$:
https://docs.google.com/document/d/1AoU23DcNxYX2vYT20V2K16Jzl7SP9taRRhIT8l_pZss/edit?usp=sharing

David Riley

unread,
Jul 15, 2020, 9:26:46 AM7/15/20
to Ian Lance Taylor, Ahmed (OneOfOne) W., golang-nuts
On Jul 15, 2020, at 12:13 AM, Ian Lance Taylor <ia...@golang.org> wrote:
>
> The disadvantage of guillemets is that they are hard to type on many
> keyboards. So to me either square brackets or angle brackets would be
> better than guillemets.

Not to mention that, while Go *is* officially in UTF-8, the chance of an output device or editor mis-formatting something that isn't 7-bit ASCII is still reasonably significant even in 2020, and that has much graver consequences for delimiters than for, say, identifiers or string contents. I'm curious about how screen readers react as well; I know we have one or two folks on this list who could probably offer some perspective there.

> The disadvantage of a two character sequence such as <: :> is that it
> is more typing. So again either square brackets or angle brackets
> seem to me to be better.
>
> An example of a reason that square brackets might be a poor choice
> would be ambiguous parsing, or cases where the code is harder to read.
>
> It's true that some other languages use angle brackets, but Go already
> does many things differently. That is only a minor advantage for
> angle brackets. To me at least it does not outweigh the
> disadvantages.

I think square brackets do the job of visually disambiguating the syntax from the other uses of parentheses on the same lines well enough, and it sounds like with the "type" keyword they do the job well enough for the parser as well. Personally, I have no horse in the race for square vs. angle brackets (other than I think it would be foolish to do something technically infeasible or problematic just because it subjectively looks nicer), but then, I also like Objective-C's visual styling, so YMMV.


- Dave

David Riley

unread,
Jul 15, 2020, 9:32:49 AM7/15/20
to Nick Craig-Wood, golang-nuts
On Jul 15, 2020, at 4:47 AM, Nick Craig-Wood <ni...@craig-wood.com> wrote:
>
> In my mind the use of [ ] clashes horribly with the array declaration syntax. When I see [int] in particular my brain says, ah array declaration of size int, what, no wait...

On the other hand, this is already how maps are declared (e.g. map[int]string). It's fairly consistent with that, actually.


- Dave

janeri...@gmail.com

unread,
Jul 15, 2020, 10:59:07 AM7/15/20
to golang-nuts
Go code use a Syntax analog to casting instead of the type keyword.

f.[int](args)
or
var x map[string]GType.[int]

That does not collide with other language features in function declaration, type definitions, inner types in structs or interfaces, variable declarations, type alias declarations and maps / array / chan type declarations.

Georges Varouchas

unread,
Jul 15, 2020, 10:59:08 AM7/15/20
to golang-nuts
Hello all,

I'd first like to say thanks to the go team for their incredible work on delivering such a usable tool as go.

I will then proceed to bring my 2 cents to the World Bikeshedding Foundation :)

I would add a vote to have something more differenciating to indicate "this is a generic instanciation" :

 * for generics delcaration : in both the previous spec and the "soon to be new" spec, the `type` keyword (in `(type ...` or `[type ...`) is a clearly visible marker that this is a generic type declaration
 * for generics instanciation, on the other hand : `(someType ...` or `[someType ...` has no easily visible indicator that this is a generic instanciation, rather than a list of values (when using `(`) or and array/map index (when using `[`)

The square brackets will be much less ambiguous, which is definitely good ;
I would still like to have a more visible thing, so that I don't have to think so much about the context when reading the code :
`[:` or `[!` or `[[` or ...
something which makes the places where generic types are instanciated more unique.

(`[!` could appear as the prefix of an index to a map with bool keys, so probably not this one)

Thanks for your attention,
*Georges*


Le mardi 14 juillet 2020 23:56:01 UTC+2, gri a écrit :

We have received a variety of feedback on the generics draft design (blog). Thanks to everyone who took the time to read it, play with generics code in the playground, file issues, and send us their thoughts.


Not unexpectedly, many people raised concerns about the syntax, specifically the choice of parentheses for type parameter declarations and generic type and function instantiations.


A typical computer keyboard provides four easily accessible pairs of single-character symmetrical "brackets": parentheses ( and ), square brackets [ and ], curly braces { and }, and angle brackets < and >. Go uses curly braces to delineate code blocks, composite literals, and some composite types, making it virtually impossible to use them for generics without severe syntactic problems. Angle brackets require unbounded parser look-ahead or type information in certain situations (see the end of this e-mail for an example). This leaves us with parentheses and square brackets. Unadorned square brackets cause ambiguities in type declarations of arrays and slices, and to a lesser extent when parsing index expressions. Thus, early on in the design, we settled on parentheses as they seemed to provide a Go-like feel and appeared to have the fewest problems.


As it turned out, to make parentheses work well and for backward-compatibility, we had to introduce the type keyword in type parameter lists. Eventually, we found additional parsing ambiguities in parameter lists, composite literals, and embedded types which required more parentheses to resolve them. Still, we decided to proceed with parentheses in order to focus on the bigger design issues.


The time has come to revisit this early decision. If square brackets alone are used to declare type parameters, the array declaration


type A [N]E


cannot be distinguished from the generic type declaration


type A[N] E


But if we are comfortable with the extra type keyword, the ambiguity disappears:


type A[type N] E


(When we originally dismissed square brackets, the type keyword was not yet on the table.)


Furthermore, the ambiguities that arise with parentheses appear not to arise with square brackets. Here are some examples where extra parentheses are not needed with square brackets:


using ()                 using []

func f((T(int))          func f(T[int])

struct{ (T(int)) }       struct{ T[int] }

interface{ (T(int)) }    interface{ T[int] }

[](T(int)){}             []T[int]{}


To test this better understanding, and to get a feel for this alternative notation, we will begin to make changes to our prototype implementation such that it accepts either parentheses or square brackets (only one or the other) in a generic Go package. Those changes will first appear as commits to the dev.go2go branch, and eventually in the playground.


If square brackets don't lead to unforeseen issues, we have another fully explored notation to choose from, which will allow us to make a more informed decision.


- gri, iant


PS: For ambiguities with angle brackets consider the assignment


a, b = w < x, y > (z)


Without type information, it is impossible to decide whether the right-hand side of the assignment is a pair of expressions


(w < x), (y > (z))


or whether it is a generic function invocation that returns two result values


(w<x, y>)(z)


eme...@gmail.com

unread,
Jul 15, 2020, 10:59:10 AM7/15/20
to golang-nuts
Square brackets feel much better. It removes a lot of ambiguity in reading code. 

Actually I personally wouldn't mind to have double square brackets as "Generics" indicator just to remove also what little reading ambiguity they can cause with indices.

type A[[N]]  E

Nic Long

unread,
Jul 15, 2020, 10:59:10 AM7/15/20
to golang-nuts
Square brackets work great. Scala uses them for generics to great effect. They also make me think of Clojure, which introduced them to Lisp for similar reasons to here - to avoid too much overload of regular parentheses - and which people have been very happy with.

This e-mail and all attachments are confidential and may also be privileged. If you are not the named recipient, please notify the sender and delete the e-mail and all attachments immediately. Do not disclose the contents to another person. You may not use the information for any purpose, or store, or copy, it in any way.  Guardian News & Media Limited is not liable for any computer viruses or other material transmitted with or as part of this e-mail. You should employ virus checking software.
 
Guardian News & Media Limited is a member of Guardian Media Group plc. Registered Office: PO Box 68164, Kings Place, 90 York Way, London, N1P 2AP.  Registered in England Number 908396


dylan...@gmail.com

unread,
Jul 15, 2020, 10:59:10 AM7/15/20
to golang-nuts
Here's my 2cents: 
I actually don't mind the parenthesis.. I've written a bunch of functions with them now (
https://github.com/DylanMeeus/hasgo2/tree/master/functions) and they become readable quickly. (Although I also enjoy LISP flavours, so maybe that biases my view of parenthesis :D) 

But as I can see, the square brackets do offer a certain readability improvement in those cases mentioned above. Somehow though the [] I associate more strongly with 'indexing' and 'slices / arrays' whereas with the `()` I don't have a strong association with a single thing. They are already used for multiple parts, you get

```
func (params) (return_params)
x.(string)
```

and of course for grouping mathematical operations. 

I'll play around with the change and see if I get used to them as quickly :) 

Tao Liu

unread,
Jul 15, 2020, 10:59:13 AM7/15/20
to golang-nuts
This is similar to C++ template,  and it's very clear to understand.

kaleidawave

unread,
Jul 15, 2020, 10:59:13 AM7/15/20
to golang-nuts
Correct me if I am wrong but wouldn't square bracket syntax have an ambiguity around calling a function pointer at a position in a slice:

z := []func() int{x, y};
a := z[0]();

jpap

unread,
Jul 15, 2020, 10:59:19 AM7/15/20
to golang-nuts
Two thoughts:

1. How useful is it in practice to have separate start and end delimiters?  Does it matter for nested declarations?  I didn't see any nested declarations in the Draft Design document, for example.  If allow the same token on both sides, we could use the vertical bar, and not require the type keyword at all:

func f(T|int|)

struct{ T|int| }

interface{ T|int| }

[]T|int|{}

type S|T| struct { v T }


(A concrete example is included at the end of this e-mail.)

The above was one of the options Matt McCullough outlined in "Towards Clarity: Syntax Changes for Contracts in Go".

2. Otherwise, could we also consider double square brackets so it becomes immediately obvious that the expression has nothing to do with an array?  (The type keyword is not needed here either.)

func f(T[[int]])

struct{ T[[int]] }

interface{ T[[int]] }

[]T[[int]]{} 

type S[[T]] struct { v T }


(The same example in this style is included at the end of this e-mail.)


On the whole, I prefer [[...]] over |...| over [...] over (...), but would also prefer that type declarations stick out easily enough.

jpap


PS. An example from Matt Layher, rewritten using vertical bars:

// Package hashtable implements a basic hashtable for generic key/value pairs.
package hashtable

// A Table is a basic generic hashtable.
type Table|K comparable, V interface{}| struct {
    // hash is a function which can hash a key of type K with t.m.
    hash func(key K, m int) int

	m     int
	table [][]kv|K, V|
}

// A kv stores generic key/value data in a Table.
type kv|K, V| struct {
	Key   K
	Value V
}

// New creates a table with m internal buckets which uses the specified hash
// function for an input type K.
func New|K comparable, V interface{}|(m int, hash func(K, int) int) *Table|K, V| {
	return &Table|K, V|{
		hash:  hash,
        m:     m,
		table: make([][]kv|K, V|, m),
	}
}

PPS. The same example from Matt Layher, rewritten using [[...]]:

// Package hashtable implements a basic hashtable for generic key/value pairs.
package hashtable

// A Table is a basic generic hashtable.
type Table[[K comparable, V interface{}]] struct {
    // hash is a function which can hash a key of type K with t.m.
    hash func(key K, m int) int

	m     int
	table [][]kv[[K, V]]
}

// A kv stores generic key/value data in a Table.
type kv[[K, V]] struct {
	Key   K
	Value V
}

// New creates a table with m internal buckets which uses the specified hash
// function for an input type K.
func New[[K comparable, V interface{}]](m int, hash func(K, int) int) *Table[[K, V]] {
	return &Table[[K, V]]{
		hash:  hash,
        m:     m,
		table: make([][]kv[[K, V]], m),
	}
}

Yaw Boakye

unread,
Jul 15, 2020, 11:00:01 AM7/15/20
to Max, golang-nuts
I'm in favor of the square brackets. scala has them as well so we're not exactly blazing a new trail. I'm not in favor of a second `type` keyword whose benefit is to disambiguate syntax to the parser. we already use significant whitespace to identify tokens. for example, we expect to be parsing a function after we've parsed the `func\s+`. I'll take a parsing error over repeated the type keyword. In this regard we're not along either. Erlang's less-than-or-equals operator is =<. It inadvertently makes whitespace a requirement when doing binary syntax: `X = <<"hello">>`. Without the significant whitespace after the equals sign, the Erlang parser always finds the `=<` operator, which leads to a parsing error. A similar behavior for the square brackets syntax will be desirable.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
--
Curried programmer
I'm tweeting when I'm not coding when I'm not holding my niece.

rvi...@gmail.com

unread,
Jul 15, 2020, 12:31:03 PM7/15/20
to golang-nuts
Dear Developers:

You have spent several years on this and I'm impressed by the result, it looks beautiful!  I'm stunned.

As you can see from the recent spotlight shown on this syntax, in the meanwhile people have resorted to using unicode characters that sort of look like delimiters but are just part of the variable name, and then parsing them outside of Golang while the compiler doesn't realize the shenanigans that are going on with variable names that have Canadian Aboriginal Syllabics, resulting in a single variable name that looks like ImmutableTreeListᐸElementTᐳ but which contains no angle brackets.  (In fact here is what it looks like with angle brackets:ImmutableTreeList<ElementT> , a barely discernible difference.)

No one will soon forget this example.

I think that reflecting on the importance of visual distinction, the admirable step you have taken moving away from parentheses is not enough:

If [ and ] seem to work and can always be parsed unambiguously I would be very humbled and gracious if the designers could please show me me how the various examples and edge cases that have come up over the years would look if gofmt changed [ and ] in these places and, indeed, instead, [_ and _] were the standard format in these spots: 2 characters, an opening bracket followed by an underscore, and an underscore followed by a closing bracket.  (Which gofmt could convert these spots into.)

The suggestion is meant to resemble a blank spot someone has written in, as on a form.  The reason for letting gofmt convert it to that is to let people enter [ and ] at the keyboard, since you have done the hard work of making the spots unambiguous; this saves a character while entering at the keyboard, at the small expense of the formater removing a character from any identifier that happens to start or end with _ when used in that spot and using the short form syntax instead of the full gofmt syntax: in that case the formatter has no way of knowing that they meant to write the sloppy [ form for it to correct to [_ on its own.   (Since identifiers may, of course, both begin and end with an underscore themselves.)

Edge Cases Because Identifiers may begin or end with an undscore (or both)   

This allowance would in fact make _int_ wholly ambiguous: it could be a variable named _int_, or a type named int depending on where in the code it occurred. (If it occurred in an array index position it would refer to a variable.)

However, you have ensured that in fact the syntax itself makes these positions totally distinct even without the visual difference.   (And not many variables are being used which both begin and end in a _.)

Therefore at the spots where you have parsed [] as a generic, if someone were to enter [_int_] at the keyboard, it would be considered only as int - for something named _int you would have to enter [__int_] and for something named int_ you would have to enter [_int__], both of which seem pretty clear to me and an edge case that would not often come up.)

Supposing that the user were allowed to continue to enter [ and ] on the keyboard, since you have made the syntax unambiguous to the compiler in these spots, gofmt would turn them into [_ and _] wherever they refer to the generic syntax.

The constraint behind choosing [_ and _] is that it is visually distinct - always important for programmers - and unambiguous.  It is within the character set easily accessible by programmers and which Go already understands, and all Go programmers use ['s and _'s all the time.

Nobody looking at code with [_ _] can ever suppose it is anything other than a new syntax.

Look at the following and imagine that the [_ _] is like a form into which someone has entered with a typewriter:

using ()                 using [_ _]

func f((T(int))          func f(T[_int_])

struct{ (T(int)) }       struct{ T[_int_] }

interface{ (T(int)) }    interface{ T[_int_] }

[](T(int)){}             []T[_int_]{}



I don't like different things to look the same.  [ and ] look the same as an array index.  They aren't an array index, so they shouldn't look like an array index.

But if you clever coders have made it so that it is unambiguous to the compiler when you mean to use the generic syntax, though you've written an opening or closing array limiter, then by all means let the programmer save having to search around for _ and allow them to use a bracket for gofmt to convert later.

The reference says:

Array types are always one-dimensional but may be composed to form multi-dimensional types.

[32]byte
[2*N] struct { x, y int32 }
[1000]*float64
[3][5]int
[2][2][2]float64  // same as [2]([2]([2]float64))

Look at that final example! [2][2][2]

I think that it would be unforutnately if some of those [ and ['s in some contexts can end up enclosing a type, and in other contexts enclose a value, depending on where you are in the parse tree, and for the programmer to never realize what is going on.

For all of these reasons I would greatly appreciate if you would show me all the possible edge cases you've discovered for types.  I would like to know whether the [_int_] notation would look beautiful for all of them.

To me it is orthogonal; uses the character set used by all versions of Go since the very beginning; breaks any Go version that doesn't know about them; looks easy on the eye; and can be checked for intent by the Go formatter, gofmt

How would it look at the edges? Please let me know by filling out this form: [__]

Seebs

unread,
Jul 15, 2020, 12:32:29 PM7/15/20
to golang-nuts
On Tue, 14 Jul 2020 23:19:40 -0700 (PDT)
Fino <xme...@gmail.com> wrote:

> I vote for <: and :>

I'm conflicted on it. <> match what several other languages do, but are
problematic for the parser. () is definitely overused. [] is tolerable.

I'd like to bring up the possibility of ??( and ??), mostly so that
whatever we end up with, people will be able to say "well, at least it
wasn't trigraphs".

-s

Dan Markham

unread,
Jul 15, 2020, 1:03:40 PM7/15/20
to golang-nuts
On Tuesday, July 14, 2020 at 9:45:41 PM UTC-7, robert engels wrote:
My opinion is that every major language (no flames please… lots of developers write lots of programs and make money doing it) that supports generics uses < > for generic types, so Go should too - since there is no reason to deviate from this other than to avoid changes to the parser. Seems better to pay this cost once - rather than every Go program that uses generics being harder to read for eternity (especially for those readers that use a lot of languages). 

This really captures my feeling perfectly, the parser changes have to be less man hours to build and maintain going forward than the man hours of all the existing and new developers. All options have a downsides at least <T> is as "standard" as one can get when talking about  generics. 

Some version of <T> will help beginners in reducing the cognitive load when learning and using Go.  

Thanks,
-Dan 
 

 
> On Jul 14, 2020, at 11:13 PM, Ian Lance Taylor <ia...@golang.org> wrote:
>
> On Tue, Jul 14, 2020 at 8:21 PM Ahmed (OneOfOne) W. <oneo...@gmail.com> wrote:
>>
>> This feels a little better, but honestly I'm still all for angle brackets or like Watson suggested, guillamets.
>>
>> fn(T1)(fn2(T2)(fn3(T3)(v))) // 1
>> fn[T1](fn2[T2](fn3[T3](v))) // 2
>> fn<T1>(fn2<T2>(fn3<T3>(v))) // 3
>> fn«T1»(fn2«T2»(fn3«T3»v)))  // 4
>>
>> To me, with a background in C++ and Typescript and a little bit of Rust, #3 and #4 are just natural and easier to read.
>
> To unsubscribe from this group and stop receiving emails from it, send an email to golan...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CAOyqgcX-OXktNtUs0G4Ns0iEr3R2qLPpU7q1%3DrOY93%3DAO16a3g%40mail.gmail.com.

Zippoxer

unread,
Jul 15, 2020, 1:40:56 PM7/15/20
to golang-nuts
Agreed. I've been a happy Gopher since 2011 and would really like to see <> or even [] instead of ().

Carla Pfaff

unread,
Jul 15, 2020, 1:52:03 PM7/15/20
to golang-nuts
I really like this square bracket syntax and I'm glad that Go does not repeat the mistakes other languages have made..

Ian Lance Taylor

unread,
Jul 15, 2020, 2:49:37 PM7/15/20
to robert engels, Ahmed (OneOfOne) W., golang-nuts
On Tue, Jul 14, 2020 at 9:45 PM robert engels <ren...@ix.netcom.com> wrote:
>
> My opinion is that every major language (no flames please… lots of developers write lots of programs and make money doing it) that supports generics uses < > for generic types, so Go should too - since there is no reason to deviate from this other than to avoid changes to the parser. Seems better to pay this cost once - rather than every Go program that uses generics being harder to read for eternity (especially for those readers that use a lot of languages).

Thanks for the note.

I'll briefly mention, as others have, that Scala successfully uses
square brackets for generics, and that in Go the map type already uses
square brackets to indicate the generic key type (though the value
type is outside the square brackets). So it's not like this is
entirely new ground.

It's also worth noting that the Go survey results
(https://blog.golang.org/survey2019-results) show us that the
languages most Go programmers are familiar with are Python and
JavaScript, which of course do not use generics at all with any sort
of brackets. So the familiarity argument only goes so far.

More seriously, though, let's look closely at Robert's example:

a, b = w < x, y > (z)

When parsing this without any information about what the identifiers
mean, this could be either an assignment of two values to two
variables:

a = w < x
b = y > (z)

or it could be a call of the instantiated function w<x, y> passing the
argument z and returning two results. By the way, don't get
distracted by the parentheses around z, it can be an arbitrary
expression, so although (z) might be unusual using parentheses around
a more complex expression might be entirely reasonable.

Without knowing the type of w, we can not parse this statement. This
has nothing to do with lookahead or the nature of parsing or anything
else. It's fundamental to the syntax.

In order for tools like gofmt and goimports to be effective and be
fast enough to run on file save, they have to be able to parse Go code
without knowing types. In the general case, where w might actually be
written as pkg.w, knowing the type of w will require chasing through
an arbitrary number of imported packages. Requiring simple tools like
gofmt and goimports to do this kind of type checking would break one
of the basic guidelines that Go has followed from the beginning: the
language must be easy and fast to parse without knowing types.

I'll note that C++ also has this syntactic problem, and indeed C++
cannot be parsed without full type information. This is part of why
tools that work with Go code run much faster than tools that work with
C++ code, and is part of why the Go compiler is much faster than
typical C++ compilers.

So it's really not a matter of "just fix the parser to use angle
brackets." As far as I've been able to tell, using angle brackets is
technically infeasible, given the way that Go works. Other languages
make different choices, and those choices work for them, but this is
not a choice that Go is able to make.

So, I hear you: you and many others would prefer angle brackets. But
unless and until someone explains how it could be done, we must
continue to regard that choice as off the table.

Sorry.

Ian

Hein Meling

unread,
Jul 15, 2020, 2:54:58 PM7/15/20
to golang-nuts
When you refer to beginners, I think you mean developers from other languages (Java/C#/C++...) that already know the concept of generics. I don't think they will have problems adapting to a square brackets notation... they are smart people, and after all they have to learn many other syntactical differences as well, so one more difference is not going to be the deal breaker. And if they make <mistakes>, the compiler will [surely] tell them ;-)

In fact, the square bracket notation is already adopted for declaring the type of maps and slices. So switching to angle brackets will make Go's generics implementation internally inconsistent, since you must declare a map[string]int with angle brackets and then elsewhere, you will be using <int> for the same concept. I think it is much more important that Go remains internally consistent in its use of angle brackets.

Best,
:) Hein

Hein Meling

unread,
Jul 15, 2020, 3:02:25 PM7/15/20
to golang-nuts
Correction: I think it is much more important that Go remains internally consistent in its use of "square" brackets.

Ian Lance Taylor

unread,
Jul 15, 2020, 3:27:00 PM7/15/20
to Paul Johnston, golang-nuts
On Tue, Jul 14, 2020 at 10:06 PM Paul Johnston <pcj...@gmail.com> wrote:
>
> If the generic expression <T> was always attached/constrained to the "type" or "func" keyword (rather than the type or function name), perhaps this would decrease the lookahead problems with lexing? For example:
>
> type<T> Point struct {
> x, y int
> data T
> }
>
> type<R,S> Transformer interface {
> Transform(R) S
> }
>
> func<T> Stringify(s []T) string {
> }
>
> type<T> Vector []T

Well, you need to consider not just the definitions, but also the uses.

The suggested syntax, whether with parentheses or square brackets, has
what I consider to be a very nice property: the definition and use
syntaxes are very similar. For the definition we write

func F[type T]() {}

For the use we write

F[int]()

The two aren't identical, but the syntax is parallel.

The same is true in Java. Admittedly in C++ the syntaxes are not
parallel, and are more like what you suggest.

Ian

Seebs

unread,
Jul 15, 2020, 3:59:38 PM7/15/20
to golang-nuts
On Wed, 15 Jul 2020 12:26:15 -0700
Ian Lance Taylor <ia...@golang.org> wrote:

> The suggested syntax, whether with parentheses or square brackets, has
> what I consider to be a very nice property: the definition and use
> syntaxes are very similar. For the definition we write
>
> func F[type T]() {}
>
> For the use we write
>
> F[int]()
>
> The two aren't identical, but the syntax is parallel.

Maybe the "type" should be required in both? That feels annoying and
ugly, but it would eliminate any possible ambiguity about how to
interpret the name in the []:

var F1 []func()
F1[otherpkg.Y]()
func F2[type T]() {}
F2[otherpkg.Z]()

-s

David Riley

unread,
Jul 15, 2020, 4:21:38 PM7/15/20
to Ian Lance Taylor, robert engels, Ahmed (OneOfOne) W., golang-nuts
On Jul 15, 2020, at 2:48 PM, Ian Lance Taylor <ia...@golang.org> wrote:
>
> More seriously, though, let's look closely at Robert's example:
>
> a, b = w < x, y > (z)

TBH, I think a big argument in favor of square brackets over angle brackets is that they ascend above the center line in most typefaces, which makes them much more visually distinct from the shorter operators and many lowercase letters. This is similar to parens and curly braces while still being visually distinct from both.

As something that is essentially a metaparameter which is similar to, but distinct in function to the arguments in parens, there's a lot of good consistency going on there that I personally find easier to visually scan without syntax highlighting than I do angle brackets. But that could be just me, and it does depend on typeface, so take it with whatever grains of salt you need to.


- Dave


Ian Lance Taylor

unread,
Jul 15, 2020, 4:50:27 PM7/15/20
to Bakul Shah, gri, golang-nuts
On Tue, Jul 14, 2020 at 11:06 PM Bakul Shah <ba...@iitbombay.org> wrote:
>
> I don't much like square brackets or angle brackets or guillemets. But here is a different way
> of reducing the need for parentheses at least for the common case:
>
> A proposal for fewer irritating parentheses!
>
> One thing to note is that generic functions & types are *different* from
> existing things like const, func, type, var. As such they should have their
> own declaration marker. For example
>
> gen T type pair struct { a, b T } // contrast with type pair(type T) ...
> gen T,U type W struct { a T; b U } // contrast with type W(type T, U) ...
> gen T func Print(s []T) {...} // print a slice of T
>
> These function/type/method generics are used by *prepending* the type:
>
> var x int pair // a pair of ints
> var y (int, int pair) W // here we have to use parentheses
> int Print([]int{1,2,3}) // print a slice of ints
> qq := int pair pair{{1,2},{3,4}} // a pair of a pair of ints
> ww := (int, int) W pair{{1,2},{3,4}}
>
> This use may seem weird if you are used to C/C++. I find it more readable
> than having to deal with extra parentheses. "int pair" clearly says a
> pair of ints and so on. What is more, if in future types are allowed to be
> *inferred* for generic function calls, you can simply drop the type prefix.
>
> If there is still a parsing ambiguity, I'd suggest adding a - as in int-pair.
>
> Additional type syntax rule:
>
> type: ... | type generic-type| (type-list) generic-type
>
> or
>
> type: ... | type "-" generic-type | (type-list) "-" generic-type
>
> FWIW I thought of this four weeks ago (June 16).


Thanks for the note. It's an interesting idea, but I'm not quite sure
it works to have prefix types with no rea