5 ms·
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 wit
by lobster_johnson 12y 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.
- cromwellian 12y agoThe 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)
- joelgwebber 12y agoSee my comment above for why I don't find this to be that great for server code. If each of those getOrElse() is an actual potential runtime failure (as opposed to just something that could in theory be nil, but you know won't be, by construction), then it's something you're going to need to deal with explicitly. E.g., think of each of those method calls as being a call to some external system (filesystem, memcache, database, etc) that might fail, even if the code's correct. Throwing an exception is usually a pretty bad strategy for those cases if you want to able to sort out what went wrong later.
- Dewie 12y ago> If each of those getOrElse() is an actual potential runtime failure (as opposed to just something that could in theory be nil, but you know won't be, by construction), then it's something you're going to need to deal with explicitly. getOrElse is a function that is used for exactly that, i.e. for things which can fail in the sense that a parser failed to parse some text because there was a syntax error in the string, or the Map datastructure didn't contain the given key so it returns "nil" or "None" or whatever. If you had something that you know wasn't supposed to be able to fail, then you would probably just go ahead and assume that it can't fail, and let the program error out if it does fail since then you've revealed a bug. To use getOrElse for things that shouldn't fail to begin with is not good. I don't know what you mean by "deal with it explicitly", since getOrElse indeed does deal with it explicitly. > E.g., think of each of those method calls as being a call to some external system (filesystem, memcache, database, etc) that might fail, even if the code's correct. Throwing an exception is usually a pretty bad strategy for those cases if you want to able to sort out what went wrong later. getOrElse doesn't throw an exception. It returns "null" or "None" or whatever value is supposed to represent "does not exist" in the case of some failure (failure here being "produced none/nil/null"). It has nothing to do with throwing (or catching) exceptions.