And in hindsight and after 15 years of cleaning up the mess we know that they just needlessly made a generation of developers suffer through primitive abstractions.
And Java is not a badly designed language at all, it did make several tradeoffs at places for various reasons, but it had a clear design goal and I think it did live up to it.
The most glaring issues were fixed in Java5 (similar to how golang just had their generics story improved). It took us all the way to Java8 to have a semi-pleasant language. I'm still waiting to see if Golang's generics are good enough to make abstractions/monadic operations similar to the Java's Streams widespread. If memory serves, basic pattern matching is coming in Java20, only 12+ years after Scala.
In contract to, say, virtual threads, a lot of these are language-level features. So they could have been done _much_ earlier. A generation of developers had their taste for abstractions skewed by the early days Java. Doing the job of the compiler in their heads. Now the next generation finds itself in the same situation with Golang.
I think this slow moving is really an advantage for Java, such a widely used language can’t allow breaking changes willy-nilly so any language feature will effectively become written in stone. Also, surprisingly many things can be expressed purely by “objects with method” APIs (Steele’s growing a language)
A new language on the other hand could have actually iterated fast because relatively few programs are written for them in the early years, go is just not a good language.
How nebulous it is is really well demonstrated in Pike's talk[0] where he somehow believes that a for loop is "simpler" than methods like "filter" and "map", and makes the very strange and incorrect claim that it's going to be faster as well (I suspect he was thinking "in Go" because those would be slower, given a lack of generics).
A lot of Go's design philosophy is, very bluntly, directed to bad programmers. For example, Pike talks about how a developer using a language with many features might spend 30 minutes just iterating on a few lines of code, because there are so many features available to use within that code. That is a very junior level problem - junior level programmers aren't very good at understanding that there's low return on investment for that sort of work so they coo over fancy code that works exactly as well as boring code.
Similarly, junior developers have a hard time learning features, so they have a hard time reading code that uses them and Go puts readability above all else. A senior developer should surely hear "filter and map are complicated?" and scoff but a junior developer would go "I have no idea what those are".
And there are significant constraints because of that lack of features - years of not having generics because it's too complex, for example.
We need junior developers to be able to approach new code but we also need to give powerful tools to the engineers who understand how to use them properly, especially when they're needed most.
This is why I think that Go is a bad language, but not just because it's missing some feature X or Y or Z, but because it is effectively an attempt to create simplicity by saying "you can't handle complexity". I also think it fails in its goals right off the bat because of goroutines and having some basic footguns, but that's not worth getting into.
What I think is good is that we can watch how a language built with this philosophy progresses over time. For one thing, it inevitably takes on "complexity" (generics), which I think is notable. At some point those people who love that they can jump in and know everything do actually run into the cases where those features are higher ROI (ie: writing higher performance libraries with nice APIs requires generics) and there are enough of them that they can push for those changes.
It's also interesting to watch a language grow with such a custom toolchain. Go has its own compiler, which makes a lot of sense for some of their goals, but it has also meant that they've had to reimplement (or just have not implemented) lots of optimizations.
To be clear, in case I've somehow insulted someone who writes Go, if you like Go I'm not saying "you are a bad developer", I'm saying that Go was designed for bad developers. I know lots of very good developers who like Go.
Just a trivial example: trying to use append(x, y) without re-assigning to x? Congrats, it will compile without warnings, work fine for a while, then at one point append will reallocate and bang, have fun letting the newbie debug that on a Friday evening.
[~] $ cat main.go
package main
import "fmt"
func main() {
x := []int{1, 2}
append(x, 3)
fmt.Println(x)
}
[~] $ go run main.go
# command-line-arguments
./main.go:7:2: append(x, 3) (value of type []int) is not used
and if you assign the result of the append to something else, x is unchanged.
Thompson and Pike are living legends. They had absolutely nothing left to prove. Yet they came together for another major project, backed by the most powerful technology corporation in the world, and they produced... this?
Go feels like a random hodgepodge of ideas from three or four computer science undergraduates – not like the creation of engineering superstars with a century of experience between them.