My main issue with "complex" or "expressive" languages are how they scale. Certainly not with regards to performance or throughput, but rather with how they scale with people. How will the code base look after 5 years and when a company have 50, 100, 200 devs instead of 10?
Expressive languages invites people to have their own dialects, their own little version of the language. It also invites people to be too smart for their own good and especially for the team's good, resulting in code that's very hard to debug and maintain for other people than themselves. I've seen in the game industry with C++ how bad it can get[1]. Because in practice there's pressure, deadlines, temporary stand-ins "helping" other teams to finish their sub-projects. There just isn't always time to be strict, so code rot will always sneak in. Of course this happens in all languages, but I find it much less extensive in so-called "boring languages".
[1] Because of this I'm actually really surprised that a few big game industry companies have recently adpoted Rust, because the main problems afaik were code complexity over time and build times.
Would take an expressive language any day, with some restrictions on templating / macros enforced on junior developers, rather than writing tedious for loops in Go.
Why? The writing part of this job is the easy part.
Furthermore, if you think that this problem is solved by just hassling junior devs a bit you're painfully wrong. Some people with great experience are still prone to this, some times as a matter of style.
Go makes it easy to understand what is happening at a micro per-statement level and not a macro level. For me, readability is about expressing __intent__ and not about every machine detail. Writing all that boilerplate is also more error prone.
My favourite example: see the following code in Go
containsVal = false
for _,i := range array {
if (i == val) {
containsVal = true
}
}
This is one statement in any language with reasonable abstraction capabilities:
python:
`val in array`
Javascript:
`array.includes(val)`
Java:
`array.indexOf(val) != -1`
C++:
`std::find(array.begin(), array.end(), val) != array.end()`
(Actually C++ one is already quite verbose and little distracting, but sure you can implement it better in C++)
Go is only so-called modern language in which standard library cannot provide trivial abstractions to convey intent. Instead you have to use interface{} escape hatch or use codegen.
Stuff like this just isn't important compared to the benefits. One has to focus on the positives and negatives and each of their proportional importance overall.
In that particular case, it's just fine to workaround these things with interface{} and move on to more important stuff. Or perhaps consider to use a map if you find yourself constantly needing to pick out an entry from a slice.
Expressive languages invites people to have their own dialects, their own little version of the language. It also invites people to be too smart for their own good and especially for the team's good, resulting in code that's very hard to debug and maintain for other people than themselves. I've seen in the game industry with C++ how bad it can get[1]. Because in practice there's pressure, deadlines, temporary stand-ins "helping" other teams to finish their sub-projects. There just isn't always time to be strict, so code rot will always sneak in. Of course this happens in all languages, but I find it much less extensive in so-called "boring languages".
[1] Because of this I'm actually really surprised that a few big game industry companies have recently adpoted Rust, because the main problems afaik were code complexity over time and build times.