-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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
Comments
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. |
Thanks for your reply. 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.
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 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. |
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
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. |
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? 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. |
@davidacm |
This proposal seems to make |
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. 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. |
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 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, |
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 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. 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. |
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. Example:First, let me to define the example structure:
Example using "->" operator.
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.
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:
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. |
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:
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. |
@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 |
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. |
No further comments. |
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
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.
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?
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).
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.
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):
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.
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.
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
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.
The text was updated successfully, but these errors were encountered: