[generics] moving the last type parameter to a type outside the square brackets

268 views
Skip to first unread message

Anonymous AWK fan

unread,
Jan 4, 2021, 3:41:28 PM1/4/21
to golan...@googlegroups.com
I think something like sync.Map[string]linked.List string is more readable than sync.Map[string, linked.List[string]].

I propose putting the last type parameter to a generic type after the square brackets and omitting them when there is only one type parameter.

Axel Wagner

unread,
Jan 4, 2021, 4:01:03 PM1/4/21
to Anonymous AWK fan, golang-nuts
What is the "them" to be omitted if there is only one type parameter? It wouldn't make sense to omit the brackets (because there needs to be some delimiter between the name of the generic function/type and the type argument). But if there is only one type-parameter anyway, I don't know what else you would omit.

Either way - you are using `sync.Map` to motivate this, with a clear analogue to `map`. But what about types that *don't* represent a map (like the Graph-example, where both type-parameters are on mostly equal footing)? And what about generic functions? I think
Foo[int]string(bar, baz)
isn't super readable, TBH.

In short, I think that introducing this sort of asymmetry into the syntax hurts more than it helps.


On Mon, Jan 4, 2021 at 9:41 PM 'Anonymous AWK fan' via golang-nuts <golan...@googlegroups.com> wrote:
I think something like sync.Map[string]linked.List string is more readable than sync.Map[string, linked.List[string]].

I propose putting the last type parameter to a generic type after the square brackets and omitting them when there is only one type parameter.

--
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/125582361.510700.1609792751744%40ichabod.co-bxl.

robert engels

unread,
Jan 4, 2021, 5:23:06 PM1/4/21
to Anonymous AWK fan, golan...@googlegroups.com
Reading

sync.Map[string]linked.List string

I have no idea what that represents?

What if you had a map of maps - which is a very common data structure - possibly with completely different key and value types? Really any nested data structures with multiple types would be impossible to read IMO.





> On Jan 4, 2021, at 2:39 PM, 'Anonymous AWK fan' via golang-nuts <golan...@googlegroups.com> wrote:
>
> I think something like sync.Map[string]linked.List string is more readable than sync.Map[string, linked.List[string]].
>
> I propose putting the last type parameter to a generic type after the square brackets and omitting them when there is only one type parameter.
>

tapi...@gmail.com

unread,
Jan 5, 2021, 3:01:05 AM1/5/21
to golang-nuts
On Monday, January 4, 2021 at 5:23:06 PM UTC-5 ren...@ix.netcom.com wrote:
Reading

sync.Map[string]linked.List string

I have no idea what that represents?

If you can read

   sync.Map[string]chan string

then you can read "sync.Map[string]linked.List string" too.

 

What if you had a map of maps - which is a very common data structure - possibly with completely different key and value types? Really any nested data structures with multiple types would be impossible to read IMO.

In

  map[k1]map[k2]map[k3]v3

and

  map[k1, map[k2, map[k3, v3]]

which is more readable?

Anonymous AWK fan

unread,
Jan 5, 2021, 6:44:20 AM1/5/21
to Axel Wagner, golang-nuts
Axel, please send your reply to golang-nuts too, you can ignore the rest of this, I already sent it to you but not golang-nuts because I didn't reply to all.

> What is the "them" to be omitted if there is only one type parameter? It wouldn't make sense to omit the brackets (because there needs to be some delimiter between the name of the generic function/type and the type argument). But if there is only one type-parameter anyway, I don't know what else you would omit.

Why does there need to be a delimiter, there isn't one between chan and int in chan int, which I think is more readable than chan[int].

> Either way - you are using `sync.Map` to motivate this, with a clear analogue to `map`. But what about types that *don't* represent a map (like the Graph-example, where both type-parameters are on mostly equal footing)?

I think most generic types would only have 1 type-parameter and the syntax should be like the built-in ones ([]T, *T, func(Params) Result, map[Key]Elem and chan T). Putting the extra ones in square brackets is the best thing I can think of. Also I think Graph[Node]Edge or Graph[Edge]Node is still readable.

> And what about generic functions? I think
> Foo[int]string(bar, baz)
> isn't super readable, TBH.

I was only suggesting this for generic types.

Levieux Michel

unread,
Jan 5, 2021, 8:36:19 AM1/5/21
to Anonymous AWK fan, golang-nuts
There is a delimiter in 'chan int' -> ' ' (space)

I don't think it is a good assumption that "most generic types would only have 1 type-parameter" (or 2, or 3, or any other arbitrary number for what it's worth). Moreover, I don't think it is a good idea to assimilate builtin language constructs to examples of "specific already-implemented generics", precisely because they are not thought to be generics and they are not used like generics.

Finally, and as for all that's written above, it is my own opinion, I think the best rules are the simplest ones (in terms of description), and *sometimes*, if it is "lose a little readability for simpler rules" or "lose a little simplicity in rules for better readability", and those gains / losses are really small (which I think is the case with your suggestion), the first choice should always be the right one. It'll only be a matter of days for people to get used to the current syntax, after that, "brain automation" takes on for such tasks and everything is okay without even having to think about it. I don't like the idea of a "special rule" for the last / only type-parameter.

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

Axel Wagner

unread,
Jan 5, 2021, 8:43:44 AM1/5/21
to Anonymous AWK fan, golang-nuts
On Tue, Jan 5, 2021 at 12:03 PM Anonymous AWK fan <awkf...@mailfence.com> wrote:
Why does there need to be a delimiter, there isn't one between chan and int in chan int, which I think is more readable than chan[int].

`chan` is a keyword, names of generic types and functions are identifiers.
So, in a way, yes, there is a delimiter: `chan`.

I think most generic types would only have 1 type-parameter and the syntax should be like the built-in ones ([]T, *T, func(Params) Result, map[Key]Elem and chan T). Putting the extra ones in square brackets is the best thing I can think of. Also I think Graph[Node]Edge or Graph[Edge]Node is still readable.

Readability is not a binary choice, but a matter of degree. Everything is *somewhat* readable. But implying an asymmetry that isn't there seems confusing to me.

I was only suggesting this for generic types.

Well, then that's another drawback. Using inconsistent syntax for generic types and functions is confusing.

Brian Candler

unread,
Jan 5, 2021, 11:31:44 AM1/5/21
to golang-nuts
Are you trying to make user-defined generic maps look more like built-in ones?

map[T1]T2  is hard-coded syntax in go. But so is m[k] for accessing elements - and you can't use that in user-defined types anyway.

Therefore I don't think it helps much to be able to write mymap[T1]T2 instead of mymap[T1, T2], because you can't use the corresponding accessors.  You would need something like:

func (m *MyMap[KT, VT]) set(k KT, v VT) ...
func (m *MyMap[KT, VT]) get(k KT) VT ...

Also: to create one of these objects there is more built-in syntax you can't use:
    v := make(mymap[T1]T2)   // won't work

so you need to use a factory function. With your proposed syntax it would be:
    v := makemymap[T1]T2()

which IMO is worse than
    v := makemymap[T1, T2]()

tapi...@gmail.com

unread,
Jan 5, 2021, 1:17:14 PM1/5/21
to golang-nuts
You mixed up the current draft custom generic function syntax with OP's generic type syntax here.
It is no doubtful the result will be ugly. What about if the custom generic function syntax is consistent with built ones?
For example:

    v := MakeMyMap(mymap[T1]T2)
 

tapi...@gmail.com

unread,
Jan 5, 2021, 1:25:00 PM1/5/21
to golang-nuts
On Tuesday, January 5, 2021 at 8:43:44 AM UTC-5 axel.wa...@googlemail.com wrote:
On Tue, Jan 5, 2021 at 12:03 PM Anonymous AWK fan <awkf...@mailfence.com> wrote:
Why does there need to be a delimiter, there isn't one between chan and int in chan int, which I think is more readable than chan[int].

`chan` is a keyword, names of generic types and functions are identifiers.
So, in a way, yes, there is a delimiter: `chan`.

Is it possible to change `chan` to a builtin identifier? (I'm uncertain on this.)
 

I think most generic types would only have 1 type-parameter and the syntax should be like the built-in ones ([]T, *T, func(Params) Result, map[Key]Elem and chan T). Putting the extra ones in square brackets is the best thing I can think of. Also I think Graph[Node]Edge or Graph[Edge]Node is still readable.

Readability is not a binary choice, but a matter of degree. Everything is *somewhat* readable. But implying an asymmetry that isn't there seems confusing to me.

I was only suggesting this for generic types.

Well, then that's another drawback. Using inconsistent syntax for generic types and functions is confusing.

OP doesn't intend to use inconsistent syntax. I think it is just that OP hasn't got an idea on the function part yet.

Brian Candler

unread,
Jan 5, 2021, 1:29:01 PM1/5/21
to golang-nuts
On Tuesday, 5 January 2021 at 18:17:14 UTC tapi...@gmail.com wrote:
What about if the custom generic function syntax is consistent with built ones?
For example:

    v := MakeMyMap(mymap[T1]T2) 

Would this be mandatory for all generics which take two or more types?  It seems unbalanced to me.  Not everything is a hash-like container with a key and value.

Silly example:

func blah[T1]T2 (x T1) T2 { ... }

x := blah[int]string(123)

I think the current draft is clearer:

func blah[T1, T2] (x T1) T2 { ... }

x := blah[int, string](123)

Axel Wagner

unread,
Jan 5, 2021, 4:31:31 PM1/5/21
to tapi...@gmail.com, golang-nuts
On Tue, Jan 5, 2021 at 7:25 PM tapi...@gmail.com <tapi...@gmail.com> wrote:
On Tuesday, January 5, 2021 at 8:43:44 AM UTC-5 axel.wa...@googlemail.com wrote:
On Tue, Jan 5, 2021 at 12:03 PM Anonymous AWK fan <awkf...@mailfence.com> wrote:
Why does there need to be a delimiter, there isn't one between chan and int in chan int, which I think is more readable than chan[int].

`chan` is a keyword, names of generic types and functions are identifiers.
So, in a way, yes, there is a delimiter: `chan`.

Is it possible to change `chan` to a builtin identifier? (I'm uncertain on this.)

Maybe. It would be backwards compatible. But why would you want to? `chan` being a keyword is a feature, not a bug, as I mentioned. Keywords and other delimiters serve as a signal to the parser.

OP suggested to use concatenation to for type argument application and used `chan int` as an example that no separator is needed. All I was pointing out is, that comparing apples to oranges, because `chan` is a keyword that provides additional context to the parser.

It might be possible to do without a separator. It might be possible to do so without parsing ambiguities. It might be possible to do without requiring unbounded lookahead. It might be possible while maintaining useful error messages and still separating the parsing from the type-checking phase (necessary for good tool workflows). I genuinely don't know if it is (and it's hard to prove a negative). All I was saying is, that a stronger argument than "it works fine for `chan int`" is needed to demonstrate that all of this is the case.

OP doesn't intend to use inconsistent syntax. I think it is just that OP hasn't got an idea on the function part yet.

I argued "if we use the same syntax for functions, readability suffers". So it doesn't matter really matter what they meant. Functions will either use the same syntax, meaning readability suffers, or functions will use a different syntax, meaning there is confusing inconsistency. It's an inescapable dichotomy: Choosing the suggested syntax for types means one of two bad outcomes.

 

On Tue, Jan 5, 2021 at 12:43 PM Anonymous AWK fan <awkf...@mailfence.com> wrote:
Axel, please send your reply to golang-nuts too, you can ignore the rest of this, I already sent it to you but not golang-nuts because I didn't reply to all.

> What is the "them" to be omitted if there is only one type parameter? It wouldn't make sense to omit the brackets (because there needs to be some delimiter between the name of the generic function/type and the type argument). But if there is only one type-parameter anyway, I don't know what else you would omit.

Why does there need to be a delimiter, there isn't one between chan and int in chan int, which I think is more readable than chan[int].

> Either way - you are using `sync.Map` to motivate this, with a clear analogue to `map`. But what about types that *don't* represent a map (like the Graph-example, where both type-parameters are on mostly equal footing)?

I think most generic types would only have 1 type-parameter and the syntax should be like the built-in ones ([]T, *T, func(Params) Result, map[Key]Elem and chan T). Putting the extra ones in square brackets is the best thing I can think of. Also I think Graph[Node]Edge or Graph[Edge]Node is still readable.

> And what about generic functions? I think
> Foo[int]string(bar, baz)
> isn't super readable, TBH.

I was only suggesting this for generic types.

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

tapi...@gmail.com

unread,
Jan 6, 2021, 7:40:24 AM1/6/21
to golang-nuts
On Tuesday, January 5, 2021 at 1:29:01 PM UTC-5 Brian Candler wrote:
On Tuesday, 5 January 2021 at 18:17:14 UTC tapi...@gmail.com wrote:
What about if the custom generic function syntax is consistent with built ones?
For example:

    v := MakeMyMap(mymap[T1]T2) 

Would this be mandatory for all generics which take two or more types?  It seems unbalanced to me.  Not everything is a hash-like container with a key and value.

Silly example:

func blah[T1]T2 (x T1) T2 { ... }

x := blah[int]string(123)

I didn't see anyone propose this syntax from the above comments.

Brian Candler

unread,
Jan 6, 2021, 9:40:15 AM1/6/21
to golang-nuts
On Wednesday, 6 January 2021 at 12:40:24 UTC tapi...@gmail.com wrote:
func blah[T1]T2 (x T1) T2 { ... }

x := blah[int]string(123)

I didn't see anyone propose this syntax from the above comments.

To quote the message which started the thread:

I think something like sync.Map[string]linked.List string is more readable than sync.Map[string, linked.List[string]].

I propose putting the last type parameter to a generic type after the square brackets and omitting them when there is only one type parameter.

I accept it was talking about type parameters to types, not type parameters to functions (although it would be weird if they were different).

But even if you think only about types, it doesn't make much sense to make the last type parameter distinct from the others. Another silly example:

# current
type Circle[T1,T2 any] struct {
    x,y,radius T1
    color T2
}

type myCircle Circle[float64,string]

# proposed
type Circle[T1 any]T2 any struct {
    x,y,radius T1
    color T2
}

type myCircle Circle[float64]string

Message has been deleted

tapi...@gmail.com

unread,
Jan 6, 2021, 11:35:19 AM1/6/21
to golang-nuts
I think OP the "parameter" in title means "argument" actually.
OP didn't specify how to declare a generic type.
Reply all
Reply to author
Forward
0 new messages