Go: a nice language with an annoying personality
corte.si
corte.si
Just because some really smart people made a decision for reasons that are great 99% of the time, but fail badly in a very important 1% case doesn't mean you have to redefine your entire belief system to avoid criticizing them.
I'm sure there's reasons it would be a bad idea, but having the ability to ignore errors like this when building a Debug build seems like it could be a solution.
There is great reasoning behind the concept of providing as few exceptions as possible to the users. Sometimes it means adapting your workflow/thinking, but I guess this is the case for every language if you want to become truly productive.
> It's pretty hilarious to watch people clambering all over each other in this thread to claim this isn't a problem because [op_is_doing_it_wrong] or [weird_hacky_workaround].
"Clambering" may be a bit hyperbolic, given there are only 15 comments (at the time I read this), and a handful of those propose "weird hacky workarounds" or "op is doing it wrong" comments.Aside from that, I agree with the author that the variable not used errors are certainly irritating during a development or debug cycle. On the other hand, it is nice during a normal compile, or when using go get to pull a lib in from some other author.
There are indeed numerous work arounds, using build constraints (go build tags args) and such. Many (most?) are quite hacky and not very general case or new-developer-friendly.
It would indeed be nice to have a 'debug' compile flag that ignored such errors, if only for a little while. Maybe even make it only usable with "go build" and never "go get" or "go install".
Certainly true. Hyperbole is basically my favorite thing in the universe.
I was thinking about a command line tool to enable this automatically by generating code based on the source files.
Why oh why do people think this way, totally conflating things?! It makes any "explorative programming" extremely painful. Really, can't I indulge in a sloppy coding style while prototyping something, have a zillion warnings pop up (or silence them with a compiler flag), have a working prototype with ugly hacks and worst practices and then move on to slowly fix the things till I get a 0 warning 0 errors "clean state".
Not all have the god-programmer mind to start with a "good design". We grow things organically, function by function, class by class. We start from a "ball of mud" and slowly mold it into the shape of our desire (now I agree that for the different style of programming that starts from a "good design" and works hard to prevent everything from turning into a ball of mud, having straight jackets" is good, but if I don't want to live in your "cathedral asylum", then don't force me to wear one!). Every line of code is and will be rewritten time and time again. And you need warnings and errors to be very different concepts to do this style of programming!
I sympathise with this view, but personally, as a full-time Go programmer, when I've been hacking up little piece of throw-away or explorative code, I've generally found that not-used errors have as often been helpful as annoying.
For example, the compiler might say "i not used", and I'll think "bloody compiler, let's delete the declaration", but I'll actually find that I've named a loop variable wrongly and that this would have been a hard-to-find error requiring a few edit-run iterations, and thus I've actually saved more time by doing what the compiler asked than if I'd been able to code sloppily.
If I do find myself adding and removing many print statements from within a package, I'll sometimes add a little function:
func logf(f string, a ...interface{}) {
log.Printf(f, a...)
}
then as long as that function exists, I can add a logf call to any file in the package with no need to add and remove imports.We can still start with mud, but it's good mud.
BTW we have warnings too, but they're generated by a different tool, go vet, which printf format checking and the like.
To which your response will inevitably be something like "Don't do that", which is a fine sentiment, but it doesn't work. I also feel "Don't do that", but I need to do something that will work, and making the compile break more often, before you have several tens of thousands of instances of this problem, is actually a great idea.
Any other apparent attempt to find middle ground is just a matter of moving around when the compile breaks. Either you break the build somewhere, or the warnings aren't fixed.
http://9fans.net/archive/2008/08/134
"Just say no"
Go may share some of Acme's developers, but it is a very different project to Plan 9.
One of them takes the "nothing really matters at all" attitude (see: the majority of PHP developers). These people do not subscribe to dogma in the least but lack structure to such an extent that everything is a mess.
The next category is of the opinion that everything must be done in a certain way (the Plan 9 creators, most Java developers). This camp emphasises dogma above all else, even at the expense of common sense.
The final group, which is in the minority, consists of those who are truly pragmatic while taking the ideal into account at the same time. They tend towards perfection but know that the universe is not perfect. These are the Zen masters of development.
Most of what's built on a "nothing really matters" platform is total mess.
Most of what's built on top of a "certain way" platform works and is a good foundation to build more platforms.
Most of what's built on top of the "truly pragmatic" platform works, but is less often a foundation to build more platforms.
We need the fanatics like RMS to jumpstart GNU, and the Plan9 people to jumpstart plan9 - a lot of what you take for granted in Linux these days like namespaces, /proc, utf-8 was copied from Plan9, and for a reason: It's easier to copy features from a clean, consistent, proven implementation like Plan9 then from an inconsistent hodgepodge like the NT kernel. (And yes, I did NT kernel programming until just about the time XP came out; I say that with authority).
Additionally, it may be easier to copy features from a "clean, consistent, proven implementation," but it is not always easy to copy features into said programs. Consider that few things are as "proven" as the Linux kernel. Or the TeX codebase, for that matter. Neither are usually considered "clean" or "consistent." While the later is seen as an interesting example of a completed code base that is no longer changing, few things have stood additions as well as the kernel.
GNU/Linux would never have gained mass adoption if certain compromises were not made. If proprietary code were completely outlawed in Linux and the GNU userland then we would not see it everywhere today.
Instead of looking at what one individual does, let's zoom out to the level of individuals working together. For an important idea to come to fruition, both sides of the coin we are talking about must invariably be addressed.
The interesting thing about us is that we can mesh our brains into bigger collective systems which in themselves act as brains - it's a fractal shape. Some individuals are capable of achieving zen on their own. Some individuals can serve as parts of a greater brain (a mass of individuals communicating with each other). That greater brain must address both sides of the coin if it is to yield something novel.
We need fanatics like RMS to jumpstart GNU but we also need pragmatists to make something truly useful of it. One does not work without the other, in both directions, in my opinion.
Thinking about it, GNU/Linux is a good example of this relationship. Stallman is an idealist and laid down very important groundwork with the GPL and GNU applications. Linus is a pragmatist and built something imperfect but extremely useful upon that foundation. If Linux did not exist then GNU would be useless (see: the HURD). If GNU & the GPL did not exist, Linux would have been bought up by a corporation and would have barely seen the light of day. The combination of idealism and pragmatism profoundly changed the world.
import (
_ "fmt"
)
To prevent the unused var error you can assign the var to an _ as well: _ = unusedVarHow is that an improvement? That's just as much work as commenting the line out.
Well, the thing is that in the long run, the cost of any software system is dominated by fixing bugs with high degree of latency. Removing various classes of error by compiler fiat is an approach that is wildly unpopular but well studied.
This sentence might have been written as:
> I'm all for opinionated language design, so long as I agree with the opinion.
And that's fine. I can only speak for myself when I say that while younger me was annoyed that the damn compiler won't get out of the way, older me is annoyed that the damn compiler didn't catch this obvious defect for me.
The difference is that older me is writing less trivial programs and dealing with more existing code and writing more complicated systems.
Disclaimer: I've never written a line of Go, so this is just my perception as an outsider. Go does seem to get a lot of flack from people with theoretical interest in programming languages (think LtU commenters). My speculation is that this is partly pushback against the (perceived) 'Go was created by the smartest guys in the industry' memes, along with the fact that it ignores most of the last 30 years of PL research.
I mean it's not as though using type system technology invented in the 1980s would have been such a stretch for a language created twenty-something years later.
The thing is, though, that Go has gotten lots of para-language stuff right. It ships with gofmt and inbuilt tools for manipulating code, it has decent concurrency out of the box, it compiles and runs fairly quickly. It's really meant to be a better C from a universe where C++ was never invented (I've seen it compared to Limbo, a language developed on Plan9 -- given the heritage, that would make sense).
If one studies the history of programming languages, one comes to realize that this has as much or more to do with the success of a language than the language itself.
Since the import is always used, it is never an error. No problem.
Go's pedantic compiler and style enforcement fade pretty quickly into the background with practice and a reasonable text editor. The pain of trying to fix mixed metaphors between modules in Python never will.
As an aside, here is the solution to your fmt.Println problem:
func traceln(v ... interface{}) {
fmt.Println(v...)
}
Just bind the function, and you can comment or uncomment traceln to your heart's desire.I don't understand this statement. People successfully write large Python programs and don't get exhausted in the process. Could you elaborate on how "mixed metaphors" affects things in Python, and how Go's syntax checks eliminate that problem in Go?
I think they would be better off adding a compiler flag for less strict checking for development.
var _ = fmt.Println
Now the fmt package is always in use. No more import issues.The reasons and choices made for this have been fully explained.
if _, err := ioutil.ReadFile(path); err != nil {
return nil, err
}
Then I realized, programmers as detailed people are easily annoyed, because small details often matter. But in both the case of the author, and myself here, we need to realize when it truly doesn't matter.Sure these Go behaviours might annoy him, but they are important for large code bases and are easily worked around. Sure, repeating am overly verbose block several times annoys me, but the seven extra lines of code does not make him unreadable or incorrect-
Here is too not sweating the small stuff.
3 letters of irony here.
What OP describes is exactly what drove me away from using Go for my 'high level' projects.
Now Go is a great language with great ideas but hacking something together in it is such a pain that I don't get a warm feeling when thinking about coding in Go. All those small things add up to where it just gets tiresome. Not something I want experience during programming.
There should be some kind of "I know what I'm doing"-mode that lets you import unused packages, etc.
This has exactly the opposite effect on me. I feel the strong grip of the compiler greatly comforting me during development. Form frees.
Yes, it's one I encounter, but only once the package is finished and I am in the testing stage and need to debug. Then, the fmt import stays there until I'm done fixing bugs, and I just use gocode's `:Drop fmt` to drop the import. Syntastic, which is a brilliant code validator for vim uses the go compiler to point out every place I have placed an fmt.Println with >>>, allowing me to remove all of them in one fell swoop, where in other languages I would have to search through the code for various print statements. The vanilla Go compiler also points this out, as the author notes.
I prefer the way the compiler works because it makes sure your code is always representative of what needs to, and happens.
To the author I would say "Why comment out the fmt.Println if you are still debugging?" and "If you are debugging, why only one fmt.Println?".
There are also multiple editors that support gdb plugins if you don't want to deal with it in its raw form which honestly isn't that bad once you're used to it.
I think I could say with a straight face that you will be able to debug a program faster with gdb than sprinkling about print statements even if you didn't have to worry about unused variables.
In other words, get used to what the language has to offer and then leverage it before saying it's annoying. The problem you have is 100% solved by gdb.
if 0 { fmt.Print(m) }The editor can just as easily highlight this is "unreachable/useless code" (maybe even using the comment colors).
However, the compiler will check the syntax and semantics of that statement to make sure it is ok.
I say it's better then commenting out code (although a
#define non_executable_for_reference_only if 0
would have probably made that more readable)Yes, it is a hack, but it serves a purpose.
It's just a matter of convention; It creates as much cognitive burden as documentation does. And indeed, I avoid documentation where it isn't needed, but I do put it where it is helpful - just like I do with code that doesn't need to be built/executed as part of the product, but that helps understanding.
Exactly. This was a convention used by Smalltalk shops. It came about because of the appearance of the Refactoring Browser, which caused a 10X increase in the rate and ease of refactoring. Before, Smalltalkers used to put snippets of code in comments with the exhortation to "run this" or "debug this" to clarify how a certain part of the API worked. The problem arose that refactorings would often break those snippets. However,
false ifTrue:
["Debug this"
myObj := MyClass new.
myObj myWonderfulMethod.].
...was very fast (it was a jump on many VMs) and would never break even if refactorings hit it.The takeaway: One's time is better spent writing useful code.
I watch people using this approach all day every day and have to sort out the shit-tip mess that is left behind because it ends up with sprawling crap.
Stop. Think. Write it once. Test it. Fix it. Done.
The "think" bit is the most important bit but is lost now that instant feedback is possible in favour of keep changing it until it looks like it works.
"Why don't we have a word for this? By "unit of hacking", I mean the work that goes on between starting to hack on a change-set and doing a commit."
Hackit.
As a unit, Ht, similar to Hz for Hertz.
(I was going to add it would be named for Jim H. in http://en.wikipedia.org/wiki/Yes_Minister , but his name was Hacker.)
Of course, where there's "_, err = ...", "if err != nil" returns are rarely far behind. :)
Here at work we have VS2010, TFS, Resharper and yet some developers don't care about unused imports and references.
Some developers use "dark themes" and it seems they don't see all the gray code that can be cleaned up.
I'd love this to be hard errors like in Go, but it's too late now.
It's incredibly useful; I don't want to go back to a language that doesn't ship with a parser in the standard library.
I'd say the Go authors have thought about support for tooling much more than other language authors, not less.
Why should it be the province of IDEs? What about command line tools? How about "any thing that works well?"
Yes, the error handling is explicit. If this is a bother panic/defer+recover is there if you want to use it.
The case here is completely contrived; a red-herring. It's not like it's ever typical to do m, err := io.Read... and not use the `m` (if you were, you'd already be writing _, err anyway). Assuming that you write a block of code and then compile it and not write all of your code line-by-line and compile it... and then I guess try to artificially fix the compiler message instead of writing the code to utilize m first?
edit: My only point is that this isn't even an issue that arises when normally writing code; the scenario in the blog isn't one that occurs when one sits down, writes a function and then compiles it. Sorry to whoever I upset.
I've been doing some Go recently, and I ran into this exact problem (except the rhs was something else). Ignoring the output of a not-directly-related function while debugging is quite common, in my experience. Like some others here, it didn't rise for me to the level of ranting about it, but it's interesting to see that it's not just me, so I'm glad it did for someone. :)