top | item 8626430

(no title)

dete | 11 years ago

Well, you can consider it mentioned to the author... that would be me!

I shouldn't have tweaked Java so mercilessly since I did love the language for so long (lo, these many years ago). However, I do think you really underestimate the compile speed of Go. Even if 99% of your builds are incremental, you can't entirely discount the occasional clean build. While it has been many years since I hacked Java, even incremental builds could take more than a few seconds. Even a clean build on our 50 kloc Go codebase clocks in under 2 seconds (0.1s when the disk caches are pre-warmed).

As for generics, I do like them, and seeing them on iOS (with Swift) is a pretty big win. I'm not entirely convinced that Go is much poorer without them, however. The type system in Go is really unique; if you tried to program Go using Java-style paradigms, there's no question that you'd find it pretty disappointing. However, Go does allow some new ways of thinking about types that allow a degree of flexibility that simply can't be expressed with formal class hierarchies.

If what you're doing works for you, just keep on truckin'. But if you give some of these new languages a chance, you might find the areas where they really shine. We did!

discuss

order

lobster_johnson|11 years ago

If you want to contrast compile speeds, try Go vs. C++. Java compilation has been very fast for a long time, as others point out.

For my part, my main beef with Go isn't necessarily the lack of generics, but the obstinate lack of expressiveness. It's back to Java or Python where you're forced to break up your code into discrete, imperative chunks instead of chaining stuff together in elegant flows. It's like Go's authors missed out on functional programming. No pattern maching, which seems like a huge miss considering Go has select { }. Go's syntax is, in many ways, even more rigid than both Java and Python.

That rigidity extends to error handling. While I agree with the philosophy behind Go's rejection of exceptions, I don't agree with how it's been implemented. In discussions about Go people always talk about exceptions vs. explicit error returns, but hardly anyone mentions the fact that error handling completely takes over our code: errs are everywhere!

A concrete example: Go has := for type inference, but it turns out you can almost never use it, because almost every function needs an "err" that you end up declaring. Often you start out like this:

    if result, err := getResults(); err != nil {
      return
    }
Quite elegant. But then you need to add some more code, and you actually can't rely on type inference anymore:

    var err error
    var result *Result
    if result, err = getResults(); err != nil {
      return
    }
    stats, err = computeStats(result)
It turns out that just because you needed another "err", you had to rewrite the statement, which is frankly ridiculous.

The next problem here is that we can't simply this:

    result, err := computeStats(getResults())
That's because computeStats() takes a Result, not two arguments (Result, error). In functional languages, this is elegantly solved through monads, but not in Go; you can't "short circuit" function chains that might return errors. There goes your expressiveness.

Another problem that the error phenomenon infects the language with is that you can't have global variables initialized this way:

    var spaces := regexp.Compile(`^foo`)
To get around this, the regexp module defines an alternative function:

    var spaces := regexp.MustCompile(`^foo`)
(Never mind the fact that you're not allowed to declare this as a const. It is a constant, I want it to be a constant, not a global variable!)

The fact that almost every function ends up having an "err" around raises the question: Why is it not an integral part of the language in the first place? Why do I have to declare err?

Other languages (Swift among them) fix this problem through sum types: The function can return a value which is either a real value or an error. Go's idea of returning a value and an error is logically nonsensical in almost every case, because the error is used to signify that the value isn't available due to failure. I'm not a language-theory purist who thinks everyone should really be using Haskell; these are practical concerns.

Overall, Go does feels disturbingly warty in places, which is incredible for a new, clean-slate language. Favourite wart: interface types being magically pointer-based, leading to the whole non-nil value being nil idiocy; it's mindblowing that they got this so wrong.

I liked Go a lot better before I started using it.

joelgwebber|11 years ago

My personal experience at a startup with a large amount of Go code running its frontend and backend servers. YMMV.

First off, I also find the interface-nil thing to be a frustrating edge-case. I also know that this design was the result of some difficult tradeoffs, as such things often are. We can argue about whether they made the right tradeoff, but I don't think it's fair to refer to it as a "mindblowingly wrong idiocy".

Second, on error handling. Having now written a large amount of server code in Go, I find that I strongly support their approach, even when I find it a bit verbose. Here's how I find it actually plays out in practice:

    // You write something like this:
    result, err := getResults()
    if err != nil {
      return err
    }

    // Then you reuse err for the next call
    result, err := getResults()
    if err != nil {
      return err
    }

    stats, err := computeStats(result)
    if err != nil {
      return err
    }
Eventually, you realize that `return err` just isn't enough most of the time, because it's impossible to make sense out of your logs. So we added a simple "error chaining" function that allows you to pass context when you return the error, which makes the logs much clearer than just a single error message, or raw stack trace. And of course it is often the case that you want to do more logging in your error blocks, even as you ignore the error and move on (e.g., "spurious error reading memcache entry; falling back to the slow thing").

The practical reality of writing good server code is that exceptions which get caught way up the stack are inscrutable in your logs (at least that was our experience), so it makes sense to know up-front exactly where failures can happen, and to be forced to think about them the first time you write your code. Yes, it can be a bit verbose, but we've found the tradeoff to be net positive.

cromwellian|11 years ago

The error handling stuff more than lack of generics is my single biggest beef with the language. It is basically C-style error handling from the 80s all over again, only you return error codes as a separate multi-return value.

The Maybe monad style is so much cleaner, it also allows Elvis operator-like changing, e.g.

foo().getOrElse(blah).getOrElse(baz)

CJefferson|11 years ago

Perhaps not 2 seconds, but I just checked and a 41,000 line Java codebase I work on compiles from cold in 3.4 seconds.

I'd never bothered with incremental compiling because the build times are so low, but I just tried now and changing 1 java file leads to a 0.3 second rebuild.

pjmlp|11 years ago

> However, I do think you really underestimate the compile speed of Go.

You mean like Turbo Pascal 4.0 back in MS-DOS?

Roboprog|11 years ago

or TP-Win vs Turbo-C-Win. Pascal compilation was "don't blink, or you'll miss it" vs "go get coffee...".

Go reminds me a lot of TP with garbage collection, dressed up to look like C to avoid bruising Bell Labs egos. (OK, closures and multiple return values are pretty cool, too, as are associative arrays and array slices)