This is a proposal for a very simply change to go to help reduce the verbosity of having to prefix with package names when the scope is 100% unambiguous,
This proposal was first discussed a bit on issue #29326 here through _(at least)_ here.
The proposal is to allow omitting the package name and simply use a dot (.) when the package name can unambiguous be determine by a set of simply scoping rules in cases where — once the simple rule(s) are learned — it was be relatively intuitive to know what package the dot-prefix symbol relates to.
I will give examples for one context — passing parameters to a func — but there may be other contexts where this concept could also apply. Examples found by searching for examples in the Go SDK _(these lines of code are independent and are not related in any way):_
Currently:
f,err = os.Open(os.DevNul)
http.Handle("/tmpfiles/", http.StripPrefix("/tmpfiles/", http.FileServer(http.Dir("/tmp"))))
out = strings.ReplaceAll(strings.ReplaceAll(strings.TrimSuffix(out, "\n"), " ", ""), "\n", " ")
Using proposed:
f,err = os.Open(.DevNul)
http.Handle("/tmpfiles/",.StripPrefix("/tmpfiles/",.FileServer(.Dir("/tmp"))))
out = strings.ReplaceAll(.ReplaceAll(.TrimSuffix(out, "\n"), " ", ""), "\n", " ")
To clarify:
os.Open() means you can drop os. from .DevNulhttp.Handle() means you can drop http. from .StripPrefix()http.StripPrefix() means you can drop http. from .FileServer()
http.FileServer() means you can drop http. from .Dir()strings.ReplaceAll() means you can drop strings. from .ReplaceAll()
strings.ReplaceAll() means you can drop strings. from .TrimSuffix()This would be a small change/help, but it would be nice for those cases where you are using a lot of package prefixed consts and vars as parameters.
One issue with this is that it makes certain kinds of refactoring less safe. Consider moving a stand-alone function F from package a to package b. Normally it would be safe to simply replace all calls of a.F with b.F, but no longer.
@josharian So is this what you mean?
package foo
const A = 1
const B = 2
const C = 3
func MyFunc( a,b,c int ) {}
from package foo to package bar but did not move any of A, B or C:
foo.MyFunc( .A, .B, .C)
Then you are saying this would break?
bar.MyFunc( .A, .B, .C)
If that is the concern I guess I can see it. _(I had not considered that because I use GoLand and it tend to elegantly handle fixing up everything correctly when refactoring.)_
I wonder if that is a big enough concern in general to mean that we'll have to forever be stuck with verbosity?
If go can make the package prefix to be optional.
This would also slow down tools, as they'd have to search all imports to find definitions. The change doesn't make the code more readable either, unless one has a perfect memory of what all of the imported packages export.
Are you sure the idea is unambiguous?
e.g.
type Rectangle struct {
x,y,w,h int
}
func (rect *Rectangle) Surface() (surface int) {
surface = .w * .h
return
}
I also propose to take a few of your existing sources and adapt them to your proposal and then evaluate if (according to your opinion) the difference between the successive (adjacent) "," and "." is still clear enough. When passing a multiple function arguments in this style you get something like
NewRectangle(.x,.y,.w.,h)
This sure looks a lot like C++ ADL lookup, which is quite a bit higher on the complexity scale than we've budgeted for Go. Yes, you can get shorter code, but it's far too cryptic.
_"Are you sure the idea is unambiguous? how about when it is used INSIDE the function body..."_
That was not the scenario I was proposing
However, I think you are applying a different definition of _"unambiguous"_ than I was intending. I did not mean _"obvious"_ I meant _"only able to be interpreted one way given a simple set of rules."_
So in your example, the rules could be established such that they are unambiguous, using one of either the two different interpretations that you envisioned competing.
_" then evaluate if the difference between the successive (adjacent) "," and "." is still clear enough."_
I believe your example was not run through got fmt. If it had been I think it would have been more clear:
NewRectangle(.x, .y, .w, .h)
It also had a syntax error. Was that possibly intentional?
_"which is quite a bit higher on the complexity scale than we've budgeted for Go."_
@rsc Any chance — and I mean this rhetorical question sincerely as I have a high level of respect for you — that your response is rooted more in unintentional dogma and less in how well the potential design decision might work out of developers using Go?
@mikeschinkel It seems clear to me that your proposal will make reading Go code more complicated, which is a cost. To me the benefit seems small. And the sentiment on this issue so far is largely negative.
I think this is also under-specified, in that you haven't spelled out the scoping rules. What happens when I write
ioutil.ReadAll(myos.Open(.DevNull))
Do we look up DevNull in myos or ioutil or both?
Could we capture much of the benefit of this proposal by having an editor that auto-completed package names based on identifier names?
_" It seems clear to me that your proposal will make reading Go code more complicated, which is a cost. To me the benefit seems small. And the sentiment on this issue so far is largely negative."_
Fair enough.
But I would ask is there anything you on the Go team can do to cut down on some of the verbosity of coding with multiple packages, in ways that would not make Go more complicated in your eyes?
Admittedly this is not an earth shattering problem, and even though I am defending this specific proposal that _does not mean_ I am dug-in on this proposal; I am happy to let this one go.
But I do find some of the more painful aspects of understanding Go code is its verbosity and would hope that some ways to address that could be embraced for Go 2.
_" What happens when I write..."_
Just to answer your question, in my proposal the following would be equivalent to your example as I was intending that the parens define the enclosing scope and the parens derive their package from their func's package. Or said another way, the closest, most inner package wins:
ioutil.ReadAll(myos.Open(myos.DevNull))
Could we capture much of the benefit of this proposal by having an editor that auto-completed package names based on identifier names?
No, I already have that in GoLand.
The problem I was trying to address is reading long lines of code. I am finding that Go code often exceeds 80 characters in length and sometimes even exceeds 120 characters in length. This means horizontal scrolling to see the entire line, and even when you don't have to scroll long lines are harder to grok than shorter lines, in general.
This proposal would not have been a silver bullet to address all very long lines of Go code but would have helped a bit.
In general it is not a goal for Go code to be terse. Clearly it should not be excessively verbose. But clarity of things like name lookup is valued over terseness. That is why, for example, method receivers are named, and there is no implicit lookup of names in the scope of the method receiver, as there is in languages like C++ and Java.
@ianlancetaylor Understood.
To clarify, I am not asking for terseness — I too think terseness is detrimental — I am just respectfully asking the Go team consider ways to reduce _excessive_ verbosity, e.g. line lengths and repetitiveness.
Most helpful comment
One issue with this is that it makes certain kinds of refactoring less safe. Consider moving a stand-alone function F from package a to package b. Normally it would be safe to simply replace all calls of a.F with b.F, but no longer.