[generics] replace ()/(type ) with :[]

165 views
Skip to first unread message

Kiswono Prayogo

unread,
Jun 18, 2020, 9:46:31 AM6/18/20
to golang-nuts

Personally () parentheses seems like to be harder to read, too similar with function calls.

It would be nicer if we use brackets instead [] like in Nim, since [] only used in array/slice/indexing (index key or empty), which is more readable than () that used in many ways (receiver, parameter, return value, function calls, grouping, etc).


Current https://go2goplay.golang.org/p/zBO9K4-yXck


package main

import (
	"fmt"
)

type Stack(type T) struct {
	list []T
}
type Pair(type K, V) struct {
	Key K
	Val V
}

func New(type T)() Stack(T) {
	return Stack(T){list: []T{}}
}
func (s *Stack(T)) Push(v T) {
	s.list = append(s.list, v)
}
func main() {
	a := New(int)()
	fmt.Printf("%#v\n",a)
	b := Stack(Stack(int)){}
	fmt.Printf("%#v\n",b)
	c := Pair(string,int){}
	fmt.Printf("%#v\n",c)
}


Probably more readable syntax:

  • F:T for single generic declaration and usage, eg. Stack:int, BinaryTree:string, Vector:Person
  • F:[T1,T2] for multiple generic declaration and usage or when having constraint or array/slice, eg. Pair:[string,int], HashTable:[string,int], Stack:[T Stringer], Stack:[[3]int]
  • F:[T1:T2] for nested generic usage, eg. Stack:[Queue:int]

So for example, if we want to use Stack that stores string-int Pair, we could use: Stack:[Pair:[string,int]], if we want to use Pair with string key and integer Stack value, we could use: Pair:[string,Stack:int]

The pros:

  • we could always differentiate between function calls (that returns function) and generics by : or :[] symbol, other than just checking whether passed parameter is a datatype or variable/constant identifier. = unambiguous
  • consistent syntax between generic declaration and usage
type Stack:T struct {
	list []T
}
type Pair:[K,V] struct {
	Key K
	Val V
}

func New:T() Stack:T {
	return Stack:T{list: []T{}}
}
func (s *Stack:T) Push(v T) {
	s.list = append(s.list, v)
}
func main() {
	a := New:int()
	fmt.Prinf("%#v\n",a)
	b := Stack:[Stack:int]{}
	fmt.Printf("%#v\n",b)
	c := Pair:[string,int]{}
	fmt.Printf("%#v\n",c)
}

Kiswono Prayogo

unread,
Jun 18, 2020, 10:26:35 AM6/18/20
to golang-nuts

Bebop Leaf

unread,
Jun 18, 2020, 10:35:59 AM6/18/20
to golang-nuts
I don't know how many of us are here, but I for one, really feels comfortable and familiar with use of parentheses.

I think anyone that is familiar with closures and functional programming would see it fitting, as if the generic function is a function that takes some type parameters and returns a function.
It would be the way generic function work, if types are data in Go, imo.

BTW, I think the fact that the new parentheses are never nested into other use of parentheses makes reading them much less confusing than supposed, but maybe that is just for me.

And disclaimer: I do write common lisp codes as side hobbies, so take this with a grain of salt - I might developed some kind of adaption to parentheses that I am not aware of.

Kiswono Prayogo

unread,
Jun 18, 2020, 11:11:08 AM6/18/20
to golang-nuts
I made this because one of my friend post in the group



that quite hard to read especially at lines 7-12 '__')
also for lines 18 and 22 if the type is not primitive type (another identifier), feels like a function that returns a function

compared to this version


but not sure, probably it just me

Bebop Leaf

unread,
Jun 18, 2020, 12:04:43 PM6/18/20
to golang-nuts

> that quite hard to read especially at lines 7-12 '__')

It might just be me, but I don't feel anything special... Go does not have any other syntax (e.g., macros) that let you use parentheses in type definition unless followed immediately by a `func`, and those parentheses can not be nested.

> also for lines 18 and 22 if the type is not primitive type (another identifier), feels like a function that returns a function

I actually like this kind of feeling. It actually is almost what happening - you can consider it as a function returning a function most of the time (the only time I can think of that you care about the difference is you want to optimize out the function call), but when you really care about the difference, you can quickly find out that by noticing float is actually a type.

Consider this snippet of code, from the example code in standard library `sort` (SortKeys):

    By(name).Sort(planets)
    fmt.Println("By name:", planets)

    By(mass).Sort(planets)
    fmt.Println("By mass:", planets)

    By(distance).Sort(planets)
    fmt.Println("By distance:", planets)

Here, `By` is actually a type of function - and the seemingly function call `By(...)` is actually a type conversion, not a function returning something with a `Sort` method.  But I won't say this cause much confusion. I think the use of the proposed generic function works in the same way.

Thomas Bushnell, BSG

unread,
Jun 18, 2020, 12:31:04 PM6/18/20
to Kiswono Prayogo, golang-nuts
On Thu, Jun 18, 2020 at 9:46 AM Kiswono Prayogo <kis...@gmail.com> wrote:

Personally () parentheses seems like to be harder to read, too similar with function calls.

This is exactly why I like it. These are parameters, and should be thought of just like other parameters in a function call. 

lgo...@gmail.com

unread,
Jun 18, 2020, 2:30:23 PM6/18/20
to golang-nuts
"  These are parameters, and should be thought of just like other parameters in a function call. .."

parameters yes,  but not in the current go context of 'parameter'   .. Is it not better syntax for  'generic parameters' to use special, distinctive symbols e.g.  C++ does via < Type > ?? 

David Riley

unread,
Jun 18, 2020, 2:56:55 PM6/18/20
to Thomas Bushnell, BSG, Kiswono Prayogo, golang-nuts
This is precisely why I don't like it, because they are *metaparameters*. Having a variable length set of options where the length determines what the FIRST options are is actually really confusing, particularly because we already have an optional set of parentheses for the return types.

I understand that folks familiar with Lisp/Scheme might be comfortable with it, but I'm very comfortable with both of those and I find this a bit maddening. Could be just me.

I really do like the idea of separate characters for metaparameters on functions; another option would be Python/Java style decorators on the function (e.g. @type(A)), though I think in practice that might be a bit unpleasant (I really wish there were a better way to do the decorator pattern in Go, though). I'm somewhat surprised that the parser developers find the variable-length list of parenthetical clauses *easier* to deal with; it seems like a good way to introduce ambiguity in the parsing. That may be part of why the parser currently breaks when returning closures. But then, I'm not the one writing the parser, so I'm running on a lot of assumptions.

I'm all for giving this time to settle and see how it plays out, of course. One thing I've learned is that it's generally a fool's errand to optimize for my use cases, at least if anyone else is going to like it. But consider this my vote for alternate delimiters.


- Dave

signature.asc
Reply all
Reply to author
Forward
0 new messages