This reminds me of the (by now) old saying that code is more often read than written - sure, you're not forced to write macros, but if you use macros (or more exotic features), you force the developers who read your code to become familiar with (possibly arcane) features that they maybe would have never used (or needed). That's why I think Go's approach to not try to be "everybody's darling" by implementing every conceivable feature is commendable (even if mentioning this in a Rust-related thread will get me downvoted again).
As coined by Google engineers: software engineering is programming integrated over time. Languages that are paragons of cutting edge PL research unfortunately tend to forget that. Go’s simplicity tends to be brought up as a negative trait (dumb language, dumb users, etc) mostly by those who weren’t yet woken up by a page that required them to quickly read, debug and fix things. Or even jump into another team’s codebase to do the same thing. Last thing on your mind then is the type safety and elegance of a language.
> Go’s simplicity tends to be brought [...] mostly by those who weren’t yet woken up by a page that required them to quickly read, debug and fix things. Or even jump into another team’s codebase to do the same thing. Last thing on your mind then is the type safety and elegance of a language.
Actually, type safety can be quite useful in such a context: it allows the person who designed the types in use to enforce certain restrictions on the developer who is hurriedly adding code to an unfamiliar codebase, thus reducing the requirement for that developer to understand larger issues in the system.
On the other hand, more often than not the developer who is (more or less hurriedly) adding code is trying to do something that the original designer didn't think of beforehand, and because of that they now have to jump through additional hoops to make it work...
This is a good observation. Some of the best codebases I’ve worked with are the ones whose authors insisted on keeping things easy to modify or remove (not by introducing unnecessary abstraction, but by simplifying things as much as possible). Some of the worst codebases are those where the author assumed that their elegant solution is ultimate and will never be read in circumstances other than to be admired.
I don't disagree, but this goes both ways (and I typically only see one side of this argument put forward). They also allow the person who designed the types to make the types so complex that the average user cannot understand what is going on. You may argue "well that person has no business looking at the code" but sometimes you have to look into other people's code when you are debugging an issue.
No doubt type systems can allow you to write safer, more robust code. But they can also allow you to introduce new risks to your codebase, one of which is difficult to read/understand code.
I don't think that people complain about Go's simplicity. People complain about the hamfistedness of Go's simplicity. The downstream effects of it include, for example the wat that you handle JSON in Go.
But there are a lot of quite frankly crazy choices, like making (milliseconds) an integer type that is incompatible with numbers. This means to multiply an time with a non-constant integer, you have to cast an integer to millisecond type first, then multiply. Which breaks the brain of a scientist like me and makes me want to throw my computer out the window.
My daily driver is Elixir, and it's not impossible to jump into totally foreign code and debug it. In fact, just the other day I did an interview where the 20 minute technical problem was drilling down from the frontend into the backend of a new-to-me system and finding the logic bug in a database query that was causing the wrong data to be surfaced.
It's not about how effortless the code is to write, but the total lifecycle effort. This includes for example maintenance, security, extending and on-boarding new team members.