Hacker News new | comments | show | ask | jobs | submit login
Go Is Not Good – A list of articles complaining that go isn't good enough (github.com)
76 points by emersonrsantos 39 days ago | hide | past | web | 30 comments | favorite

Go is good, it's just that its designers went half way through the design of the language and said "fuck it, we're done".

They came up with implicit interfaces, they didn't make them covariant, and they barely used them in the std lib itself.

They came up with multiple return values, they didn't think it would have been more useful for the programmer to get tuples and destructuring in practice.

They came up with struct tags, they didn't think fully typed annotations would have been more useful.

And then they exposed all the language internals in the reflect package,to make up for Go shortcomings with serializing,deserializing data, which made refactoring go internals in a backward compatible way an impossible task.

The worst idea is go generate. It shouldn't even be here. Littering source code with pragmas and forcing people to write 3rd party executable and maintain manifests isn't simple,readable,maintainable or elegant. Macros with reification would have been a bit more honest.

Go relies way too much on runtime behavior for a statically typed compiled language, This is absolutely undeniable.

The irony is that Rust, with garbage collection instead the borrow checker, would have been a better implementation of Go than Go itself. But it seems we'll never get that language.

Have you heard of Scala or F#?

After a couple of years with Go, I'm neither particularly happy about, not particularly unhappy. It's a decent language. Productivity is high, which trumps many other concerns.

There has been so much controversy about generics, but one related area where Go is consistently annoying is when you want to define a sort of language of data types -- where in a different language such as Haskell would define algebraic data types. For example, an AST of syntax nodes: There's no way define this (simplified):

    data Node = Literal | Identifier | And | Or | Not | EQ | NEQ | ...
Instead, you must define a bunch of structs and use a dummy interface that all nodes implement. Even then, Go has no pattern matching, so if you want to handle a node, you need a switch statement on type, then check the data with an if. And even then, there's no way to prove at compile time that the switch statement is exhaustive (you could do some runtime magic with reflection-based lookup tables, but it's still poor). If you want a generic mechanism to traverse and rewrite such deeply nested data structures, there's a lot of exhausting boilerplate in the way of the core logic, because, ahem, no generics.

I'm in the middle of a project that has three sets of such data structures (a low level AST, a high level expression grammar, and a query plan graph), and it's an area where the Go development experience turns from pretty good to pretty dismal. I've considered writing a mini-DSL and using "go generate" to generate Go code, but I've not yet decided on the best design.

Maybe a language like scala should fit your needs better ?

I am using it since 8 months and give me all those abstractions you speak about and its elegant synthax allow me to be very productive. I don't know if I am as productive as I would have been using go, but I am certainly more productive than using Java.

But this not a language as easy to pick up as go (even though I didn't have much problem to pick it up right out of school).

I also think it is much more difficult to write reliable software in go than it is in scala. The much stronger type system (generics, algebraic data type) help me to check the correctness of my program at compile time.

Scala is too overengineered for my taste — the combination of Java-style OO with functional programming doesn't sit right. I like the functional part, less the other stuff. I also want a slim, natively compiled language without a VM.

hear hear! That is the one thing stopping me from going full-steam ahead with Scala, reeks of java, promises the future yet is abused forever because it cannot decide what it wants to be ... do I want to be Haskell? do I want to be Java?

So, Rust?

Rust has a ton of complexity so it can remove GC. It's a big leap, and something Swift and Scala don't attempt.

Haskell would be a closer match.

Hmm, is it "a ton"? I've only written a few things in Rust, so I'm not overly familiar with it, but, for the few things I wrote, it was straightforward to at least reason about why something might not work (even though I may not know how to actually make it work). I imagine that, in a few weeks, one can become so familiar that the compiler won't have too many remarks about one's code.

Definitely going to be looking at Rust soon.

> Instead, you must define a bunch of structs and use a dummy interface that all nodes implement.

This point interests me. I personally eschew 'dummy' interfaces in my Go code, preferring to work with the commonalities of such an algebraic type.

For your Node example, I'd look for things that all of the nodes do or represent and work from there. The interface type built on that would have other bound functions using those commonalities to synthesize common functionality.

Remember that in Go interfaces allow one to add disjoint configurations, such as things that (to borrow from webdriver) accept Click() (like buttons) or Set() (input fields). Those interfaces can apply independently from the meat of being a webelement. By implementing (or mixing in) the behavior, you can have a richer whole.

The problem is that these data structures don't have behaviour. They're pure, immutable data.

The fact that I have to wrap them in an interface is also a performance concern, because an interface devolves to a pointer if it can't fit the contents into a single machine word. So in effect, every single struct becomes a pointer, which in my case seems unnecessary.

There are some concerns that cut across all uses of the data that could be methods, but even things like mapping over (transforming) the data shouldn't really need to be built into the data structures via interfaces. Not least because it's horrible to write. For example, consider this:

    type Expression interface{}
    type And struct {
      LHS, RHS Expression
You want to be able to do something like this:

    newExpr := Map(expr, func(e Expression) Expression {
      if and, ok := e.(And); ok {
        if and.LHS == True {
          return and.RHS
        if and.RHS == True {
          return and.LHS
      return e
How do you implement Map? You have several choices. You can let Expression have Map(), and then every expression needs to implement it:

    type Expression interface {
      Map(fn func(e Expression) Expression) Expression

    func (e And) Map(fn func(e Expression) Expression) Expression {
      e.LHS = fn(e.LHS)
      e.RHS = fn(e.RHS)
      return fn(e)
Or you could do something like:

    type Expression interface {
      GetChildren() []Expression
      SetChildren(...Expression) Expression

    func (e And) GetChildren() {
      return []interface{e.LHS, e.RHS}

    func (e And) SetChildren(children ...Expression) Expression {
      e.LHS = children[0]
      e.RHS = children[1]
      return e
Now you can implement Map() once for all expressions:

    func Map(e Expression, fn func(e Expression) Expression) Expression {
      children := e.GetChildren()
      newChildren := make([]interface{}, len(children))
      for i, child := range children {
        newChildren[i] = fn(child)
      return e.SetChildren(newChildren...)
...but at the cost of being expensive, due to slices.

Less expensive idea: Since all my expressions are nonary, unary or binary, I could have a Nonary/Unary/Binary to avoid GetChildren() and SetChildren() operating on slices, and then Map() could simply do a switch.

Or you could simply skip all of that and build a switch statement:

    func Map(expr Expression, fn func(e Expression) Expression) Expression {
      switch e := expr.(type) {
        case And:
          e.LHS = fn(e.LHS)
          e.RHS = fn(e.RHS)
        // ...
      return fn(expr)
This is why I was considering generating code, because it's terrible having to write and quality-check all that boilerplate.

(There's a lot of boilerplate: Map() is not the only function I need. I also need a top-to-bottom version (the above is depth-first) and a Find(), and multiple other things that need to recursively determine something about an expression, without knowing all the types involved.)

Anecdata, but my experience has been that people who really enjoy Go either don't care about or don't want at all the kinds of language features you are describing.

As with many language trade offs, at the end of the day people tend to gravitate towards languages that fit their own personal preferences

I taught Go to 3 fresh new computer engineers, where they had no real-life programming experience. It was different to them: channels, no traditional oop and no generics, but they picked it up very quickly, and with them we were able to produce a very large application. We tried to keep things as much as simple as possible, avoided coding magic as much as possible and using Go was a nice experience for us. Not saying this proves anything, it is just a real-life example from us. Most of the times people do not need all the interesting things you think you should have in a language, and may like their "opinoniated" way of approaching to problems.

Just curious... If they had no prior programming experience, why was go 'different'? Different compared to what?

> Just curious... If they had no prior programming experience, why was go 'different'? Different compared to what?

Probably compared to what they were shown in university/bootcamp/whatever.

'no programming experience' could esily mean 'no professional programming experience' which is very close to 'no programming experience'.

I am enrolled in a computer engineering course, and I can tell you the amount of code we get to write is ridiculously low.

They had only experience from university courses.

A year and a bit into coding in Golang full time after many years in C++ and Java, I like the language too. Compared to both of those languages, it feels simple and clear, probably because it is fairly young and very opinionated. I can think of a few things I miss (single-line IFs, IMPLEMENTS clauses for structures, MAP/FILTER for collections, ...) but they are all fairly minor things.

I agree with the complaints about project structure (GOPATH), difficulty importing third-party modules/packages, upper-case/lower-case public/private scoping not ideal... But I think it's a very good, flexible language on the whole and I think its approach to OOP and pointers is really good (even though it's different from most other OO languages). Unlike some of the comments, I think it's actually not too verbose considering the flexibility it offers (it's much less verbose than C++).

That said, for building web applications, I still prefer Node.js.

I'm curious: what is it you don't like about importing packages in Go? With go get and goimports editor integration, I find third-party package integration pretty easy.

If it's a question of versioning or package.json-style dependency management, I agree that Go still has some significant pain points that haven't been fully addressed directly.

Not the GP, but I note it's difficult to, say, take some code which uses package xyz and substitute the source-compatible package abc without editing every file. e.g. Try compiling a large project with a custom version of 'os' swapped in. (e.g. To add tracing, or simulate random I/O failures.)

Some package management systems can decouple what packages are called inside the code from what they're called outside.

There are only two kinds of languages: the ones people complain about and the ones nobody uses.

Such a discussion makes no sense. You will see plenty of web pages where people bashing at Go, like they are bashing at Scala, Java, Ruby, C++, and all the other languages.

I'm using the computer for over 30 years, writing in different languages, and I'm happy with Go, even it has his shortcomings here and there.

If I have to write some loop which talks a certain protocol over a network (servers) Go is my first choice.

The Internet is a funny thing, You will find a bunch of arguments that eggs are healthy and that they are not.

Similar with programming languages.

I recommend adding a pile of salt as banner to symbolize the intention of the repository better.

News you can use: one of the complaints is "weird mascot"

That wouldn't go on my list, no. One wonders whether hammers and drills would have mascots if they were invented today.

Here's my two cents after following Go and other programming languages for quite some time:

Coders looking for a better C are generally happy.

Coders looking for a better Java or C# are in general not happy.

I don't think its worthwhile to compare Go to dynamic languages.

> I don't think its worthwhile to compare Go to dynamic languages.

I do think we should be making this comparison, because there's a trend in devops of moving from Python and Ruby (each of which have system-provided dependencies) to Go (which statically links a copy of everything).

I see this as unfortunate because devops should be promoting taking full advantage of the system's well-understood and -tested libraries and services rather than avoiding them with approaches like container isolation. If you have a reason to avoid your distro's python, fix that.

You raise a good point. Perhaps I should have said it's a much greater challenge to compare Go to dynamic languages as there are too many moving parts. However, when you constrain the comparison to a specific domain, then the problem is more tractable. I don't have a ton of dev ops experience, so I won't weigh in on this matter. That being said, despite how far Python/pip has come there are still great challenges when installing certain packages. For this reason, I do see the value in statically linked copies of everything--even if it seems like overkill. Disk space is cheap, but does make remote installs take longer.

Is this the new "JavaScript, The Good Parts" ?

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact