The crowd that remains is mostly composed of people who have never used an ML-style language (Haskell, OCaml, F#, SML, ..) and come from a background of C, Java and Python. Go is definitely an improvement over these languages in many areas.
The crowd that remains is mostly composed of people who have never used an ML-style language (Haskell, OCaml, F#, SML, ..) and come from a background of C, Java and Python. Go is definitely an improvement over these languages in many areas.
That said, I think a lot of what makes Go great is because of its simplicity, lack of surprises, and general lack of cleverness. You can get your hands around the language features very easily, in mere hours.
Beyond that, I think the ease of building tooling on top of the AST (or in general), the ease of deploying code to production, the build speeds, the inclusion of go get and fmt, the policies around imports and variable use (use or lose) all add up to be more than the sum of its parts. It is very obviously built by a team looking to use it in production, on real projects, as soon as possible.
Cabal used to be horrendous. Now it's just mediocre, and better than the distribution tools I've seen in the Python world (e.g: easy_install). Much better than the lack of distribution tools in the C and C++ world.
> That said, I think a lot of what makes Go great is because of its simplicity, lack of surprises, and general lack of cleverness. You can get your hands around the language features very easily, in mere hours
nil everywhere is an easy to explain language feature. It might be easier to explain than pattern matching. It's definitely worse though.
I'm not saying that Go has no strong points in its favor.
It has plenty surrounding it that you mention.
It's just sad that this is all around such a poor language.
There is a lot of "worse" in Go. It seems the "worse" choices were for 3 reasons: (1) make the language simpler, and (2) make the compiler / tooling simpler to build or (3) ship a production usable product in X time. I think all might yield benefits in the long term.
Getting angry about a "poor language" is pointless, there are hundreds. It seems people get more angry that it might get popular more than their favorite language. For a bit, I was a bit of a stick in the mud about Go because I was hoping Erlang (maybe with Elixir) would take off. After that I was worried it might get popular before Rust had a chance to get off the ground.
But, after having spent some time with it -- I have grown to like its odd, pragmatic mix of tooling and features.
The authors of Eiffel went ahead and paid a very dear price to fix their nullability-everywhere. The C# author says if he could go back in time and fix one design mistake in the language, it would be the nullability-everywhere mistake. That it is responsible for a huge percentage of field issues with C#.
Yet Go was designed after these, and still put in nullability everywhere.
Seeing society pour tonnes of resources into a bad language, when we could all have benefited from these resources being poured into a good one is sad. Having a new language with new approaches and ideas is great. But one that we already know will create poorer quality software, not so great.
What a huge waste of talent and resources we're seeing here.
P.S: Cabal is not really so bad anymore.
My experience when using "cabal install <pkgname>" is generally 90% success, and 9% failures that I can fix by a simple "cabal unpack" on some overly restrictive package to fix its version constraints and carry on.
I don't think you appreciate arguments for pragmatism. I write plenty of C and Go code. I rarely run into null pointer errors in Go, while I run into plenty of them in C. It's possible to mitigate the Billion Dollar Mistake without encoding it into the type system.
This null issue has come up plenty of times on the Go mailing list. Search for the billion dollar mistake, and you should find some responses from the Go devs.
> But one that we already know will create poorer quality software
We do? I think you meant, "I believe it will create ...".
Of course, to make both of these reasonable, you would also have to add parametric polymorphism. And while you're at it, you may as well throw in full type inference. (I mean, why not?)
These aren't gigantic changes, and it's already well-understood how to implement all these efficiently--OCaml has it all, along with a fast compiler that outputs fast code.
But Go doesn't. I really wish Go had taken more (i.e. some) inspiration from OCaml.
Also, ignoring the merits of this particular case, I don't agree that a decision is reasonable just because it's the "different side" of a tradeoff. Virtually any choices can be recast as tradeoffs, but there are still wrong decisions to be made!
> These aren't gigantic changes
Then we have a fundamental disagreement. Adding sum types and parametric polymorphism would drastically change the language. I am not claiming that the change would be for better or worse.
> Also, ignoring the merits of this particular case, I don't agree that a decision is reasonable just because it's the "different side" of a tradeoff. Virtually any choices can be recast as tradeoffs, but there are still wrong decisions to be made!
Could you point me to authoritative sources that state how languages should be designed?
Going by the character of your post, it seems like you don't care much for the cohesion of a language design. The Go devs care about this, a lot. So saying "we should add feature X, and while we're at it, Y too" without stating how those features will integrate with the rest of the language just isn't going to cut it for people who care about the totality of a language design.
That said, the ability to build a static binary quickly and scp to a server is ... amazing. The convention (not forced) of localizing what you depend on in your /src and making hermetic commits is amazing in practice. It means that I simply git clone FOO && go build BAR and it builds and spits out a static binary I can ./bin/BAR
You do realize that these so-called language design mistakes seem to have no correlation on how successful a language will become?
>when we could all have benefited from these resources being poured into a good one is sad.
A language that nobody wants to learn is not a good language.
2. Nullability IS a kind of sum... and the idea of a zero value would be impossible (or at least ruinously inefficient) without nullability. Secondly, the fact that all dereferences are checked gives you most of the same benefits, just minus all the type noise.
3. Products for errors are actually a GOOD thing, in that they allow developers to easily choose between pedantic error handling or relying on downstream panicing to handle situations that are unrecoverable anyway. Whereas with type-level enforcement, you HAVE to do all that extra checking, and that straitjacket costs you clarity and elegance.
4. Interfaces do allow for sum types, and type switch statements and/or dispatch allow for pattern matching. Not terribly elegant, but effective and understandable.
My closing thought: type systems are crutches for reasoning about the behavior of programs at compile time... and we need all the help we can get with that challenge, so yay for type systems!
At some point, however, they become cages, because there will always be a frontier beyond which they will be unable to encode contraints that are obvious to the person writing the program, and at that point, the resulting contortions will obscure, not enlighten.
What do you mean "a zero value would be impossible"? The whole idea is that sometimes you need it, and sometimes you don't, so you want your language to let you distinguish these 2 cases and then check for your program's exhaustiveness in checking it when you do have a zero value.
The fact dereferences are checked converts one runtime error into another. Proper sum types convert a runtime error into a compile-time error. How do you possibly view this as "most of the same benefits"?
3. No, they allow you to use the wrong value when there actually was an error and the value has a different meaning than intended (or just another nil dereference).
The type system forces you to have either error checking or you can use explicit type system escape hatches which are then greppable. For example, Haskell does have unsafe functions like "fromJust" which unsafely assume a result value is not an error -- but these functions are rightly considered a code smell. If you want to use them, you can, at least making the smelly parts of the code easily findable. In Go, all the code is smelly, instead.
4. Type switch statements are so clunky I don't think they can, with a straight face, be considered "effective" alternatives for pattern matching.
Consider:
myFunction Nothing = ...
myFunction (Just x) = ...
This is understandable. This is effective. Do you think the equivalent type switch in Go is?Type systems don't become cages and aren't "crutches". They are tools that you can use as much as you want. A program in Haskell can be written to be type-unsafe and put all the code in useless types. Or it can encode any amount from very little to a lot in the type system. It's just a tool you get to choose how much of your invariants you want to verify.
Go doesn't offer you that tool.
3. Null values are almost always what result, as opposed to undefined or incorrect values. So it's a product type that is effectively a sum type. As for code smells: actually, there is a greppable equivalent in Go, and it is "... , _ := ... ". Future Go compilers might allow for a flag that treats "error" types specially in this regard.
Interesting to hear about fromJust, didn't know about it.
My own experience is that unchecked errors aren't where Go's 'debugging load' lies. The load is often in reasoning about concurrence, causal dependancies, and state machines.
4. If you're saying that because pattern matching isn't a core feature of Go, you wouldn't use it for everyday code, you're right. I'm a fan of pattern matching, I use it in Mathematica all the time (albiet unchecked). But my point stands: you can build sum types through interfaces, and when that makes a design significantly cleaner, a decent Go programmer will use it.
5. "Type systems don't become cages": well, a lot of people will disagree with you there. Ask C++ programmers about const. Ask Java programmers about checked exceptions. Ask Haskell programmers about logging. So I guess that's just, like, your opinion, man. :)
5.1. Go may not offer you as rich type verification as Haskell, but it does things Haskell can't: it can detect race conditions, for example, in concurrent code [I shudder to think how you even write concurrent code in Haskell].
Another closing thought: you focus a lot on Haskell. Haskell seems to be a very fecund place to devise type-theoretic patterns and techniques. And it seems to have taken the niche of "programming-language research testbed", which also results in a lot of interesting ideas. But there isn't nearly as much evidence that it is a smart choice for production code.
I've been impressed by what I've seen, read, and understood about Haskell. Still, I have an unshakable impression that a lot of the quality that is associated with Haskell code is explained better by the hypothesis that the bar for writing Haskell code is very high: bad programmers can't write Haskell programs that do the things they want to do, so we see a lot of nice Haskell code and little bad Haskell code because of the base competency of the audience.
But that doesn't imply that an experienced programmer would write higher quality code in Haskell than in Clojure or Go. We all have a complexity budget to spend on using tools to their full potential and doing things a certain 'proper' way. Simple languages leave more of that budget to spend on your program itself. Type-heavy languages leave less, with the promise that the cost is amortized. Calling Go a "sad language" and implying its authors are incompetent because you don't agree with their particular choice of budget demonstrates, to me, a certain arrogance and lack of imagination.
3. So that means you might get both a success and error value at the same time? And ignore one of them?
As for concurrent problems being worse than nullability, that sounds very plausible -- as Go also messed up concurrency by making any guarantee about immutability of shared state impossible.
4. That means the threshold for using sum types is pretty high -- which means you end up not using them when you ought to (e.g: error products rather than sums).
Sum types would make error handling significantly cleaner, but are not used that way in Go.
5. I think C++ programmers rather like const. Maybe you are confused with C programmers? C just got const all wrong. Java got checked exceptions wrong, too.
Haskell logging is not a serious problem in my experience. Debug logging is done the same impure way as in other languages. Production logging is easy to add as a feature to your monad stack.
Some bad type systems become cages -- and then people hate all type systems. Ironically, Go also has a type system, and since it is a rather poor one, it is a cage as well.
5.1: I'm sorry but this point of yours shows rather extreme ignorance about Haskell. Haskell can statically prevent race conditions. The facilities for writing concurrent code in Haskell are far more advanced than those of Go.
> But there isn't nearly as much evidence that it is a smart choice for production code.
I use a bunch of languages for production code, including Haskell. In my experience, Haskell is the best choice for production code of most kinds.
There is likely a selection bias in Haskell. That isn't why Haskell code tends to be good. As an example of a measure of quality: Haskell makes it easy to verify there are no runtime crashes in your program. Even excellent programmers will not be able to make that guarantee in Python, Java or Go. This is a pretty huge thing -- having a guarantee of no runtime crashes. The same Haskellers who can give this guarantee for Haskell code would have no hope of giving this guarantee in these other languages. I'm not talking about a big "try: except:" clause -- but about a compiler exhaustiveness check that verifies all functions are total, and a cursory check that all recursions are well-founded.