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: remove dot imports from the language #29326

Open
ianlancetaylor opened this issue Dec 18, 2018 · 69 comments
Open

proposal: Go 2: remove dot imports from the language #29326

ianlancetaylor opened this issue Dec 18, 2018 · 69 comments

Comments

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Dec 18, 2018

This issue is broken out of #29036.

We should remove dot imports from the language (import . "path"). Quoting @rogpeppe:

The official guidelines suggest that a dot import should only be used "to let the file pretend to be part of package foo even though it is not". This is a stylistic choice and strictly unnecessary. The tests can still use package-qualified names with only minor inconvenience (the same inconvenience that any external user will see).

Other than that, I believe the most common use is to make the imported package feel "close to a DSL". This seems to be actively opposed to the aims of Go, as it makes the programs much harder to read.

@josharian
Copy link
Contributor

@josharian josharian commented Dec 19, 2018

While experimenting with #20104 just a few days ago, dot imports were incredibly useful—instead of having to plumb a bunch of context through complicated code generation scripts and carefully conditionally adjust a dozen format strings, I could just dot import ssa in the relocated files. Long term, I would choose to do the work to eliminate the dot import. But it was very helpful as scaffolding. And not just for experimentation: the dot import is a single line change for reviewers, whereas qualifying every package use would have made the diff unreadable (and unrecognizable to git as a file move).

To be clear (unlike type aliases), this kind of refactoring is strictly speaking possible without dot imports. It is just a lot more graceful with them.

Loading

@agnivade
Copy link
Contributor

@agnivade agnivade commented Dec 19, 2018

Other than that, I believe the most common use is to make the imported package feel "close to a DSL".

Yes, this is what Goa does; which is used by a lot of people. If we remove dot imports, the entire package will break.

I have no issues either way. Just wanted to point this out.

Loading

@beoran
Copy link

@beoran beoran commented Dec 19, 2018

DSLs are an important use case for dot imports, I disagree that DSLs make programs harder to read. On the contrary, they make programs easier to read for specific types of program, such as the Goa examples shows, and more importantly also make a program easier to understand for non-programmer domain experts for whom the DSL has been designed. This is why I respectfully ask that this proposal be rejected.

Loading

@alanfo
Copy link

@alanfo alanfo commented Dec 19, 2018

Although dot imports are a feature which should be used sparingly, I can't see the sense in banning them altogether.

There are definitely occasions when they are useful for testing and experimentation and there are some other reasonable uses as well - see for example golang/lint#179.

I often use them myself when importing the math package, so I can just write Cos instead of math.Cos. Admittedly this isn't the greatest of use cases but, for me at least, there is no risk of confusion as I would never use the names of math functions as public names in my own packages.

Loading

@deanveloper
Copy link

@deanveloper deanveloper commented Dec 19, 2018

As per mentioned in #29036 (comment)

I've found dot-imports to be useful when writing tests. For instance:

package mypack_test

import . "github.com/deanveloper/mypack"

// ...

I wouldn't really mind the dot-import being removed. I was just adding my particular use case for them.

And to reiterate, I wouldn't mind dot-import being removed. It's just what I use them for.

Loading

@gotzmann
Copy link

@gotzmann gotzmann commented Dec 19, 2018

Most of the popular programming language use some form of "dot import" without any hassles. Hows ftm.Printf is more clear then simple print or echo?

Loading

@bcmills
Copy link
Member

@bcmills bcmills commented Dec 19, 2018

Tests are a great argument for removing dot-imports. I've seen lots of tests that failed to catch extremely redundant identifiers, because the tests and examples didn't include the package name as everyone else would.

Loading

@bcmills
Copy link
Member

@bcmills bcmills commented Dec 19, 2018

At first glance math seems like a great argument for not removing them, but then consider functions like Jn, Y0, and Log: the former two are somewhat obscure, and the latter could easily collide with a similarly-named function in another package. I could see a reasonable argument for dot-importing trigonometric functions and rounding functions, but math overall seems like a bit of a stretch.

Loading

@deanveloper
Copy link

@deanveloper deanveloper commented Dec 19, 2018

Hmm now that I think of it, I feel like I've heard a lot of this stuff before. I feel like this might be a duplicate, but I searched for proposals of removing dot imports but didn't find anything

Loading

@natefinch
Copy link
Contributor

@natefinch natefinch commented Dec 19, 2018

Dot imports make your code lie. That's Bad.

When you write AssertEqual(t, got, expected) and your current package doesn't have an AssertEqual function... that code is lying. If you're reading that in a code review (i.e. not in an IDE with hover text etc), you would not be crazy to think AssertEqual is some function in the current package.

Also, if you then copy that code to a different file in the same package... it will fail to compile, and there's nothing goimports can do to figure out where AssertEqual comes from. So you have to Just Know™ where it comes from and add the import yourself. That's bad, too.

I don't believe DSLs of the type made popular in dynamic languages such as python and ruby belong in Go. Those languages specifically give you tools so that you can make non-native functionality look like native functionality. Those languages intentionally let you play fast and loose with types and what the code actually does when you type foo += bar can be vastly different depending on what foo and bar are. Go is not that language. foo += bar will always be simple to understand in Go. There's only a couple things it can possibly mean.

DSLs sacrifice clarity of what the machine is doing for clarity of what the code says. That's not Go's way. You should not need external context to understand where an identifier comes from and what it is calling or doing.

Loading

@networkimprov
Copy link

@networkimprov networkimprov commented Dec 19, 2018

Maybe the proposal should be "flag dot imports with go vet"?

@ianlancetaylor and others have asserted that Go2 shall not break existing code except where unavoidable for essential new features.

Loading

@deanveloper
Copy link

@deanveloper deanveloper commented Dec 19, 2018

@ianlancetaylor and others have asserted that Go2 shall not break existing code except where unavoidable for essential new features.

This seems to be his proposal, haha. I think that if there's a consensus that a feature is actively harmful that it should be (at least) considered for removal. IIRC Russ Cox (maybe it was Rob Pike) have talked about removing shadowing if we get the check keyword.

Loading

@networkimprov
Copy link

@networkimprov networkimprov commented Dec 19, 2018

Assignment redeclaration would also be flagged by Go2 vet :-)

Loading

@deanveloper
Copy link

@deanveloper deanveloper commented Dec 19, 2018

For now, yes, but what I was trying to say is that a full removal of a feature is something we have already considered (and seem to still be considering, looking at this proposal). Saying that significant members "have asserted that Go2 shall not break existing code [...]" makes it sound like feature removal is something that shouldn't even be listened to, rather than something we'd rather not do.

Loading

@ianlancetaylor
Copy link
Contributor Author

@ianlancetaylor ianlancetaylor commented Dec 19, 2018

@deanveloper I should clarify that although I opened the issue, it's really @rogpeppe 's proposal. I split out of #29036 to clarify that issue.

Personally I'm somewhat against this proposal because I don't think the benefit of clearer code is worth the cost of breaking existing packages. But I'm willing to be convinced otherwise.

Loading

@mikeschinkel
Copy link

@mikeschinkel mikeschinkel commented Dec 30, 2018

I literally just learned about dot imports last week, and have been looking forward to using them in the future, mostly for the handful of random helper functions which can typically be isolated to one package. That way I can write more compact code that uses those helper functions and the code would be easier to reason about for me or anyone who pays attention to the imports.

Idea: If you want to do away with dot imports, consider allowing only one dot import per file?

Loading

@rogpeppe
Copy link
Contributor

@rogpeppe rogpeppe commented Dec 30, 2018

For the record, the reason I bundled this issue up with #29036 was that it's necessary to do this if we want to make all imported symbols predictable, which is, I believe a useful property to have globally.

I sympathise with the use of dot imports in @josharian's scenario, but I see it as somewhat of a niche case, which surely could be addressed with a little more additional tooling to make it easier to package-qualify chosen identifiers in generated code. Getting git to recognize diffs of files that move between packages is often a problem anyway. There are many such cases where code is refactored and the diffs are large because the package qualifiers change. I see that as the more general problem here, and dot-imports aren't a good solution in most such cases.

In short, I think that the readability advantages of having properly qualified identifiers everywhere outweigh the occasional extra burden of adding the qualifiers.

Loading

@mateuszmmeteo
Copy link

@mateuszmmeteo mateuszmmeteo commented Jan 29, 2019

In my case during image manipulations I'm often using "." import as possibility to load JPEG (image/jpeg) or PNG (image/png) lib.
Without "dot import" this library will be removed and I cannot use jpeg.Encode() method.

How do you want to solve such issue?

Loading

@deanveloper
Copy link

@deanveloper deanveloper commented Jan 29, 2019

I don't understand your issue, can you provide an example? How are you using dot imports and why can't you just use jpeg.Encode?

Loading

@creker
Copy link

@creker creker commented Jan 30, 2019

@mateuszmmeteo why do you need dot import for that? Usually you write import _ "image/png" and let image.Decode figure out the rest.

Loading

@StephanVerbeeck
Copy link

@StephanVerbeeck StephanVerbeeck commented Apr 23, 2019

I am currently using lxn/walk which has a declarative package for defining forms.
Removing the "." import would brake ALL EXISTING code that uses this package.
And I personally would be very angry having to write "declarative.Button" instead of "Button".
I would have to write the name of the package several thousand times to be exact.
That is not my idea of making code more readable, seems to be more an idea of enforcing restrictions for which I see not a point that they should exist. The original designer who created the dot-import was correct in providing this functionality.

On the other hand, every programmer should also have the common sense to NOT have multiple dot-imports in the same GO-source. Because as long as there is only one dot-import it is PERFECTLY clear as to where the methods and types belong.

I would even go further to request that the intellisense of "Visual Studio Code" be improved to understand this.

Loading

@mikeschinkel
Copy link

@mikeschinkel mikeschinkel commented Apr 23, 2019

"I would even go further to request that the intellisense of "Visual Studio Code" be improved to understand this."

Just FYI, GoLand already supports auto-complete on dot imports.

And I personally would be very angry having to write "declarative.Button" instead of "Button".

I agree with this in general with Go. There are a lot of cases when I really wish I could forgo having to prefix with a package name. I know this is a tangent but it would be great if we could define an alias like so:

type Button alias declarative.Button

Another thing that would be great is if Go would assume the same package when other members of the package are passed in to a func call with a simple dot prefix, e.g. instead of this:

forms.ShowDialog("Do you want to close?" forms.Yes, forms.No, forms.Cancel)

It would be great if Go assume the follow was equal to the previous:

forms.ShowDialog("Do you want to close?", .Yes, .No, .Cancel)

I'll be happy to break these two requests out into new tickets assuming I get positive reactions from those on the Go team.

Loading

@ianthehat
Copy link

@ianthehat ianthehat commented Apr 24, 2019

I agree with this in general with Go. There are a lot of cases when I really wish I could forgo having to prefix with a package name. I know this is a tangent but it would be great if we could define an alias like so:

type Button alias declarative.Button

type Button = declarative.Button

works, but will give you an exported symbol, you can always do

type button = declarative.Button

if you don't want that

See https://golang.org/ref/spec#Alias_declarations

Loading

@pradeepg26
Copy link

@pradeepg26 pradeepg26 commented May 1, 2020

The tension in the discussion so far appears to be between the purported ease of use of DSLs enabled by dot imports vs the lack of clarity of the source of symbols that said dot imports cause.

The main issue that I see with dot imports is that it imports all symbols. This is analogous to star imports in Java, which are (mostly) disallowed by most style guides, precisely because of the lack of clarity. If we were to extend the analogy, Java side steps the issue of clarity with individualized imports (both static and non-static).

As @balasanjay suggested, I think we can strike a balance here to satisfy both camps. This of course would work best if Go could provide a way to not reexport the symbols. I came up with two ideas to solve this issue but have not put any thought into the larger consequences for either.

The first is to mimic the Java import strategy and allow for imports of individual symbols from a package. This might look something like import "path/to/package:symbol_to_import". As a side note, I considered not using the / character to delimit the package from the symbol as wiser and thus chose the : character to mimic Bazel. This would allow you to use symbol_to_import directly without qualifying it with the package name. Depending on which camp you're in, you might see the inability to "rename" symbol_to_import to something shorter as an advantage or disadvantage.

The other idea is to use C++ style "using" statements to declare a local non-exported short name. This might look something like using ShortName = package.SomeReallyReallyPerhapsUnnecessarilyLongName. This idea is really close to the type alias concept which might cause confusion.

I personally would hate to lose the ability to develop or use DSL style APIs in Go. I think that if we were to remove dot imports (which seems like a good idea), providing an alternative solution for the provided use cases would be great.

Loading

@ianlancetaylor
Copy link
Contributor Author

@ianlancetaylor ianlancetaylor commented May 1, 2020

You can already refer to specific symbols using type aliases or const or var declarations. Those work for everything other than package-scope exported variables, which are not the common case.

For example:

import "fmt"
var Printf = fmt.Printf
func F() { Printf("%s\n", "hi") }
type Stringer = fmt.Stringer

That is, I don't see much benefit to adding support for importing explicit symbols, since we can already inject explicit symbols in the current package scope.

Loading

@pradeepg26
Copy link

@pradeepg26 pradeepg26 commented May 1, 2020

That is, I don't see much benefit to adding support for importing explicit symbols, since we can already inject explicit symbols in the current package scope.

Totally agree. The only issue with your example code is that the symbols Printf and Stringer are re-exported. This can obviously be avoided by changing the names to printf and stringer, but explicit symbol imports would have the advantage of not being re-exported while keeping the same name. To be clear, I'm not necessarily proposing that these changes should be made. I'm only proposing ideas for discussion assuming that the Go team wants to make a change to address some of the concerns brought up in this thread.

Loading

@brpaz
Copy link

@brpaz brpaz commented May 2, 2020

Well, I have a situation where I was thinking of using dot imports and found this thread.

My use case is the following. I have a logger package that encapsulates the zap logger where I do some initialization logic.

// Logger The application logger
var Logger *zap.Logger

func SetupLogger() {
	if Config.Env == "dev" {
		Logger, _ = zap.NewDevelopment()
	} else {
		Logger, _ = zap.NewProduction()
	}
}

If I want to use my Logger, I would need to call logger.Logger which is ugly. Using a dot import solve this problem.

Not sure if there is a cleaner way.

I don't like global variables at all and my inner brain says I should use DI, but I am also trying to be pragmatic and for things like logging and configuration which needs to be accessed everywhere in the application, I am debating if it´s worth injecting all the way down to the data layer or use a global.

Typescript for example, allows to import just specific symbols and not the entire package.

Just my 2 cents.

Loading

@dc0d
Copy link

@dc0d dc0d commented Jul 10, 2020

To have some shared code between tests, that shared code could be placed inside a package, so those other test suites can import it.

Consider having some BDD-style specs, implemented using ginkgo (or another tool) and are put inside a Go package. Let's call this package apispecs. This package has these two dot-imports:

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

Now that shared spec, package apispecs, can be imported by different tests, in different packages.

Having a warning, without a way to suppress it (something similar to what is provided for _ imports - by providing a comment), is both a cognitive load and a setback for the tooling (it's constantly visible on your IDE, for example, and makes the warnings something not important - in time).

The only solution that came to my mind was aliasing (or wrapping) everything in dot-imported packages (maybe not exactly everything, and they should be obviously public, but details are ignored here for the sake of conversation, and usually, the public surface that has to be covered, is not exactly small. Also, you can no longer enjoy the autodiscovery/intellisense capabilities provided by the IDE).

Loading

@codeblooded
Copy link

@codeblooded codeblooded commented Mar 2, 2021

I have an example that uses dot imports that is not just a DSL. The Kubernetes project's go client, named client-go, is abstracted for any cloud provider. The specific implementation for the cloud provider you choose, eg. GCP/AWS/Azure, relies on a dot import as a plug-in. See https://github.com/grpc/test-infra/blob/65b0fc4550b2508776a3b064301c4b4062215833/kubehelpers/auth.go#L23 for an example.

I am strongly against this proposal. I don't think dot imports are a good practice at all, but I see this breaking a lot of things unnecessarily. I don't see dot imports used much in practice. The kubernetes project, encoders and testing DSLs like ginkgo/gomega are the only ones I know of. I think the community recognizes the problems with them.

Loading

@go101
Copy link

@go101 go101 commented Mar 2, 2021

@codeblooded
In you linked code, it is an anonymous import, not the dot import talked about in this thread.

Loading

@StephanVerbeeck
Copy link

@StephanVerbeeck StephanVerbeeck commented Mar 2, 2021

I am not happy that this issue still receives new debate.
If this option is removed that it destroys one of the greatest things in GO and it will break ALL MY EXISTING commercialized code!!!
As software developer this raving lunatics idealistic must-must-must goes not well by me.
There is a good reason why it is part of GO and that some do not wish to use it is their decision but I fail to see the need to enforce their misconceptions on the entire world.
This topic is not open for discussion any longer so close it and please finally decide AGAINST this idiotic request.
It is obvious that the original poster is not aware of the magnitude of financial damage that he demands for.
Don't make us regret using GOlang for commercial main products.
Please end this topic!

Loading

@go101
Copy link

@go101 go101 commented Mar 2, 2021

This can be viewed as feature removal. If your go.mod file specifies an old version in the go directive, then the old code will still compiles by using the newest Go toolchain.

And go fix will help you transit without much effort.

Loading

@mateuszmmeteo
Copy link

@mateuszmmeteo mateuszmmeteo commented Mar 2, 2021

Loading

@beoran
Copy link

@beoran beoran commented Mar 2, 2021

If this issue is accepted, I will name all my dsl packages ᣟ, to be able to get a short, almost invisible import name again. With my excuses to the Canadian Aboriginals.

It goes like this:
ᣟ.go:

packageimport "fmt"

func Yes() {
	fmt.Printf("yes\n")
}

ᣟ_test.go:

package ᣟ_test

import "applied-maths.com/bnxt/platform/ᣟ"
import "testing"

func TestYes(t *testing.T) {
	.Yes()
}

Loading

@ianlancetaylor
Copy link
Contributor Author

@ianlancetaylor ianlancetaylor commented Mar 2, 2021

@StephanVerbeeck Please be polite and respectful and follow the Go Community Code of Conduct. Thanks.

Loading

@beoran
Copy link

@beoran beoran commented Mar 2, 2021

@ianlancetaylor I think we should advance this issue through the proposal process more rapidly. It's been open for over 2 years now without making much progress and this for a feature of Go that is already widely used.

However, as we can see from the emoticon poll and the various discussions, there is no consensus over this issue neither in favor or against it. Perhaps it's time to apply the rule from https://github.com/golang/proposal "If general consensus cannot be reached, the proposal review group decides the next step by reviewing and discussing the issue and reaching a consensus among themselves."? Or should this proposal be placed on "hold"?

Loading

@ianlancetaylor
Copy link
Contributor Author

@ianlancetaylor ianlancetaylor commented Mar 2, 2021

@beoran This is an incompatible change, so it follows a different process, one that admittedly tends to move more slowly. This is indicated by the Go2 label.

Loading

@beoran
Copy link

@beoran beoran commented Mar 2, 2021

@ianlancetaylor I see, I didn't know that. how is the Go2 process different? I looked around and I could find #33892 as the umbrella issue that is tracking this, but not a formal description of the go2 process. Is that different process already formalized and written down anywhere, and if so could you refer us to it, so we can have an idea of how this issue will progress?

Loading

@ianlancetaylor
Copy link
Contributor Author

@ianlancetaylor ianlancetaylor commented Mar 2, 2021

It's a much more casual process, since the bar for incompatible changes is much higher. We haven't tried to formalize it.

Loading

@StephanVerbeeck
Copy link

@StephanVerbeeck StephanVerbeeck commented Mar 3, 2021

I guess the problem most programmers have with dot-import is not the principle.
Rather I guess they have issues with the fact that code intellisense does never implement it (not working in visual studio code) so Go-users consider the dot-import to be broken while it is rather the code intellisense that is broken.
It is indeed a nuisance that goto-definition is not working for these where this is clearly a decision of the implementor to ignore this because he/she himself/herself is not using it.
So maybe the solution to this topic is to finish the last 10% work rather than undoing the working 90%.

Loading

@drekle
Copy link

@drekle drekle commented Mar 3, 2021

Rather I guess they have issues with the fact that code intellisense does never implement it (not working in visual studio code) so Go-users consider the dot-import to be broken while it is rather the code intellisense that is broken.

I use intellisense and goto-definition and they are very convenient, however as a matter of principle I don't think advanced features of any IDE should be relied on to solve code readability issues caused by dot imports.

Loading

@josharian

This comment has been hidden.

@josharian

This comment has been hidden.

@rvolosatovs
Copy link

@rvolosatovs rvolosatovs commented Apr 15, 2021

I find dot imports extremely useful in internal packages.

Given a package A:

  • A/B and A/C contain implementations of interfaces defined in A.
  • A/internal contains utilities used by A, A/B and A/C
  • A/internal/test contains test utilities used by A_test, A/B_test and A/C_test
  • A/internal/test/shared contains test utilities used by A/B_test and A/C_test

The dependency graph looks like this (LHS is the (sub-)package, RHS are the dependencies)

  • A -> { A/internal }
  • A/B -> { A, A/internal }
  • A/B_test -> { A/B, A/internal/test, A/internal/test/shared }
  • A/C -> { A, A/internal }
  • A/C_test -> { A/C, A/internal/test, A/internal/test/shared }
  • A/internal -> { }
  • A/internal/test -> { A/internal }
  • A/internal/test/shared -> { A }

For example, see the following package with such structure (A being pkg/networkserver, and A/B being pkg/networkserver/redis)
https://github.com/TheThingsNetwork/lorawan-stack/tree/bb0f4c7cc2c8c71efe4140ac9078cfb2ee1ddba2/pkg/networkserver

While importing A/internal as internal makes sense in implementation code, importing A/internal/test and A/internal/test/shared named makes less sense and probably requires aliases to avoid clashes with e.g. external pkg/test. Dot imports make this way cleaner and clearer

Loading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet