Go is decidedly polarizing. While many are touting their transition to Go, it has become equally fashionable to criticize and mock the language. As Bjarne Stroustrup so eloquently put it, “There are only two kinds of programming languages: those people always bitch about and those nobody uses.” This adage couldn’t be more true. I apologize in advance for what appears to be just another in a long line of diatribes. I’m not really sorry, though.
I normally don’t advocate promoting or condemning a particular programming language or pontificate on why it is or isn’t used within an organization. They’re just tools for a job.
Can we please get over this “we use hot language X” thing? I don’t care what nails you used to build your house. Just show me the house.
— Tyler Treat (@tyler_treat) May 12, 2015
Today I’m going to be a hypocrite. The truth is we should care about what language and technologies we use to build and standardize on, but those decisions should be local to an organization. We shouldn’t choose a technology because it worked for someone else. Chances are they had a very different problem, different set of requirements, different engineering culture. There are so many factors that go into “success”—technology is probably the least impactful. Someone else’s success doesn’t translate to your success. It’s not the technology that makes or breaks us, it’s how the technology is appropriated, among many other conflating elements.
Now that I’ve prefaced why you shouldn’t choose a technology because it’s trendy, I’m going to talk about why we use Go where I work—yes, that’s meant to be ironic. However, I’m also going to describe why the language is essentially flawed. As I’ve alluded to, there are countless blog posts and articles which describe the shortcomings of Go. On the one hand, I’m apprehensive this doesn’t contribute anything meaningful to the dialogue. On the other hand, I feel the dialogue is important and, when framed in the right context, constructive.
Simplicity Through Indignity
Go is refreshingly simple. It’s what drew me to the language in the first place, and I suspect others feel the same way. There’s a popular quote from Rob Pike which I think is worth reiterating:
The key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt.
Granted, it’s taken out of context, but on the surface this kind of does sound like Go is a disservice to intelligent programmers. However, there is value in pursuing a simple, yet powerful, lingua franca of backend systems. Any engineer, regardless of experience, can dive into virtually any codebase and quickly understand how something works. Unfortunately, the notion of programmers not understanding a “brilliant language” is a philosophy carried throughout Go, and it hinders productivity more than it helps.
We use Go because it’s boring. Previously, we worked almost exclusively with Python, and after a certain point, it becomes a nightmare. You can bend Python to your will. You can hack it, you can monkey patch it, and you can write remarkably expressive, terse code. It’s also remarkably difficult to maintain and slow. I think this is characteristic of statically and dynamically typed languages in general. Dynamic typing allows you to quickly build and iterate but lacks the static-analysis tooling needed for larger codebases and performance characteristics required for more real-time systems. In my mind, the curve tends to look something like this:
Of course, this isn’t particular to Go or Python. As highlighted above, there are a lot of questions you must ask when considering such a transition. Like I mentioned, languages are tools for a job. One might argue, then, why would a company settle on a single language? Use the right tool for the job! This is true in principle, but the reality is there are other factors to consider, the largest of which is momentum. When you commit to a language, you produce reusable libraries, APIs, tooling, and knowledge. If you “use the right tool for the job,” you end up pulling yourself in different directions and throwing away those things. If you’re Google scale, this is less of an issue. Most organizations aren’t Google scale. It’s a delicate balance when choosing a technology.
Go makes it easy to write code that is understandable. There’s no “magic” like many enterprise Java frameworks and none of the cute tricks you’ll find in most Python or Ruby codebases. The code is verbose but readable, unsophisticated but intelligible, tedious but predictable. But the pendulum swings too far. So far, in fact, that it sacrifices one of software development’s most sacred doctrines, Don’t Repeat Yourself, and it does so unapologetically.
The Untype System
To put it mildly, Go’s type system is impaired. It does not lend itself to writing quality, maintainable code at a large scale, which seems to be in stark contrast to the language’s ambitions. The type system is noble in theory, but in practice it falls apart rather quickly. Without generics, programmers are forced to either copy and paste code for each type, rely on code generation which is often clumsy and laborious, or subvert the type system altogether through reflection. Passing around interface{} harks back to the Java-pre-generics days of doing the same with Object. The code gets downright dopey if you want to write a reusable library.
The argument there, I suppose, is to rely on interfaces to specify the behavior needed in a function. In passing, this sounds reasonable, but again, it quickly falls apart for even the most trivial situations. Further, you can’t add methods to types from a different (or standard library) package. Instead, you must effectively alias or wrap the type with a new type, resulting in more boilerplate and code that generally takes longer to grok. You start to realize that Go isn’t actually all that great at what it sets out to accomplish in terms of fostering maintainable, large-scale codebases—boilerplate and code duplication abound. It’s 2015, why in the world are we still writing code like this:
Now repeat for uint32, uint64, int32, etc. In any other modern programming language, this would get you laughed out of a code review. In Go, no one seems to bat an eye, and the alternatives aren’t much better.
Interfaces in Go are interesting because they are implicitly implemented. There are advantages, such as implementing mocks and generally dealing with code you don’t own. They also can cause some subtle problems like accidental implementation. Just because a type matches the signature of an interface doesn’t mean it was intended to implement its contract. Not to mention the confusion caused by storing nil in an interface:
This is a common source of confusion. The basic answer is to never store something in an interface if you don’t expect the methods to be called on it. The language may allow it, but that violates the semantics of the interface. To expound, a nil value should usually not be stored in an interface unless it is of a type that has explicitly handled that case in its pointer-valued methods and has no value-receiver methods.
Go is designed to be simple, but that behavior isn’t simple to me. I know it’s tripped up many others. Another lurking danger to newcomers is the behavior around variable declarations and shadowing. It can cause some nasty bugs if you’re not careful.
Rules Are Meant to Be Broken, Just Not by You
Python relies on a notion of “we’re all consenting adults here.” This is great and all, but it starts to break down when you have to scale your organization. Go takes a very different approach which aligns itself with large development teams. Great! But it’s taken to the extreme, and the language seems to break many of its own rules, which can be both confusing and frustrating.
Go sort of supports generic functions as evidenced by its built-ins. You just can’t implement your own. Go sort of supports generic types as evidenced by slices, maps, and channels. You just can’t implement your own. Go sort of supports function overloading as evidenced again by its built-ins. You just can’t implement your own. Go sort of supports exceptions as evidenced by panic and recover. You just can’t implement your own. Go sort of supports iterators as evidenced by ranging on slices, maps, and channels. You just can’t implement your own.
There are other peculiar idiosyncrasies. Error handling is generally done by returning error values. This is fine, and I can certainly see the motivation coming from the abomination of C++ exceptions, but there are cases where Go doesn’t follow its own rule. For example, map lookups return two values: the value itself (or zero-value/nil if it doesn’t exist) and a boolean indicating if the key was in the map. Interestingly, we can choose to ignore the boolean value altogether—a syntax reserved for certain blessed types in the standard library. Type assertions and channel receives have equally curious behavior.
Another idiosyncrasy is adding an item to a channel which is closed. Instead of returning an error, or a boolean, or whatever, it panics. Perhaps because it’s considered a programmer error? I’m not sure. Either way, these behaviors seem inconsistent to me. I often find myself asking what the “idiomatic” approach would be when designing an API. Go could really use proper algebraic data types.
One of Go’s philosophies is “Share memory by communicating; don’t communicate by sharing memory.” This is another rule the standard library seems to break often. There are roughly 60 channels created in the standard library, excluding tests. If you look through the code, you’ll see that mutexes tend to be preferred and often perform better—more on this in a moment.
By the same token, Go actively discourages the use of the sync/atomic and unsafe packages. In fact, there have been indications sync/atomic would be removed if it weren’t for backward-compatibility requirements:
We want sync to be clearly documented and used when appropriate. We generally don’t want sync/atomic to be used at all…Experience has shown us again and again that very very few people are capable of writing correct code that uses atomic operations…If we had thought of internal packages when we added the sync/atomic package, perhaps we would have used that. Now we can’t remove the package because of the Go 1 guarantee.
Frankly, I’m not sure how you write performant data structures and algorithms without those packages. Performance is relative of course, but you need these primitives if you want to write anything which is lock-free. The irony is once you start writing highly concurrent things, which Go is generally considered good at, mutexes and channels tend to fall short performance-wise.
In actuality, to write high-performance Go, you end up throwing away many of the language’s niceties. Defers add overhead, interface indirection is expensive (granted, this is not unique to Go), and channels are, generally speaking, on the slowish side.
For being one of Go’s hallmarks, channels are a bit disappointing. As I already mentioned, the behavior of panicking on puts to a closed channel is problematic. What about cases where we have producers blocked on a put to a channel and another goroutine calls close on it? They panic. Other annoyances include not being able to peek into the channel or get more than one item from it, common operations on most blocking queues. I can live with that, but what’s harder to stomach are the performance implications, which I hinted at earlier. For this, I turn to my colleague and our resident performance nut, Dustin Hiatt:
Rarely do the Golang devs discuss channel performance, although rumblings were heard last time I was at Gophercon about not using defers or channels. You see, when Rob Pike makes the claim that you can use channels instead of locks, he’s not being entirely honest. Behind the scenes, channels are using locks to serialize access and provide threadsafety. So by using channels to synchronize access to memory, you are, in fact, using locks; locks wrapped in a threadsafe queue. So how do Go’s fancy locks compare to just using mutex’s from their standard library “sync” package? The following numbers were obtained by using Go’s builtin benchmarking functionality to serially call Put on a single set of their respective types.
BenchmarkSimpleSet-8 3000000 391 ns/op
BenchmarkSimpleChannelSet-8 1000000 1699 ns/opThis is with a buffered channel, what happens if we use unbuffered?
BenchmarkSimpleChannelSet-8 1000000 2252 ns/op
Yikes, with light or no multithreading, putting using the mutex is quite a bit faster (go version go1.4 linux/amd64). How well does it do in a multithreaded environment. The following numbers were obtained by inserting the same number of items, but doing so in 4 separate Goroutines to test how well channels do under contention.
BenchmarkSimpleSet-8 2000000 645 ns/op
BenchmarkChannelSimpleSet-8 2000000 913 ns/op
BenchmarkChannelSimpleSet-8 2000000 901 ns/opBetter, but the mutex is still almost 30% faster. Clearly, some of the channel magic is costing us here, and that’s without the extra mental overhead to prevent memory leaks. Golang felt the same way, I think, and that’s why in their standard libraries that get benchmarked, like “net/http,” you’ll almost never find channels, always mutexes.
Clearly, channels are not particularly great for workload throughput, and you’re typically better off using a lock-free ring buffer or even a synchronized queue. Channels as a unit of composition tend to fall short as well. Instead, they are better suited as a coordination pattern, a mechanism for signaling and timing-related code. Ultimately, you must use channels judiciously if you are sensitive to performance.
There are a lot of things in Go that sound great in theory and look neat in demos, but then you start writing real systems and go, “oh wait, that doesn’t actually work.” Once again, channels are a good example of this. The range keyword, which allows you to iterate over a data structure, is reserved to slices, maps, and channels. At first glance, it appears channels provide an elegant way to build your own iterators:
But upon closer inspection, we realize this approach is subtly broken. While it works, if we stop iterating, the loop adding items to the channel will block—the goroutine is leaked. Instead, we must push the onus onto the user to signal the iteration is finished. It’s far less elegant and prone to leaks if not used correctly—so much for channels and goroutines.
Goroutines are nice. They make it incredibly easy to spin off concurrent workers. They also make it incredibly easy to leak things. This shouldn’t be a problem for the intelligent programmer, but for Rob Pike’s beloved Googlers, they can be a double-edged sword.
Dependency Management in Practice
For being a language geared towards Google-sized projects, Go’s approach to managing dependencies is effectively nonexistent. For small projects with little-to-no dependencies, go get works great. But Go is a server language, and we typically have many dependencies which must be pinned to different versions. Go’s package structure and go get do not support this. Reproducible builds and dependency management continue to be a source of frustration for folks trying to build real software with it.
In fairness, dependency management is not an issue with the language per se, but to me, tooling is equally important as the language itself. Go doesn’t actually take an official stance on versioning:
“Go get” does not have any explicit concept of package versions. Versioning is a source of significant complexity, especially in large code bases, and we are unaware of any approach that works well at scale in a large enough variety of situations to be appropriate to force on all Go users. What “go get” and the larger Go toolchain do provide is isolation of packages with different import paths.
Fortunately, the tooling in this area is actively improving. I’m confident this problem can be solved in better ways, but the current state of the art will leave newcomers feeling uneasy.
A Community or a Carousel
Go has an increasingly vibrant community, but it’s profoundly stubborn. My biggest gripe is not with the language itself, but with the community’s seemingly us-versus-them mentality. You’re either with us or against us. It’s almost comical because it seems every criticism of the language, mine included, is prefixed with “I really like Go, but…” to ostensibly diffuse the situation. Parts of the community can seem religious, almost cult-like. The sheer mention of generics is now met with a hearty dismissal. It’s not the Go way.
The attitude of the decision making around the language is unfortunate, and I think Go could really take a page from Rust’s book with respect to its governance model. I agree entirely with the sentiment of “it is a poor craftsman who blames their tools,” but it is an even poorer craftsman who doesn’t choose the best tools at their disposal. I’m not partial to any of my tools. They’re a means to an end, but we should aim to improve them and make them more effective. Community should not breed complacency. With Go, I fear both are thriving.
Despite your hand wringing over the effrontery of Go’s designers to not include your prerequisite features, interest in Go is sky rocketing. Rather than finding new ways to hate a language for reasons that will not change, why not invest that time and join the growing number of programmers who are using the language to write real software today.
This is dangerous reasoning, and it hinders progress. Yes, programmers are using Go to write real software today. They were also writing real software with Java circa 2004. I write Go every day for a living. I work with smart people who do the same. Most of my open-source projects on GitHub are written in Go. I have invested countless hours into the language, so I feel qualified to point out its shortcomings. They are not irreparable, but let’s not just brush them off as people toying with Go and “finding ways to hate it”—it’s insulting and unproductive.
The Good Parts
Alas, Go is not beyond reproach. But at the same time, the language gets a lot of things right. The advantages of a single, self-contained binary are real, and compilation is fast. Coming from C or C++, the compilation speed is a big deal. Cross-compile allows you to target other platforms, and it’s getting even better with Go 1.5.
The garbage collector, while currently a pain point for performance-critical systems, is the focus of a lot of ongoing effort. Go 1.5 will bring about an improved garbage collector, and more enhancements—including generational techniques—are planned for the future. Compared to current cutting-edge garbage collectors like HotSpot, Go’s is still quite young—lots of room for improvement here.
Over the last couple of months, I dipped my toes back in Java. Along with C#, Java used to be my modus operandi. Going back gave me a newfound appreciation for Go’s composability. In Go, the language and libraries are designed to be composable, à la Unix. In Java, everyone brings their own walled garden of classes.
Java is really a ghastly language in retrospect. Even the simplest of tasks, like reading a file, require a wildly absurd amount of hoop-jumping. This is where Go’s simplicity nails it. Building a web application in Java generally requires an application server, which often puts you in J2EE-land. It’s not a place I recommend you visit. In contrast, building a web server in Go takes a couple lines of code using the standard library—no overhead whatsoever. I just wish Java shared some of its generics Kool-Aid. C# does generics even better, implementing them all the way down to the byte-code level without type erasure.
Beyond go get, Go’s toolchain is actually pretty good. Testing and benchmarking are built in, and the data-race detector is super handy for debugging race conditions in your myriad of goroutines. The gofmt command is brilliant—every language needs something like this—as are vet and godoc. Lastly, Go provides a solid set of profiling tools for analyzing memory, CPU utilization, and other runtime behavior. Sadly, CPU profiling doesn’t work on OSX due to a kernel bug.
Although channels and goroutines are not without their problems, Go is easily the best “concurrent” programming language I’ve used. Admittedly, I haven’t used Erlang, so I suspect that statement made some Erlangers groan. Combined with the select statement, channels allow you to solve some problems which would otherwise be solved in a much more crude manner.
Go fits into your stack as a language for backend services. With the work being done by Docker, CoreOS, HashiCorp, Google, and others, it clearly is becoming the language of Infrastructure as a Service, cloud orchestration, and DevOps as well. Go is not a replacement for C/C++ but a replacement for Java, Python, and the like—that much is clear.
Moving Forward
Ultimately, we use Go because it’s boring. We don’t use it because Google uses it. We don’t use it because it’s trendy. We use it because it’s no-frills and, hey, it usually gets the job done assuming you’ve found the right nail. But Go is still in its infancy and has a lot of room for growth and improvement.
I’m cautiously optimistic about Go’s future. I don’t consider myself a hater, I consider myself a hopeful. As it continues to gain a critical mass, I’m hopeful that the language will continue to improve but fearful of its relentless dogma. Go needs to let go of this attitude of “you don’t need that” or “it’s too complicated” or “programmers won’t know how to use it.” It’s toxic. It’s not all that different from your users requesting features after you release a product and telling those users they aren’t smart enough to use them. It’s not on your users, it’s on you to make the UX good.
A language can have considerable depth while still retaining its simplicity. I wish this were the ideal Go embraced, not one of negativity, of pessimism, of “no.” The question is not how can we protect developers from themselves, it’s how can we make them more productive? How can we enable them to solve problems? But just because people are solving problems with Go today does not mean we can’t do better. There is always room for improvement. There is never room for complacency.
My thanks to Dustin Hiatt for reviewing this and his efforts in benchmarking and profiling various parts of the Go runtime. It’s largely Dustin’s work that has helped pave the way for building performance-critical systems in Go.
Follow @tyler_treat
Hi, I’m promoting Nix here. If you want reproducible builds and decent dependency management, you may look at our Go packaging in the Nix package manager.
Here I’ve written a simple post about it: http://lethalman.blogspot.it/2015/02/developing-in-golang-with-nix-package.html
You know exactly which versions of libraries you are going to use, for every project, and also for multiple go versions. Reproducible and shared builds of libraries, together with native library dependencies.
Go is a minimal language, and brings all block for a great environment – go tools.
go ge
is not a part of a language – it’s just a reference tool to install a package. In the same time everyone says that you should vendor your project dependencies.There are multiple ways for that, but I found that go environment variables works best here. And there are tools which help you with that. Personally I’m using gomand it works really good. Highly recommending.
Ever tried Nim? It’s statically typed, has a Python-like syntax, supports smart concurrency, and is super fast. It’s also planning on reaching 1.0 later this year. The only issues I’ve encountered are that closures don’t always compile (you can usually use templates) and that distinct tuples are broken (which, to quote the creator, is an oxymoron).
I’ve heard good things about it—might have to check it out at some point.
Um, reading files in Java is ghastly?
Try:
byte[] encoded = Files.readAllBytes(myFile);
or
List lines = Files.readAllLines(myFile)
Granted, this is Java 8. It used to be a lot worse in earlier versions. Programming in Java 8 is a lot more fun, given that you can do stuff like:
List lines = Files.readAllLines(myFile)
int count = lines.stream().map( l -> l.toLowerCase() ).filter( l -> l.contains(“secret”)).count();
Yes, and Java is also gradually improving as a language (much faster than it used to, nowadays), whilst is conciously stagnating semantically. It will be a hilarious irony if in a couple years Java grows all the missing features that will make it a semi-trendy language again, and Go ossifies and becomes a subject of complaints about its verbosity and meager semantics.
>Um, reading files in Java is ghastly?
Then later,
>Granted, this is Java 8.
You mean the version of Java that was released a full 2 years after Go 1 was released? I don’t see what you were trying to prove with your comment. What are you trying to say by showing on the 8th version of the language, nearly 8 years into its life, the stdlib finally has an easy method for reading files?
Java 8 also closed the 14 year old bug about how Java had no native way to deal with POSIX file permissions… god, what a waste of time that language was.
Yes, but even with Java < 8 you could use Apache Commons where you could do:
List lines = FileUtils.readLines(myFile, "UTF-8");
The fact that this functionality was not included in the standard library for full 7 versions reflects horribly on Java's design, but Go didn't came out of the blue and revolutionized everything with its simplicity. Most modern languages, both statically-typed (e.g. C#, D) and dynamic (e.g. Python, Ruby) had simple ways to do simple things such as iterating lines in a text file and some of them could also do this lazily (great if you're piping long files) and still don't sacrifice range-traversal, because all of this language have ranged traversal even for non built-in types.
” What are you trying to say ”
He was responding to the claim that “reading files in Java is ghastly” … duh.
“You mean the version of Java that was released a full 2 years after Go 1 was released?”
Go isn’t relevant to his comment, except to Fanbois.
Writing
int count = lines.stream().map( l -> l.toLowerCase() ).filter( l -> l.contains(“secret”)).count();
As an example of readable, simple, fun code is hilarious. Get over it, Java is garbage.
Two unrelated comments:
> In Go, the language and libraries are designed to be composable, à la Unix. In Java, everyone brings their own walled garden of classes.
Can you explain? I find Go and Java are almost identical, and the choice between them falls to the choice of the runtime capabilities — not the language. I use Go for small command-line apps — because it requires no warmup and is statically linked — while I choose Java for important server applications — because of monitoring, performance, dynamic linking and hot code swapping and polyglotism.
> on the surface this kind of does sound like Go is a disservice to intelligent programmers.
This sentiment (which I hear often recently from devotees of languages for “smart programmers”) really bugs me. I think it is actually a feature of, granted, intelligent yet very novice programmers to believe that the program is its code, and that they should focus on the expression of an algorithm and clever abstractions. Even more intelligent (well, actually just more experienced) programmers understand that the program is much more than the code, and it is the organization — and, more importantly, the algorithm itself — that are much more important (and require intelligence) than their expression and use of clever abstractions.
I actually find this focus on algorithm expression rather than algorithm and data-structure design a sign of a general dumbing down of the profession.
> the program is much more than the code, and it is the organization that are much more important (and require intelligence) than their expression and use of clever abstractions.
you are contradicting yourself here, writing abstractions is a fundamental way to organize your code. in fact, it’s essential to manage increasing complexity.
> So, the language that we give them has to be easy for them to
> understand and easy to adopt.
I think this is at the core of why I like the philosophy of Go – most other languages have gone for making it easier to write code, with increased language complexity (and obfuscation) to accommodate that, but Go has a concern on making code that’s being easy to *read*, and most programmers in the real world will do that much more often.
I know its the author’s opinion but it seems a little late or maybe misguided to be cautiously optimistic. People are just dipping their toe in the Go waters anymore. Shops are completely betting the farm on it and it seems like that’s mostly because of the language being what it is right now. That’s not to say there can’t be improvements. But it seems that some refuse to recognize that being C with the most dangerous pitfalls eliminated is one of the biggest selling points.
It’s always refreshing when someone acknowledges their hypocrisy up front… but, for most programmers I know, I’d say the battle was lost with Rob Pike’s comment:
“The key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt.”
“Capable” was IMO the wrong adjective, esp. without reference to one’s definition of a “brilliant” language – or the much bigger role that the surrounding infrastructure plays. With actual respect for their many fine employees, it’s not at all clear to me how much “brilliant” software is actually conceived and executed at Google (i.e., vs. at acquisitions that don’t regard their engineers as incapable). Does Go work for that environment? Marvelous.
As for the rest of us, I am reminded of the immortal words of Malcolm Reynolds:
“Now I did a job; and got nothing but trouble since I did it, not to mention more than a few unkind words as regard to my character. So let me make this abundantly clear. I do the job, and then I get paid. Go run your little world.”
I’ll stand judged on the actual quality & maintainability of my work and choice of environment, thanks…
Thank you for sharing your thoughts so openly!
I agree that generics are very useful when writing reusable libraries, but I disagree they are absolutely necessary when writing and maintaining software at large scale. At large scale, at some point, processes communicate with other processes on the same machine or over the network, and you have to marshal/unmarshal and type check at runtime.
Your example with the function isInt64InSlice is a good illustration of why the lack of generics is annoying at small scale, but it hardly shows why this is a problem at large scale.
PostgreSQL is an example of a complex piece of software, written in C, which doesn’t use generics and is still very readable and maintainable.
A few other technical points:
– “Cannot add methods to types from a different package”: Would you want something like extension methods in C#?
– “Channels are slow”: I’ve read there is some work going on to improve them. That said, you admit yourself that Go is still one the best option among the “concurrent” languages. Are you aware of another language with a similar mechanism builtin in the language or the library and that performs better on this matter?
– “Accidental implementation of an interface”: I agree this a problem in theory, but in practice it’s very rarely an issue. Have you been beaten by that often? Do you think the benefits of implicit implementation are not worth it?
– “Cannot implement your own exceptions”: Yes you can. You can pass some value to panic and retrieve it upper in the call stack with recover and decide what to do depending on the value. It’s not something Go developers do often, but there is some use of this technique in the standard library for example. It’s explained in “Effective Go”.
– “Map returns a bool instead of an error”: I don’t see the issue here. A lot of other languages do the same and return a default value and/or a boolean at false when you probe a map with a missing value.
– “Adding an item to a closed channel panics”: This is a programming error, like a division by zero or an out of bounds array access.
– “Channels as iterators”: Goroutines and channels are not designed to build iterators. They offer a general low-level mechanism for concurrency. How would you expect the goroutine to know that nobody is listening anymore and shutdown?
About the community, I would not call it “stubborn”. In my experience, it is generally helpful and very professional. But I agree that the topic of generics has become a taboo, which I regret. It looks like the core team wants to focus on improving the compiler, the runtime, the library, the tooling and the garbage collector, and because of that wants to keep the language stable in the meantime. I understand the frustration, but don’t you think their decision makes sense and is the best way to use the limited resources of the core team?
You wrote that “a language can have considerable depth while still retaining its simplicity”. What languages would you recommend that solve the flaws of Go while preserving its simplicity (in terms of user experience)? It’s a sincere question. I’d like to use such a language.
More generally, you ask how can we make developers more productive and how can we enable them to solve problems? That’s interesting because this is precisely the question Go tries to answer. Are you sure the answer lies in a more powerful programming language? What about the runtime, the tooling, the libraries?
The Go team thinks the answer lies in simplicity (light syntax, garbage collection, interfaces, composition over inheritance, builtin arrays/slices/maps), builtin concurrency (goroutines, channels), great tooling (speed of compilation, go test, gofmt, godoc), easy deployment (no virtual machine, static binary).
Do you think the answer lies in having type parametricity, algebraic data types, pattern matching, immutability, Hindley-Milner type inference, higher kinder types, etc.? I’m not saying they are not useful or desirable. I’m just saying that the low hanging fruits in terms of developer productivity may be elsewhere.
Cheers!
Thanks Nicolas! “One man sounds right till the other speaks” – Prov 18:17. I currently work in Ruby. The obsession with “dryness” and beautiful code makes me want to puke — and I love Ruby! However, we can hardly get a product out without the incessant debacle over style and conciseness. Grokking a new piece of code can take days unraveling the complex hierarchy of “helpful” classes and modules and succinct expressions. Thank God for a tool like RubyMine to help me find these hidden magical things!
Now I don’t do much concurrency so I can’t speak to that, but as a language Go keeps me engaged – even when I am mentally stressed – I can still write Go code. Sometimes when grokking source, I delve off into the standard library without realizing it! I’m not frustrated by fancy constructs, I can just code — get things done! Yes, I don’t like some choices (I want my ternary operator back!), but I take a pill and live without it. In the end I make good products with Go. I have 3 very useful apps in production in 1.5 years: A custom CMS – calvaryeastmetro.com (based on Fragmenta), a technical notes app: github.com/rohanthewiz/go_notes (my personal Stack Overflow!), and a temperature monitor for a kitty house –https://github.com/rohanthewiz/kitty_mon — that’s the end-goal isn’t it?
Generics are an anti-pattern, go uses interfaces for Generics, why does the underlying method need to know if it’s having a list of oranges or apples ? It’ll use the same strategy all the way.
And the calling code can still see that it’s actually using a fruits list
Generics is anti-pattern? Please explain and demonstrate. The burden of proof falls on you for claiming that. “Go doesn’t have feature X” does not imply “Feature X is an anti-pattern”. The same applies for “Rob Pike thinks that X is complex and dangerous”.
Why would you need to know that L is a list of apples or oranges? You don’t necessarily have to. A dynamically-typed language goes on perfectly fine without knowing the type of any object (not just list members) until run-time. It’s definitely simpler than having to reason about templates (and then covariance and contravariance), but you lose all the niceties of static typing (performance, better compile-time error detection and tooling) in this tradeoff. If Go claims to be a statically-typed language geared at writing performance-critical code it should act like one. Interfaces are slow and error-prone. Try to implement your own custom container and see if you can make a performant generic container that relying just on interfaces.
Besides, even if you’re ok with dynamically-typed interfaces, Go makes it hard to write simple things like a generic sum function.
If you feel like you don’t need to know that L is a list of oranges or apples, that’s fine: but why would you need type-casts and reflection then? If Generics is anti-pattern, then reflection and type-casting are the mother of all anti-patterns.
“Generics are an anti-pattern”
No intelligent, knowledgeable, or intellectually honest person would make such an absurd claim.
Generics are an anti-pattern? Wow, I’d love to hear you explain that one.
While back I looked at the parallel sort in Workiva’s go-datastructures. Peep (and/or point Dustin at!)
sorts
(here’s words and pretty numbers about it). Like I said in the words I think there are some ways to go further, would be into looking at anything new y’all code up.On the substance, uh, agree about some, disagree about some, it wouldn’t be that interesting to hash out.
One other thought with benefit of coffee: if you want to move the ball on some of this, at least for your company but maybe for folks at large, creating tools (like saner ways to generate code where you want to) seems like an under-recognized leverage point, more accessible to the working Gopher than most probably think. Beyang Liu of Sourcegraph gave an interesting related talk.
That raises a mess of questions, like “uh, what should these tools do?” But I’m only here to gesture at that can of worms.
Don’t listen to the haters! You’re so correct I almost wonder if I wrote this myself. Every time I’ve asked for help or suggested something, I’ve gotten similar treatment, i.e., “you’re doing it wrong”. Go has amazing potential, but it’s just lost with all the “we’re ALWAYS right” rhetoric. Oh, I especially love the “X is an anti-pattern” folks – really, everything Go doesn’t do just happens to be wrong? So convenient….
If the community and core devs don’t admit to flaws and mistakes, Go will go the same way as everything they want to replace – look at what DHH has done to Rails with his “we’re always right and we’re always smarter than you” attitude.
It’s so refreshing to use something simple enough to fit in my head, especially coming from large Rails apps, but everybody makes mistakes. Yes, Rob Pike, you, too.
On negative aspect more:
Go has no string type. Sounds strange, but let me explain: A string is a collection of characters. Go uses UTF-8, but go’s string type is just a collection of bytes. If you use slice or index on a string you access the bytes, not the characters. Also the length is on the bytes. And you see so much code out there, that is flawed by that: People using the slicing syntax, which then works by accident most of the time (ANSI characters), but not in general on UTF-8. An example i came by is the Go-support by Ragel.
From my experience the Go language is just like any other Google product: half-baked and half-implemented at best. No brilliant inventions there, just mistakes repeated from other programming languages and their own failed experiments. Inconsistent – you already said that. A pointless invention, a pollution of programming language space, meaningless step to nowhere. I ought to be surprised that this company spat out such a crap, but somehow I’m not.
Really putting it all out there, eh?! haha. I tried go, and lack of generics made it too annoying for me. C++11 code actually looks pretty nice now. And I have to say that I’m intrigued by Swift coming to Linux. I have been toying with it, and it seems a much better design than Go, but solving a lot of the same problems.
Also, I mean, go doesn’t generate optimized enough binaries. They should have used LLVM.
Have you ever tried D? It’s a very well designed language and a pleasure to work with. It does not treat programmers as kids, although it fixes a lot of C++ headaches. I think it just needs more press time (and a prettier site) =)
Nice article about Go. Thanks for that. I like it very much. Finally a systems language without manual memory management.
But few comments about paragraph referring about java.
>Building a web application in Java generally requires an application server, which often puts you in J2EE-land.
There is this package in com.sun.net.httpserver.HttpServer (be warned this is only works on Oracle’s JDK) and others that can be used to write equally simple web server in java. Agree with J2EE part though, but its people’s choice.
>The advantages of a single, self-contained binary are real, and compilation is fast.
Lazy linking (a.k.a lazy loading) in java was the choice made early on. But Android folks tried this concept of single assembly in the form of Dex (Dalvik executable) Assembly quite successfully reducing overall size in the process. Java should also provide this kind of option by giving a compiler flag and corresponding handling in run time.
“Share memory by communicating; don’t communicate by sharing memory.”
Agree. By now It is quite firmly established that shared memory concurrency is quite troublesome not only in performance point of view but also creating and maintaining application programs with that. Wish we have this model in java at language level it self.
Actually we should try writing java compiler and jvm in Golang with those features.
At the end these two languages are pretty good, Go being extremely good for System Programming and Java equally good for Application programming, can be complementary.
Don’t forget that, with Spring Boot, you can start writing controllers and it will download the requisite libraries and such around your code and start up an http server without the need for tomcat.
A certain amount of caution about “feature requests” can be a good thing in moderation, to avoid the “PL/1 effect” – a language that is everything to everyone but too big to grok and use effectively. In case you hadn’t already guessed, I’m still sore with the C99 committee for trying to turn C into Fortran-77…
God, I’m so glad I chose Clojure instead of Go as my primary language
It appears that Go, as C did, will impede progress in programming languages for another 50 years. Pity.
Interesting article. I will have to read it a few times to understand it completely.
As far as I can see, one of GO’s main features is a set of tools for concurrency (multi tasking).
I think your iterator routine is an example of how not to use the range keyword
If using ‘ range ‘ forces you to launch a goroutine , Then perhaps you should use a for-loop instead, I don’t know GO,
But the launching of new threads shouldn’t be done willy nilly inside of sub routines.
Great article.
As for the productivity chart, I would argue that for a static(ally typed) language, it’s more like a flat line. You probably won’t feel really productive in the beginning, but you would stay at roughly the same level, or at least lose a lot less momentum as your codebase grows. Somehow I don’t think you get magically more productive when there’s a huge codebase under you to maintain.
Go channels performance: If you happen to have some time to experiment, I guess you’ll still see that for the same task (100000 tasklets) Go is always beaten by Stackless Python, at the core of its own game. Not indicating the performance of real programs, but still funny enough.:)
I wouldn’t try Go unless I really had to, and so far I really disliked the look and feel of Go code I stumbled across. On top of that, I have the impression that the situation would be similar to Java’s, which I think is a very unfair language. Java forces you to be an OO extremist, using classes for everything like a golden hammer, meanwhile the system taking its own shortcuts, not eating its own dog food. (Maybe less so in >=8, but I’d bet it’s not really hard to find cases like that.)
Great post! Have nice day ! :) lttwe
Java is the most popular programming language at the moment. If there is any language which is going to replace it, that language should have the same set of libraries, documentation resources, and community around it.
Golang was created to put guard rails around stupid programmers. The creators took a caustic view of their fellow programmers and codified it into one of the worst languages ever built.
“The key point here is that our programmers are not researchers. They are, as a rule, very young, they come to us after their studies, perhaps they studied Java, C / C ++ or Python. They can’t understand an outstanding language, but at the same time we want them to create good software. That is why their language should be easy for them to understand and learn.”
The one thing far worse than a junior programmer is an arrogant one. And golang was created by 3 pricks.