views:

257

answers:

3

Google's new language Go tries to make dependencies management easier by explicitly requiring that all dependencies listed in a module actually be used. The compiler will reject a module that declares a dependency to a module without using anything from that module.

It is illegal for a package to import itself or to import a package without referring to any of its exported identifiers.

I can think of some obvious advantages (e.g. cleaner modules) but maybe there are some non-obvious ones. The only disadvantage I can think of is having an overly pedantic compiler, complaining too much during refactoring, but maybe there are more?

Do you have any experience with other languages enforcing this? What are the pros and cons of this approach?

+5  A: 

Not only you need to explicitly use all dependencies, but also all variables must be used. The compiler will give you errors when you have unused variables.

They are annoying. But it will make others happy because they get clean code.

I'm thinking that probably Go designers intended Go to be a language that is largely IDE dependent.

yuku
+1 for the IDE-based design. I've reached the same conclusion, largely from the fact that only the object files have anything resembling a header, and also that Go includes modules for parsing Go.
Jurily
A: 

As mentioned by yuku, if you have got an IDE that is on par with what Netbeans and Eclipse can do for java, you don't really have to care about this sort of thing.

Right click on the little light bulb in the margin and select "Remove all unused dependencies".

For variables that aren't used, they usually get a squiggly underline, and are pretty easy to spot.

The only difference here is that unlike other languages, you actually have the compiler complaining in addition to the IDE but if you use an IDE anyway, this becomes a non-issue.

At work we have coding policies that pretty much state that we have to do the same thing (ourselves), among other things, for other languages of course. So I would say that this sort of this does actually have real applications. Though IMHO, the compiler should give the developer the option to toggle this behaviour on and off. Strict mode anyone?

bguiz
+1 for strict mode - I like the feature, but I also think it's a good idea to be able to turn it off.
Russell Newquist
+1  A: 

I'm guessing that the biggest motivation for this, and the biggest result, is an improvement in compile times. The tech preview video made a major point of their ability to compile large amounts of code in short time periods (their example was 200,000 lines of code in 8 seconds on a MacBook - no machine specs given).

Also in the tech video, they specifically mention that one of the biggest ways they achieved that was in changing the way modules were compiled and linked.

Here's an example of how something would work in a current C/C++ system:

Class A is defined in header file C_H and implemented in C_CPP. Class B derives from C and is implemented in files B_H and B_CPP. Class A derives from B and is implemented in files A_H and A_CPP.

Because of the derivation chains, A_CPP includes A_H, B_H, and C_H. B_CPP includes B_H and C_H. C_CPP includes C_H. Due to the nature of the C preprocessor, which essentially turns a #include into a cut and paste operation, the contents of C_H are run through the compiler 3 times and B_H is run through twice.

Further, the contents of A_CPP, B_CPP, and C_CPP all live in their own object files. Hence when the linker goes to resolve A.o, it is forced to load and process both B.o and C.o. Also, when it resolves B.o, it has to process C.o again.

Precompiled headers can help quite a bit with the first part of this problem, but they also can be a royal pain to maintain and I know a lot of developers who simply don't use them for that reason. It also doesn't fundamentally change the problem - the headers are still processed at several levels multiple times, only now a precompiled binary is processed instead of the source. Several steps are cut out, but not the entire process.

Go approaches things differently. In the words straight out of the PDF from their tech talk:

"The Go compiler pulls transitive dependency type info from the object file - but only what it needs. If A.go depends on B.go depends on C.go: - compile C.go, B.go, then A.go. - to compile A.go, compiler reads B.o not C.o. At scale, this can be a huge speedup."

OK, slight tangent is done. Why is that relevant? The answer is also in the Go Tech Talk PDF:

"Package model: Explicit dependencies to enable faster builds."

I'm guessing that the Go developers took the stance that when compile times are measured in seconds, even for very large projects, that it's more productive for developers to keep compile times that short. Say it takes me 8 seconds to compile 200,000 lines of code and discover that I've got an extraneous package import, 5-10 seconds (with a good IDE, or good familiarity with your dev environment) to find it and fix it, and another 8 seconds to recompile. Call it 30 seconds total - and now all of my future compiles stay in the 10 second range. Or we can let our module grow by including unnecessary dependencies, and watch that compile time creep up from 8 to 10, 12, or 15 seconds. It doesn't seem like much because we're all used to compile times on the order of minutes - but when you start to realize that that's a 25% performance degradation, you stop and think about it for a minute.

Go compile times are already lightning fast. Consider also that processor speeds are still increasing (if not as much as in the past) and that the number of cores available is also increasing (and compiling large amounts of code is well suited to multi-threading). 200,000 lines of code in 8 seconds today means that it's not unreasonable to imagine 200,000 lines of code compiling essentially instantaneously in 10 years. I think the Go team has made a conscious decision here to make compile times a thing of the past, and while the issue you bring up is only a very small part of that, it is still a part of that.

On another note entirely, the Go team also seems to have developed a philosophy of a language design that forces some good programming practices. To their credit, they've made the effort to achieve that without serious performance penalties, and largely succeeded. [Aside: The only two things I can think of offhand that actually impact performance are garbage collection and forcibly initialized variables - and the latter is pretty trivial in this day and age.] This is going to royally irritate some programmers, while making others happy. It's an old, old argument in the programming world, and it's pretty clear which side Go has come down on, like it or not.

I think the two forces together influenced their decision, and I think that at the end of the day it's a good way to go, although I support other commenters here who have suggested allowing a "--strict" flag or some such to make this particular behavior optional, particularly during the early stages of a project's lifecycle. I could easily see myself defining variables or including packages when I first start writing code that I know I will need later, even though I haven't yet written the code that needs them.

Russell Newquist
"If module A requires module B which requires module C, most compilers would compile C, compile B and C (again), and then compile A, B (again), and C (again) and then sort out linking issues" - no they wouldn't. They compile each source file once, then link them together.
Nick Johnson
Nick, I misstated slightly, and will update my answer momentarily with the correction. However, if you use header files in C/C++, you end up with **large** portions of code that are compiled more than once.
Russell Newquist
Yes, headers get included more than once, because they're handled by the preprocessor. If you have 'large' bits of the code in your header files, UR DOIN IT WRONG.
Nick Johnson
But like it or not, lot's of people do it that way. And on large projects, it can be hard to control.
Russell Newquist
Also, even if only a very small percentage of your code is in header files, it adds up on big projects.
Russell Newquist
Also also, for particular problems the nature of C/C++ inlining and templates sometimes make it very problematic, if not impossible, to keep code out of header files.
Russell Newquist