Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Meh, everyone has a preferred type of language they find least surprising. To many Ruby is the least surprising, and Matz used "principle of [his] least surprise" as a guiding principle when designing it, but many other people find it enigmatic and confusing. Some people find Haskell the least surprising and most consistent, others say Java is. Every language has caveats and inconsistencies, even Go. Like how Go has generics, but only for two or three built-in types. It seems to be more about the person trying out the language than the language itself.


Minimalism helps. Ruby has one kind of minimalism, but fails at minimalism in some other ways. The same with Go. People who like Haskell like the kind of reasoning it facilitates. People who like Java want a managed version of C++'s "good parts."

A lot of it has to do with expectations. Going beyond the principle of least surprise, there's also a principle of "no dismay." Go is like the dependable old Corolla. It sets modest expectations, and it delivers a high degree of utility very consistently, and if you're ever let down, it's very rare. C++ can cause you utter dismay due to rather subtle slip-ups. I've only seen this level of dismay in Golang around subtle resource release issues around prepared statements.


The article has two examples of how I already find Go to be inconsistent and/or surprising:

    w, err := b.NewWriter(ctx, "gopher.png", nil)
    ...
    _, err = w.Write(data)
    ...
    if err := w.Close(); err != nil
Why is := used on the first line but = used on the second line, and then := used again on the third line? I get that you use := when introducing a new variable, and = when just assigning, the third line is not really introducing a new variable, since err is still in scope... or is it?

Not to mention, what is the last argument `nil` supposed to represent? In most high quality Node.js libraries, functions/constructors will take an optional options object so you can omit it, or pass an object like { timeout: 300 } to it.


Third line ':=' is not required, you can reuse globally scoped 'err' with '=' as well; unless you want to preserve the current value of global 'err' for future use (which would be confusing).


The third line has a new err inside the if statement's scope.

It's equivalent to

    { // begin new scope here
      err := w.Close()
      if err != nil {
        foo
      } else {
        bar
      }
    }


> the third line is not really introducing a new variable, since err is still in scope... or is it?

The "if" block creates a new scope.


agreed that the := that can be used when one new var is introduced but not the others is extremely confusing.

I also hate the inconsistent behavior when you check multiple errors in a row, the first one will get := but not the second one (if you don't put those in a if statement, which I never do). But now if for some reason, you remove the first := statement, the code will error on the second one, that needs to be changed from = to :=


> It seems to be more about the person trying out the language than the language itself.

This is true, but Go does have a clean and mostly familiar syntax, by design: you can mostly follow along with a piece of Go code even if you haven't learned the language.

I think that what the OP is pointing at is a related issue: readability. This is explicitly a high-priority design goal for Go: lots of things in the language are designed so that developers can easily read and understand the code in the projects that they work on, and that memory use is never hidden.


On the flip side, I personally find go extremely difficult to read.

First, repetitive error handling makes it overly tedious to actually get a quick intuition for what a function is doing. With Rust as a comparison, it’s extremely easy to see what the happy path is trying to accomplish without having the downsides of arbitrary and unexpected exceptions. Go is inarguably worse here.

Second, the inability to express higher-level concepts (due to, yes, lack of genetics) seems like it forces every function to deal with the nitty-gritty details of whatever it’s trying to accomplish, rather than being able to express ideas at a higher level and deal with the specifics in smaller code units. Yes, you don’t get “surprising” behavior, but the cost of this is that you’re forced to keep both the high level goals and the low level details in your head simultaneously, which I find to be a massive hindrance.

As sort of a side effect of both of these, there ends up being a ton of repetitive boilerplate. This actually hides bugs since it’s easy to miss minor differences between overly similar blocks of code dealing with errors or looping. As an example, languages like Rust and Ruby that have proper functional-style iterator methods prevent so many bugs through their inclusion it’s absolutely baffling to me how someone would opt to design a language today without such things.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: