Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: Go 2: add a feature to group the methods bound to a specific receiver. #55863

Closed
davidacm opened this issue Sep 25, 2022 · 14 comments
Closed
Labels
Milestone

Comments

@davidacm
Copy link

davidacm commented Sep 25, 2022

I'm editing this comment to summarize the comments of the thread, so that new readers can have a better overview of the proposal.

Author background

  • Experience: one year coding in go and rust. 14+ years coding in languages like C, python, Kotlin, Java, typescript / javascript, dart, php, Erlan, prolog, etc.
  • My main languages are python and C (not c++ although I use it too).
  • I'm a visually impaired developer, and I'm an user of screen readers like NVDA.
  • I usually wouldn't mention this, but is an important factor for this feature in my opinion.

Related proposals

  • Has this idea, or one like it, been proposed before?
    Yes, the closed proposal: Go 2: remove redundant struct specifier on methods declarations #29459. but that was proposed in the incorrect way. I didn't like that idea because it didn't help readability and could cause confusion, language breaks with older versions, etc.

    • If so, how does this proposal differ?
      By using a specific statement syntax or pattern for this, with a block statement for the methods.

Proposal

I want to suggest to implement a way to group the methods for a specific receiver. The main goal of the proposal is to implement a feature that allows the user to declare the methods receiver in one place in order to avoid repetition of the receiver identifier and the type of the receiver for each method.

By this way, the user can avoid the repetition of write (receiverName receiverType) for each method asociated with a specific receiver. It would be in a similar way to how interfaces group method signatures, but with the full declaration of each method. A new type would not be created.

It could be like syntactic sugar for the current way of declaring receivers, to avoid repetition of the receiver for each method.

It should not add differences in the final compiled code.

In my limited knowledge of compilers (I have only written a basic compiler for C and one for Cobol) my suggestion shouldn't be hard to implement. And would not break the compatibility with the older code.
Even I can write a script to reconvert this proposal to the original form very easy. but then, my editor would tell me a lot of errors until I reconvert it to the original way.

Who does this proposal help, and why?

  • Removing the code repetition (identifier of the structs) repetition is an antipattern in any language and increases the complexity of refactoring. That is a code smell and even involves more lines of changes when a code is updated, doing the changes harder to review (like in a pull request). Is better if you just need to change the reference identifier in one place and not in 20, 30...
  • Would be more clear to see the purpose of the methods. Because they are grouped, indented (if the developer follow styling rules) and the signatures are shorter because the part of the receiver statement is not repeated for each method.
  • Grouped methods can help to the ide to detect symbols easier or the compiler to relate the methods with the appropriate receiver.
  • If the methods are easily related that they refer to the same receiver, the auto styling feature of the ides can format the code automatically. E.G. auto-indent the methods related with a receiver.
  • The entire group of methods related with a receiver could be folded, since they are inside a block. This will help to handle the code more easily. This could be useful for sighted people.
  • Would be easier to detect a group of related methods, if the methods were indented. The editor can know when a new indentation level is needed if a declaration of the group is detected.
  • This would help maintain a good order in the code and then, good writing standars. Is very common in languages to write related things in the same place. E.G. Structs have a specific block for the fields, interfaces a specific block for the method signatures.
  • Shorter function signatures will help to understand the code more easily. As a blind person, I prefer to read the function name as soon as possible. Consider that I don't see the code, I listen a synth voice reading all the lines of code, or a braille display where I touch the symbols. So, listen "func (s *SomeStruct) functionName" for each method, is a very unpleasant situation if you're using a screen reader or a braille display. I think that sighted people could benefit from this structure as well. Usually shorter lines are better.
  • It will improve the accessibility because of method signatures easier to read for screen readers (voice), easier way to locate method identifiers when using a braille display or when moving by words with the keyboard, because the method identifier for each signature will be at the same position in the distinct lines of the code. And I'm not looking at cases where people use magnifiers. Surely we can find even more advantages than disadvantages.

The proposal helps to a better accessibility.

I created this specific section because the accessibility and usability was my main motivation to open this proposal. Focusing on this matter (from the point of view of a blind user using a screen reader) when I read the code, I don't see the screen. I move and read by lines instead. There are things that help me to be faster when I'm reading or writing code with a screen reader (from now on SR).

  • Since I just see one line at a time, I prefer grouped things (blocks) because is easier for me to relate the tings. Even better if the group of things are indented because good SRs can detect and skip an entire block of certain level of indentation.
  • Most of times I prefer short lines with a few symbols (like "()[]." etc)
  • I prefer the identifiers as close to the beginning of the line code (that is, ignoring indentation because the cursor is usually placed after the indentation).
  • I prefer the identifiers at the same position in the line, whenever possible. In most languages, this is the standard for identifiers of functions. The receiver statement between "func" and the identifier, doesn't help to this.
  • when I read something, I hate to listen repeated information to get to the main text. In fact, it is an accessibility anti-pattern specified by the WCAG to send the user less relevant information repeatedly. It slows down navigation using a SR and slows movement around an interface or document. I know, we are talking of a language, not UI design. But I think that the ease and comfort to do things should also be taken into account in a language specification.

Use case example with a SR with the current way of declaring receivers.

let's see an example about why I consider tedious to define the receiver for each method. I will write a simple code, and then, the text of the voice output when I'm reading the code for each method signature.

Code.
type Student struct {
	Name string
	Age  int
}

func (s *Student) PrintStudentName() {
	fmt.Printf("student name: %s", s.Name)
}

func (s *Student) PrintStudentAge() {
	fmt.Printf("student age: %d", s.Age)
}

func (s *Student) PrintStudentInfo() {
	fmt.Printf("student name: %s, age: %d", s.Name, s.Age)
}
Voice output.

The voice output for each method signature is the following:

func left paren s star Student right paren PrintStudentName left paren right paren left brace
func left paren s star Student right paren PrintStudentAge left paren right paren left brace
func left paren s star Student right paren PrintStudentInfo left paren right paren left brace

As you can see, I have to listen "left paren s star Student right paren" each time I read the method signature. So, reading a code is very verbose and if you sum each time I have to read it, that represents a lot of unnecessary words and symbols because the code needed for declaring methods is very repetitive.

The same applies if I list the symbols (breadcrumbs) on VS Code, because the receiver part is read first and then, the identifier. Is an uncommon and unconfortable situation. And since the symbols can't be grouped, is not easy to skip a bunch of related methods to get the cursor to the next unrelated structure, function, ETC.

Another inconvenience is that if I want to move the cursor to the method identifier, I need to press control + right arrow 6 times. (using vs code) Compared with just one time when I need to focus the name of a function without the receiver declaration at the begining of the method signature.

If I'm usin a braille display, the identifier in a function is usually at the same place and this is good for readability. But if you add the receiver declaration, the position of the method identifier depends on (the size of the receiver type identifier plus the receiver name).

Some blind users prefer to use a simple and basic editor (not all platforms have accessible ides) and then the situation can be even harder.

Conclusion about accessibility.

An easier readability with SR is just one of the edges of grouping methods related with a receiver. But there are many advantages for everyone and not for blind people only.

I know that for now, we can't do anything with the older code. But it would be great when people start adopting a new way of doing the tings. Better readability, less repetitive code, easier coding, less code to write...

Possible approaches to declare groups of methods.

The initial idea was, a new keyword with a block statement to group the methods related with a specific receiver. But as @ianlancetaylor commented, is very hard to find a new keyword, because is complex to determine if the keyword has been used by someone in a GO code. So, is the hardest idea to implement.

Another way is to use an existent keyword followed by a symbol (or symbols) that indicates method grouping.

The number of grouped methods should not be limited to one or two, the devloper should be able to write N groups of methods for each receiver. So, the developer can sort the methods according to the functionality.

Let's see an example. Tis is a simple, but could be a very useful idea.

An example of my proposal (this was updated to increase the understanding):

type SomeStruct struct {
	someField any
}

"statement to start group definition"(p *SomeStruct) {
	func GetSomeField() any {
		return p.someField
	}

	func SetSomeField(val any) {
		p.someField = val
	}
}

"statement to start group definition"(v SomeStruct) {
	func AnotherMethodByValue() any {
		return s.someField
	}
}

replace the part "statement to start group definition" with any desired pattern. This is just an example of the structure.

Posible patterns to indicate method grouping.

One approach could be to use the operator "<-", used in channels, but won't confuse anyone because it will be used in a distinct context. Or add a new operator like "->", the same operator used in C++ to access pointer fields.
With the first operator, I don't see inconvenience, because go already has some operators with two or more functions. For example, the operator "*", used as an arithmetic operator and pointer operator too.

Example using "->" symbol.
// "func->" does not mean a function definition, it means a group of functions with the same receiver.
func->(p *SomeStruct) {
	func GetSomeField() any {
		return p.someField
	}

	func SetSomeField(val any) {
		p.someField = val
	}
}

func->(v SomeStruct) {
	func AnotherMethodByValue() any {
		return s.someField
	}
}

I like this approach because "->" is easy to recognize and the meaning of the arrow right "->" can be related easy with a group of things.

Example using "<-" operator.

the example with "<-" would be the same as above ("func<-" instead) that just in case if define a new operator were not possible.

But another way could be to use the ">-" symbol after the receiver declaration. To indicate that the specified receiver will be handled by the next methods. Seems very intuitive for me.

// "func->" does not mean a function definition, it means a group of functions with the same receiver.
func(p *SomeStruct) <- {
	func GetSomeField() any {
		return p.someField
	}

	func SetSomeField(val any) {
		p.someField = val
	}
}

func(v SomeStruct) <- {
	func AnotherMethodByValue() any {
		return s.someField
	}
}

I prefer the first example (or the "func<-" replacement) because although I used a new operator in the first example, it is very easy to realize that this is not a function definition statement.

Costs

  • This will involve a small change to the language. there would be two ways to do things in order to preserve compatibility. but it's an unavoidable fact in every evolution of a language.
  • This will add an extra and small optional step to learn go, but in my opinion this proposal is very easy to understand, and considering the advantages mentioned above, most of people would migrate to use the shorter and cleaner way when writing methods for receivers.
  • As @rittneje commented, can be harder for the user to remember the name of the receiver in a block of grouped methods. But this inconvenience can be mitigated following good programming style rules. For example, use the same identifier name as a convention for the receiver. In python, that is a very intuitive language to learn, "self" is usually used to refer to the instance, but the user could use another name if s/he wants. It's just a recommendation that the user can follow or not. All languages have conventions / recommendations, even GO.

I'm sure go has much more complex things that this proposal.

If go were able to implement a feature to group methods, this definitely will help in a lot of good ways for writing and reading code. It would be a great addition to GO. And although we can't do anything with the older code... the new code will be more easy to read, write, maintain, and with more accessibility features. Will be easier to process by parsers, linters and extensions. Will be more structured, with well-defined hierarchies. All those advantages will incentivize developers to use the new functionality of grouping methods by receiver.

All languages should evolve to be easier to understand, read, write and maintain. Repetition is not good in any language, even human languages.

My apologies if the idea was already presented before. I looked at the documentation and the issues, and I found the #29459 only. But I don't liked that idea because of the reasons commented in that thread. I consider that my suggested approach is very distinct and easier to implement and understand.

@gopherbot gopherbot added this to the Proposal milestone Sep 25, 2022
@ianlancetaylor ianlancetaylor added LanguageChange v2 A language change or incompatible library change labels Sep 25, 2022
@mattn
Copy link
Member

mattn commented Sep 25, 2022

Thanks your suggestion. I'm thinking whether this improvement can be made using existing methods. For example, this is one of the ideas for this, creating a single file containing only the definition of a struct and the methods of that struct would be an improvement (little or more). The filename should contains the struct name like 'users_methods.go' or 'users_properties.go'. Or adding comment to make the method groups. Before considering the changes, I want to know more description what part of the current Go syntax makes you difficult to read.

@davidacm
Copy link
Author

Thanks for your reply.
About the use of screen readers (SR) reading methods is just one of the inconvenience, but take into account the others arguments too.

The main goal of the proposal is to implement a feature to declare the methods receiver in one place to avoid repetition of the receiver identifier and the type of the receiver. It could be like syntactic sugar for the current way of declaring receivers.

Extra advantages.

Another advantage of grouping methods is that the entire block of methods could be folded. Is not good for accessibility (currently I've disabled folding feature) but can be useful for sighted people.

I think people would love more of go if they don't need to repeat the receiver declaration each time, if they have the option to group methods with just one receiver declaration for that group of methods.

Accessibility.

Focusing on this specific point (when using a SR) when I read the code, I don't see the screen. I move and read by lines instead. Since I just see one line at a time, I prefer grouped things (blocks) because is easier for me to relate the tings. Most of times I prefer short things with a few symbols (like "()[]." etc) and I prefer the identifiers as close to the beginning of the line code (that is, ignoring indentation because the cursor is usually placed after the indentation). In fact is one of the things I like from go, identifiers before type definition.

Use case example.

let's see an example about why I consider tedious to define the receiver for each method. I will write a simple code, and then, the text of the voice output when I'm reading the code for each method signature.

Code.

type Student struct {
	Name string
	Age  int
}

func (s *Student) PrintStudentName() {
	fmt.Printf("student name: %s", s.Name)
}

func (s *Student) PrintStudentAge() {
	fmt.Printf("student age: %d", s.Age)
}

func (s *Student) PrintStudentInfo() {
	fmt.Printf("student name: %s, age: %d", s.Name, s.Age)
}

Voice output.

The voice output for each method signature is the following:

func left paren s star Student right paren PrintStudentName left paren right paren left brace
func left paren s star Student right paren PrintStudentAge left paren right paren left brace
func left paren s star Student right paren PrintStudentInfo left paren right paren left brace

As you can see, I have to listen "left paren s star Student right paren" each time I read the method signature. So, reading a code is very verbose and if you sum each time I have to read it, that represents a lot of unnecessary words and symbols because the code needed for declaring methods is very repetitive.

Another inconvenience is that if I want to move the cursor to the method identifier, I need to press control + right arrow 6 times. (using vs code) Compared with just one time when I need to focus the name of a function without the receiver declaration at the begining of the method signature.

If I'm usin a braille display, the identifier in a function is usually at the same place and this is good for readability. But if you add the receiver declaration, the position of the method identifier depends on (the size of the receiver type identifier plus the receiver name).

Conclusion.

An easier readability with SR is just one of the edges of grouping methods related with a receiver. But as you can see in my previous comment, there are many advantages for everyone and not for blind people only.

I know that for now, we can't do anything with the older code.
But it would be great when people start adopting a new way of doing the tings. Better readability, less repetitive code, easier coding, less code to write...

@zephyrtronium
Copy link
Contributor

zephyrtronium commented Sep 25, 2022

Adding a new keyword is never backward compatible. I don't have much opinion on whether this is a good or bad idea, but I can suggest a syntax which would be truly backward compatible and arguably consistent with existing concepts. This would be to allow methods to use the declaration block syntax available with const and var. Your example would become:

type SomeStruct struct {
	someField any
}

func (p *SomeStruct) (
	GetSomeField() any {
		return p.someField
	}

	SetSomeField(val any) {
		p.someField = val
	}
)

func (v SomeStruct) (
	AnotherMethodByValue() any {
		return v.someField
	}
)

This is backward compatible because, currently, method declarations require a bare identifier to follow the receiver, so the presence of parentheses would indicate a block instead.

@davidacm
Copy link
Author

davidacm commented Sep 26, 2022

Hi @zephyrtronium. I don't mean backward compatibility. I mean that you can continue running older code in new versions, not new versions code in older compilers. Currently, can you run go 1.9 in go 1.4 compilers for example?
I.E. what about type aliases introduced in go 1.9?

How can your suggestion be backward compatible if go expects an identifier after receiver declaration but you are sending a block statement?

I like your suggestion and it's even more easy to read (because you dont use func for each method) but could be harder to implement in the language. And in my opinion, can be a little harder to understand for new GO developers.

I think that this feature should be like syntactic sugar, to help to make it easier to write and read methods with receivers involved. The main goal should be to remove the current code repetition, required for methods with receivers. And the suggested solution is by grouping them in a block, and an initial receiver declaration for that group of methods.

It should not add differences in the final compiled code.

Currently, we can group signature of methods using interfaces, I think that the same thing but for groups of complete methods can be useful. Although is unrelated, because I don't mean to create new types; it should be transparent for the parser.

P.S. Although I said "it's even more easy to read (because you dont use func for each method)" the keyword "func" should be required even in the grouped methods to maintain the consistency between function declarations.

@zephyrtronium
Copy link
Contributor

@davidacm
Good points. I took a mental shortcut I had used in the past, but which didn't fit here. 🙂

@ianlancetaylor
Copy link
Contributor

This proposal seems to make groupMethods a keyword. That is not backward compatible in the way that I use that term, because a statement like groupMethods := 1 would no longer compile. That is, Go code that is valid today would not be valid if we adopt this proposal. That doesn't in itself mean that we can't accept the proposal, but it is something to consider.

@davidacm
Copy link
Author

Hi @ianlancetaylor, you're right. I wrote the keyword "groupMethods" as an example, it doesn't mean that this keyword should be the word used for this. In fact, I don't like this specific word because that is very long to be used as a keyword.

For me, using a keyword seems to be the easiest way to implement this. But find a good keyword could be very hard, I'm thinking about this since your comment.

So, the suggestion of @zephyrtronium could be a good alternative to start, because that does not require a keyword. is more complex because it would require to recognize a pattern, so the implementation would not be so direct as by using a keyword.
I can't think in a good pattern to group methods. Start it with the keyword func could be confusing because developers could see it as functions inside a function, and will be unclear if the compiled result is the same by using the current way or this proposal. I think that my knowledge of GO is not enough to find a good pattern without using a keyword.
I'm thinking in some alternatives, (E.G. use the func keyword with an additional symbol that indicates grouping (preferably a prefix symbol because the group indication would be recognized immediately by the user) but might not be an elegant solution.

In my opinion the repetition of the identifiers for each method associated with a receiver is tedious and I don't think I'm the only one who thinks that way. But I have tried to present all the arguments why I would like to handle that in a different manner. Surely there are more arguments in favor of unifying the methods binded to a receiver, so it would be good to think together of a good and simple solution.
I'm convinced that allowing grouping of methods bound to a receiver would be a great addition to GO, because of my arguments I wrote in my previous comments. This would solve a lot of things that feel unconfortable in GO.
It can help readability, accessibility, refactoring, code review, code writing, ETC.

@davidacm davidacm changed the title proposal: Go 2: add a feature to group those methods related with a specific struct. proposal: Go 2: add a feature to group the methods bound to a specific receiver. Sep 26, 2022
@rittneje
Copy link

Personally, I think this change would lead to code that is harder to read. Today it is fairly simple for me to understand what each variable in the method body is, especially if the method is short (generally good practice). But with this, I'd have to scroll up to the top of the block (which could be arbitrarily long) just to see what the name and type of the method receiver are, leading to similar frustrations to what I experience today with global variables. In languages like Java this isn't an issue because this is a special keyword with a well-defined meaning.

Another issue with this proposal is that in practice the method receiver can either be a pointer or non-pointer, but those could not be in the same group. So for example, MarshalJSON and UnmarshalJSON would either be separated, or would not leverage this feature.

@davidacm
Copy link
Author

hi @rittneje.

Your comment is a good and very valid point. But this inconvenience can be mitigated following good programming style rules. For example, use the same identifier as a convention for the receiver.
In python for example, that is a very intuitive language to learn, you usually use self to refer to the class, but you could use another name if you want. It's just a recommendation that you can follow or not.
In python although you need to specify self for each method, is not tedious because self is a short identifier and you don't need to write the type of the identifier, that is the problematic part because the type is not of the same size in all cases. In terms of accessibility, in python the name of the method always begin at the same position, because you don't need to add anything before of the method identifier.

In all languages in the world, you can choose between a good or bad standard to write the code. that's why documents of recommendations when writing code exist.
Although is not a forced requirement, it's usually to use just one letter to name the receiver in GO. And is nice, but you can choose to write a long very name, if you want. But if you follow the conventions and recommendations, usually it will be easy to identify if it's a receiver identifier. It's not the same case as global variables, because in global variables a programmer can write N variables, but in go you can write just one receiver for each method. And would be the same in the proposal.

For me, as a blind person, indentation is very important because my screen reader can skip an entire block of certain level of indentation, or go to the start of a code block according it's indentation too, I can skip between levels of indentation in various ways. But although is not a requirement, the 99.9% of developers indent the code. So, even if it's not a forced requirement (except in python) is a very stablished standar.

Your comment has made me see that this proposal represents a small penalty but you get good benefits instead: less repetitive code, fewer changes when updating a code (and therefore shorter and easier to review commits), easier to read and write method signatures , grouped and indented code (if you want to indent) possibility to fold the grouped code related to the receiver.

It will improve the accessibility because of method signatures easier to read for screen readers (voice), easier way to locate method identifiers when using a braille display or when moving by words with the keyboard, because the method identifier for each signature will be at the same position in the distinct lines of the code. And I'm not looking at cases where people use magnifiers. Surely we can find even more advantages than disadvantages.

P.S. I'm not saying that GO has accessibility issues. In the end it's just code and people can adapt. It's more a matter of comfort, intuitiveness, ease, readability, maintainability. In short, it would be a usability improvement in many ways.

All languages should evolve to be easier to understand, read, write and maintain. Repetition is not good in any language, even human languages.

@davidacm
Copy link
Author

davidacm commented Oct 2, 2022

Hi again. I've been thinking in a good way to implement this feature without adding a new keyword. Currently, my ideas hover around extending the functionality of the keyword "func".

One approach could be to use the operator "<-", used in channels, but won't confuse anyone because it will be used in a distinct context. Or add a new operator like "->", the same operator used in C++ to access pointer fields.
With the first operator, I don't see inconvenience, because go already has some operators with two or more functions. For example, the operator "*", used as an arithmetic operator and pointer operator too.

Example:

First, let me to define the example structure:

type SomeStruct struct {
	someField any
}

Example using "->" operator.

// "func->" does not mean a function definition, it means a group of functions with the same receiver.
func->(p *SomeStruct) {
	func GetSomeField() any {
		return p.someField
	}

	func SetSomeField(val any) {
		p.someField = val
	}
}

func->(v SomeStruct) {
	func AnotherMethodByValue() any {
		return s.someField
	}
}

I like this approach because "->" is easy to recognize and the meaning of the arrow right "->" can be related easy with a group of things.

Example using "<-" operator.

the example with "<-" would be the same as above, that just in case if define a new operator were not possible.

But another way could be to use the ">-" symbol after the receiver declaration. To indicate that the specified receiver will be handled by the next methods. Seems very intuitive for me.

// "func->" does not mean a function definition, it means a group of functions with the same receiver.
func(p *SomeStruct) <- {
	func GetSomeField() any {
		return p.someField
	}

	func SetSomeField(val any) {
		p.someField = val
	}
}

func(v SomeStruct) <- {
	func AnotherMethodByValue() any {
		return s.someField
	}
}

Other thoughts.

I prefer the first example (or the "func<-" replacement) because although I used a new operator in the first example, it is very easy to realize that this is not a function definition statement.

When I'm exploring the code by symbols ("control + shift + ." in VS Code) My screen reader reads the receiver part for each method too. Usually, in that kind of structure, the screen reader should read the symbol name directly. But address this issue by the extension of go, with the current implementation of receivers, will be very hard because:

  • If the receiver part is removed in that view, since you can write the methods in no particular order, then can be hard to find the required method.
  • Group methods by receiver in the view, that would breaks the order of methods and could confuse the reading user.

So, if go were able to implement a feature to group methods, this definitely will help in a lot of good ways for writing and reading code. It would be a great addition to GO. And although we can't do anything with the older code... the new code will be more easy to read, write, maintain, and with more accessibility features. Will be easier to process by parsers, linters and extensions. Will be more structured, with well-defined hierarchies. All those advantages will incentivize developers to use the new functionality of grouping methods by receiver.

@apparentlymart
Copy link

apparentlymart commented Oct 4, 2022

While experimenting with some of the ideas discussed above, I tried something like what @zephyrtronium described:

package main

type Foo struct{}

func (f *Foo) (
	Blah() {}
)

Of course I was expecting this to be treated as invalid because the Go specification doesn't allow this syntax today, but the specific error it produced was quite interesting:

./prog.go:5:15: syntax error: unexpected (, expecting name or (
./prog.go:6:12: syntax error: unexpected { after top level declaration

Apparently the parser encountered an unexpected opening parenthesis while also expecting an open parenthesis, which seems curious.

I imagine this specific error message is just a bug/quirk in the parser, but it makes me wonder if this is somehow ambiguous in a way I'm not seeing: the wording of this error message makes me think there's a branching point in the parser here where a name leads down the normal method parsing path whereas an open parenthesis (assuming it had been valid here) would lead down some other path.

If this isn't actually ambiguous, it does seem to be the most similar to the existing pattern of grouping multiple declarations together into a single statement with a shared prefix, as in these other examples:

import (
	"fmt"
	"testing"
)
const (
	Foo SomeEnum = iota
	Bar SomeEnum
)
// (and similarly for the "var" keyword)
// This one seems non-idiomatic, but is still allowed per the spec and
// is included as an explicit example in the current spec text.
type (
	Foo struct{}
	Bar interface{}
	Baz int
)

...it also seems like it is the option which introduces the least additional noise for a screen reader to read through: just one extra "left paren" and "right paren" pair in return for only reading "left paren p star Foo right paren" once across all of the grouped declarations.

@ianlancetaylor
Copy link
Contributor

@apparentlymart Thanks, filed #56022 for the bad error message. It's not ambiguous. The parser is expecting either a receiver or a name after the keyword func. It just happens to give an error message saying that it expects a receiver even if it already found a receiver.

@ianlancetaylor
Copy link
Contributor

If we do this, I think we should use the syntax that @zephyrtronium shows in #55863 (comment). Using that syntax would also have the advantage of letting us have a single declaration of receiver type parameters for a generic type.

While we appreciate the accessibility argument, most Go code will continue using the current approach. And there will always be many reasons to define methods in different files, rather than in a single group. In fact, we do not want to encourage people to always define methods in a group, because while that can be more compact, it also encourage people to group together things that are not naturally grouped.

It's also worth considering that a group with many methods means that some of the context—the receiver name—will be far away from the method, obscuring what a name refers to. This would be particularly confusing if the receiver name is also a different name at package scope, which does happen from time to time.

Therefore, this is a likely decline. Leaving open for four weeks for final comments.

@ianlancetaylor
Copy link
Contributor

No further comments.

@ianlancetaylor ianlancetaylor closed this as not planned Won't fix, can't repro, duplicate, stale Dec 7, 2022
@golang golang locked and limited conversation to collaborators Dec 7, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

7 participants