back
225 comments
I did Go for years, but stopped doing any serious work in it about a year ago.

In general, I found it a chore to maintain Go-based systems.

There's a lot to like about Go. But it doesn't seem pragmatic for the types of applications I see people using it for.

For example, the entire error thing is absurd. Anders Hejlsberg got this right many years ago: 9 out of 10 errors are "handled" by a central error handler (log + "sorry, try again"). You can do this with error values, but at best it's verbose. Maybe error values makes sense for system programming, I could certainly believe it makes more sense.

But, really, the main problem is the type system. I'm a huge fan of dynamic languages. I think they're so much more productive. This is especially true when your system is interacting with external data - like user input and a database. I did C# and Java for years. These interactions are, at best, painful with static languages. But they're downright infuriating in Go (poor reflection, no generics). I wrote a somewhat popular library to help deal with map[string]interface{} nonsense (2), but it's not enough.

When it comes to web/tcp stuff, I think OpenResty for any middleware-ish system and Elixir for everything else are far more productive and enjoyable ecosystems. We've been doing Elixir for about a year now, and I've never seen such a consensus on productivity and quality (everything else always had some issue: Ruby is slow, Node is single threaded, Java is verbose).

There's certainly more space than just that. Go might still be a good choice for CLI tools, and both OpenResty and Elixir have a distinct memory/threading model that precludes some use cases - but those are in the minority.

(1) - Nothing against Erlang model, but I do think it's more restrictive.

(2) - https://github.com/karlseguin/typed

> This is especially true when your system is interacting with external data - like user input and a database. I did C# and Java for years. These interactions are, at best, painful with static languages.

Why? I find when you're importing external data or user input, that's exactly where you want strong types as that's the most likely place unexpected values are going to be generated (e.g. unexpected null values, string instead of an array).

I don't like the "central exception handler" idea -- it is invariably too far away from the error site to do anything intelligent.

I do like Erlang's approach with hierarchical error domains ("processes" and "supervisor trees"), and I honestly think Go made a mistake here: The semi-recent introduction of Context acknowledges that a control hierarchy is present and an important part of a concurrent, distributed application and that there are aspects of this that need to flow through everything. The Context exists to permit a caller to call functions and launch goroutines that inherit deadlines, the ability to cancel their work, and so on. But this is a rather blunt and intrusive tool that could have been better solved by baking termination into the goroutine scheduling runtime. Of course, one reason goroutines cannot be forcibly terminated is because everything is mutable all over the place, but you could still have termination induce system-level errors that the goroutine would be forced to handle (or maybe you could safely induce a panic)... but that ship seems to have sailed a long time ago.

And yet a hierarchical approach similar to supervisor trees would have been awesome to have in Go. Managing hordes of complex, "crash only" goroutines in Go gets gnarly fast, and you tend to end up with your own ad-hoc type of supervisor trees that uses waitgroups and maps and what not to corral trees of composed workers to ensure they are all able to cancel, retry + back off, and time out appropriately. Even something like retry with backoff isn't part of the standard library, even though it's extremely core to distributed, concurrent programming.

I think I agree with Go's authors that exceptions are bad, but exceptions in an explicitly hierarchical, supervisor-type process model, where processes are "crash only", actually make sense. It means you tend to design your system into discrete parts consisting of dumb workers (that give up and crash on the smallest error) and slightly less dumb supervisors (that react to said crashes). It's not a panacea, but it's better than the loose, somewhat impoverished set of tools that Go gives you.

>For example, the entire error thing is absurd. Anders Hejlsberg got this right many years ago: 9 out of 10 errors are "handled" by a central error handler (log + "sorry, try again").

You are correct that this is how it's often done - 9 out of 10 errors probably ARE "handled" by a central error handler. But this is an incorrect approach if the goal is a reduction of catastrophic errors.

In the following paper, "Simple Testing Can Prevent Most Critical Failures" (OSDI '14). the authors discuss how some catastrophic errors occur in distributed systems that they test (Cassandra, HBase, Redis, HDFS, MapReduce).

http://www.eecg.toronto.edu/~yuan/papers/failure_analysis_os...

Notably section 4.1: Trivial Mistakes in Error Handlers:

"""

Figure 5 further breaks down the mistakes into three categories: (i) the error handler ignores explicit errors; (ii) the error handler over-catches an exception and aborts the system; and (iii) the error handler contains “TODO” or “FIXME” in the comment.

25% of the catastrophic failures were caused by ignoring explicit errors (an error handler that only logs the error is also considered as ignoring the error). For systems written in Java, the exceptions were all explicitly thrown, whereas in Redis they were system call error returns.

"""

I haven't used Go much but I personally much prefer Rust's error handling which appears to be largely similar.

I've done my first project with go. Goroutines are undoubtedly cool and I like the strong typing.

* Unfortunately error handling sucks (no stacktrace by default? Let's go back to the days of peppering print statements around just for fun).

* DB support is terrible (I've spent hours doing what I could have done in half an hour with python).

* It's so verbose, even simple things like mapping over an array to create another array requires a for-loop dance. Same for just checking if a value is in an array. Same, obviously for no exceptions which you either love or hate. I hate.

I can't see myself doing any other projects with it.

Wat. OpenResty as an alternative to go? Wat?

Please, nobody write full fledged WebApps or all of your middleware in lua. Trust me that's a horrible idea. I have seen about everything you can do in lua/nginx and it does not turn out pretty.

> I'm a huge fan of dynamic languages. I think they're so much more productive.

This hasn't been my experience. I work in Python (and JS) by day, and I can't wait until we upgrade to 3.6 so I can reasonably employ type annotations. Without them, people tend to write hacky code, such as functions which do completely different things based on the data type passed in (rather than fixing the abstraction) or build up huge, nested data structures with no formal definition (various parts of the code base just tack on whatever they like, and the documentation inevitably falls out of date). Even my prototyping is in Go, because I would rather have a compiler catch my errors than to spend tenfold the time trying to debug. Also, when I'm working in Python, I sorely miss the tooling that I have in Go and other static languages.

I don't think Go is the end-all-be-all of programming languages, but I think any dynamic language would be more productive if it was at least optionally typed.

"I'm a huge fan of dynamic languages. I think they're so much more productive. This is especially true when your system is interacting with external data - like user input and a database." Im not so sure about it. Database is not dynamic and user input should be controlled, or number, or date, or text, for example an user could enters the price of a product a text.
>Node is single threaded

You can still utilize all of your cores in Nodejs. So which problem are you encountering?

I always wonder if the Go error issues could be handled more cleanly by using the suture library. I haven't seen widespread use but it's supposed to give a supervisor pattern to Go.
The Node event loop (the control plane) is single threaded, but the data plane is multi-threaded, you can easily write C++ bindings to run in the thread pool to handle CPU intensive work.
>I found it a chore to maintain Go-based systems

My opinion is the opposite. Almost every go code base I've seen is extremely consistent compared to other languages. Going from one code base to another is almost always seamless because learning Go involves learning the Go tools which forces you to follow Go coding standards. I'd choose to work on a Go code base over Java or C++ any day of the week.

I don't have much experience with elixir, but that's because I like to stay employed.

>the entire error thing is absurd. Anders Hejlsberg got this right many years ago: 9 out of 10 errors are "handled" by a central error handler

This is almost always unacceptable in production, especially when it comes to mission critical hard/software. You need to be able to handle errors in production that you may not even be able to catch in testing and that means you need to catch all the errors and think critically about what should be done when that error is encountered. "Central error checkers" are almost never robust enough for mission critical systems.

Go basically forces people to write high-quality, resilient code bases, and I like it for that.

> You know how much code I’d have to write if this were C++, C#, or Java? None. They all have reusable notions of an immutable, ordered map.

Actually C++ doesn't have immutable data unless you count compile-time constants and literals. You can declare things 'const', but that only provides a read-only (1) view on mutable data.

Also, to provide good 'const' support in containers, you usually have to provide extra read-only iterators that are largely identical to the writeable iterators. Go look at the C++ standard library. All the containers have both 'iterator' and 'const_iterator' (not just a 'const iterator').

Another approach is to just declare member variables 'const'. So you have a 'const map<int, int>' as a member variable. Except for that to work, you need to construct that member variable in the initializer list. The body of the constructor is too late. This likely means taking an already-initialized map as a constructor argument and copying it (probably exposing implementation details in the process) or writing a helper that returns the appropriate map type and copying it. Good optimizers will elide many or all of these copies, but relying on quality of optimizers isn't portable.

Anyway, I'd say the complexity of the Go approach isn't significantly worse that the C++ alternatives, if it's worse at all.

(1) Of course, with casting shenanigans or use of the 'mutable' keyword, anyone can circumvent 'const' without a lot of work.

> Actually C++ doesn't have immutable data unless you count compile-time constants and literals. You can declare things 'const', but that only provides a read-only (1) view on mutable data.

  const std::set<int>& s // This is a read-only view.
  const std::set<int>  s // This is immutable.
> Another approach is to just declare member variables 'const'. So you have a 'const map<int, int>' as a member variable. Except for that to work, you need to construct that member variable in the initializer list. The body of the constructor is too late. This likely means taking an already-initialized map as a constructor argument and copying it (probably exposing implementation details in the process) or writing a helper that returns the appropriate map type and copying it. Good optimizers will elide many or all of these copies, but relying on quality of optimizers isn't portable.

  Foo() : s(helper()) {} // This is a move, not a copy.
Let's take this const map:

  const std::map<int, int> constMap = { {1,1}, {2,2 } };
Or take a const map copy-constructed from from a non-const map, if you wish.

How is this not immutable data, and "only provides a read-only view"?

It is linguistically guaranteed to be non-mutating - const-casts excepted of course.

All languages that are constructing const data types on the fly are putting them in a writeable pages in memory, after all.

Yes, if you use the escape hatch from the type system, the type system won't help you. Do you also avoid the use of strong types since you can always cast to a *char?
And java doesn't have them either. You need to use an external library, likely the google containers library.
So, once upon a time I wanted to associate information with http connections (as I am accustomed to in every other language I've ever written) just to enable proper http keepalive and debugging through a proxy written in Go.

Ended up with this. [0]

Turns out the Go authors don't think you should do this so I had to majorly alter and recompile the stdlib.

I still appreciate many go tools and the cross platform single binaries that work great on Solaris/Illumos. But in many cases the strong opinions of Go authors really stand in the way of getting work done.

[0]: https://github.com/doublerebel/publictransport

These days you have http requests with context (I think starting from Go 1.8), see https://golang.org/pkg/net/http/#Request.Context
I keep getting drawn back to Go.

I left it a while back, with a lot of the same frustrations. But then I tried a bunch of other things, and nothing seemed to work better. There seem to always be some frustrations with whatever language. And I found myself craving that simplicity again.

I might get annoyed with it again, and try some other stuff. And that might stick. But I suspect Go is the worst language except for all the others [1].

[1] http://www.goodreads.com/quotes/267224-democracy-is-the-wors...

I think you have to work with the language you're in. I had similar frustrations when I first moved into doing some dev in Java and C# and it really bugged me that there was no compile-time guarantee that an Object reference passed as a method parameter would not be modified within that method (as I was so used to using const-refs with in C++)

In retrospect however, I was frustrated with these languages because I was wanting it to be something it wasn't. To my mind, the loss of that feature meant that programs were less expressive - particularly in large codebases - I had to read methods to work out if something was being modified or not.

To complain here about Go lacking immutability and constness features seems to be the same as complaining that it is missing in Python, Ruby or JavaScript. It's a fact that constness isn't a feature of these languages, if you want these features, use a different language.

The link to the immutable, ordered map that "Java provides" seems to be a link to a third-party library created by Google.

If that's enough for you to consider that a language provides a feature, just wrap the immutable ordered map implementation in your blog post in a library, publish it, and now Go provides that functionality too.

After a year of programming with Go I have noticed that I, an old C++ programmer, have the reflex to decompose a program in terms of interacting data structures (objects) which encapsulate their behavior. I programmed like that in Go in the beginning. But when I looked at other Go code and especially the std lib code which is easily accessible, I was always amazed at the simplicity of the code. Now I try to write simple programs and this is hard after years of C++ programming.

I don't know if there is a term for Go like programming. I'm tempted to call it minimalism. It may be uncomfortable for people coming from classical programming, but the simplicity of the language and the code is a clear win in the long run.

It's so interesting how the hype cycle works. Only 1 year ago, people on HN were bashing Node.js hard and saying that you should use Go instead and everyone was praising Go.

Now it seems everyone is bashing Go. Moral of the story; never buy into the hype whether it is positive or negative. I think that Go is just fine and so is Node.js.

Maybe the author of the article makes a valid point, but no language is perfect and ultimately you have to understand it well in order to use it well.

Of course the problem he has is that it doesn't follow the hot new immutability fad, and of course he doesn't explain what he needs it for, just links to a stack exchange question which essentially says that it might be useful for some cases.

It seems a lot of these articles, and programming language theory in general, is just complaining about features without any thought as to why they matter. What is this "problem" stopping him from doing? Why does it matter? And why should I abandon a language that has served me very well just because some edge-case feature is not supported?

Worth reading the comments where someone points out a much simpler and more elegant solution, although it's still not an ideal situation.

As noted in the post, generics could solve this, so I'm looking forward to Go 2 if indeed generics is included. There's a few QOL issues I'd like to see addressed in 2 which would make Go a lot nicer to work with, and I say that as someone who spends most of their time working on Go codebases.

Go's been great for creating a web scraper. But it's been an unmitigated disaster for being able to write the data to a database due to the immaturity of ORMs.

Might as well not pretend something is an ORM if it can't do the fundamentals (like upserts), fully hydrating structs, etc.

Having to encapsulate data to ensure read only semantics seems like par for the course from an OO perspective. In C# (which is 15ish years old at least) I don't think there has been an immutable map until very recently. Also (and this is probably a code style thing) I can't remember having reached for an immutable ordered map in a long career.
> You know how much code I’d have to write if this were C++, C#, or Java? None. They all have reusable notions of an immutable, ordered map.

Java doesn't do that well at all though; its immutable maps can be used in place of mutable maps, since there's no way of having a Map interface that extends ImmutableMap to add extra operations, so you can get nasty runtime surprises if you've got the wrong one. Conversely, if you want a mutable map keyed on a non-trivial structure, you have to do just as much boilerplate as the author complains about to set up private members, setters, builders, etc.

Obviously C++ can do it quite readily, but nobody accused it of lacking features.

This is really just another use case for generics, and the discussion there is well-trodden. Go probably isn't the language for you if you value generics more highly than fast compile times, native binaries, etc, but I'm not sure why that still seems to come as a surprise.

I am not sure where the authors exact requirements fit in the greater picture of the desired application. But one important tool which works great in Go is functional representation. If you want e.g. an immutable map, make it a structure with private fields, and give access vial functions (methods). The first comment to the article describes a nice example of this approach.

I often read criticism of Go which is based on a missing "feature". In my experience, implementing these features is surprisingly easy in Go, as the building blocks are all there. In this respect, Go has some resemblance on Lego :).

My background contains a lot of Scheme/Lisp, so abstracting things with functions comes natural to me. It turns out that Go has basically the equivalent function concept (first class functions, closures, ..) so enables all the powerful abstractions coming from them.

OT: I recently learned that the newer ETH client Parity is written in Rust and much faster than the older Go-based Geth ETH client.

Guess it is due to a different architecture and design which makes the client faster (e.g. chaindata is smaller) but does anyone know why they chose Rust instead of Go for the Parity client?

I personally tend to choose Go for newer projects as I'm looking for the efficiency of C and safety/elegance of more "modern" languages. However, I always keep an eye on D and Nim. It seems these are the only two languages that can compete with Go in both above aspects.
I asked this same question on reddit. And I am not trying to be snarky. I have programmed a long time and I cannot think of a single time where I thought, "this would be easier with generics."

Where is the list of problems that are easier to solve with generics?

The OP suggests "some form of codegen" as a way of getting around the Go dev team's, shall we say ... unusual hostility toward generics. But that way lies C++ templates, which were originally meant to be implemented as macro-generation -- and which lead to the messiness and complexity of C++ builds which were one of their major reasons for doing a new language in the first place.
It's an interesting point of view that regards those four items as necessary requirements for a data structure, and then regards the 24 lines of code needed to implement it as a massive "explosion of code." It would be interesting to know what language is considered the ideal tool for someone who looks at things that way. Something functional?
Go contains many good ideas that may be carried over to more pragmatic languages. Using it for anything beyond simple, self-contained command-line tools and network servers never really worked out for me. And that makes sense, since it was explicitly designed to scale over groups of inexperienced developers at any cost; the opposite of my needs. Modern C++ runs circles around Go for more complex software, and it provides the tools to add as much convenience as needed on a case-by-case basis. I recently ported a 7kloc Go codebase to C++ and wrote down some of my impressions here: https://github.com/andreas-gone-wild/blog/blob/master/rewrit...
Go was explicitly designed to be a simple language, without a rich tapestry of data structures to suit every use case.

Languages are like tools. If you need a saw, use a saw, instead of complaining about the knife you are using.

Out of curiosity, what is the use case for immutable structs? (asking as I'm a ruby/js/go dev and as such I've never used such idiom)
Where Google is SO GREAT that everyone could only follow them, never doubt any thing, or it would turn out to be that you are stupid
I don't know if you are the author of this post or not but try Crystal. It fixes all the things wrong with Go in my opinion.
> That’s an infuriating amount of code to write

24 lines, really ?

In addition to what you describe, my other big complaint with Go is the attitude you find from the devs, in threads discussing these types of challenges.

"Oh, you think you want an ordered map? No you don't. Your reasons are stupid. You're stupid. Now gtfo and start using Go to write things we deem to be respectable."

The sorted map alternative is really only viable for insert-only maps or really small maps. You'd have to remove keys from the list when they're removed from the map.

Most sorted map implementations are based on trees.

In any production grade application where order and persistence is truly important you need to supply a time stamp anyway and at that point you'd want to just store your animals in a database. This is pretty trivial imo.

If you just want a very temporary ordered list you could probably use a channel.

What is the perfect language?