YourLanguageSucks
wiki.theory.org
wiki.theory.org
I'm a bit of a fanboy, but when you compare it to the language that has been around longer and is a direct competitor (Java), C# really blows it out of the water for most common programming tasks (async, operator overloading, pair/triple returns in C# 7, var keyword, superior generics, to name a few.)
LOL
"Go's error type is simply an interface to a function returning a string.
Go lacks pattern matching & abstract data types.
Go lacks immutable variables.
Go lacks generics.
Go lacks exceptions"
I've been searching for as succinct a criticism as this and now have it, thanks.
We have a senior dev whose software caused a memory leak in prod. One of our less senior devs (who has an engineering PhD in concurrent systems theory) immediately identified that the senior was trying to do clever optimizations for single threadedness that simply cannot work when concurrent... I wouldn't be surprised if his code is causing a goroutine leak.
I am convinced that go is popular because it's comfortable to elder seniors and makes them think they can use their aging python/c/c++ skills in something flashy, new, concurrent, "supported by Google" and... Well at least it's genuinely safer.
Disclaimer: I'm an aging dev myself.
It’s true that you can write bad Go by trying to do things that are not a good idea, but I’m not sure what you’d expect. The Go ecosystem is damning evidence of the ease of writing pretty good code in the language.
Disclaimer: I work for Google (but I do not work on Go and I did not work for Google when I first began writing Go.)
There are, however major footguns, I find it hard to read other people's, or even my own code (parsing nested if conditionals is no fun when you're debugging under duress), and CSP is really just.... Not the best concurrency paradigm.
Pardon me. C was my second real language after Pascal and c++ came immediately after that in a 6 month timespan, so for my underperforming and scelerotic brain (aside from an ld_preload shim I wrote two years ago haven't touched them since the aughts) they are "together" in my brain. Curly braces and all that.
Looks like your senior dev has completely missed that.
I've had the opportunity to talk with a few people who were involved in Go's design and they explicitly said the idea was to get fresh-out-of-school programmers to be productive on web systems immediately.
Mono/C# is better, but the learning curve is pretty steep compared to Go with all the missing reference assemblies, diverse runtime versions, etc. Gopath sucks and sucks to configure but it's less complicated than .net.
So you can ask a junior web dev to learn a new language in a week and write a native windows application, and they might actually deliver. Whether this is empowering or signals the death of Quality remains a hotly contested issue.
But when it comes to Go, I absolutely adore its simplicity. I find myself wanting to write more Go code and I sometimes even catch myself trying to find reasons to write implementations in Go.
Anyways, that's just my two cents.
> Similarly, the len function on strings returns the number of bytes in the string, which is not necessarily the number of characters.
Should len do unicode normalization (NFC)? Than it's extremely complex. Or should it count code points? Then it's simply broken.
Counting bytes is fine, I'll do explicit normalization if I need to.
>can be conveniently concatenated with +, and can be used as map keys.
Does Go do a normalization for a concatenated string as it should?
And the select code [0] is kind of horrifying.
I am going to disagree with this, though:
> Go's native package system does not support specifying versions or commits in the dependency information. Instead, the go community recommends that each major release have its own separate repository; github.com/user/package/package-{v1,v2,v3}.
As far as I'm aware, you don't create a new repo for a new major version - just a new branch, or a subdirectory. This comment [1] from Russ Cox describes it pretty well IMO. (Though it appears that the page hasn't been updated since 2017, and Go Modules is more recent than that)
0: https://github.com/golang/go/blob/master/src/runtime/select....
> Because strings are just slices of bytes, there is no simple way to index or slice a string if it contains non-ASCII characters.
Indexing into a UTF-8-encoded string is expensive -- O(n) instead of O(1) -- so it's good that it also looks expensive.
> Despite the above, Go has two competing types for representing text, string and []byte.
As has any other serious language. Byte arrays and strings are just not the same thing. My only real gripe with Go's treatment of strings is that converting from []byte to string is a simple cannot-fail cast. That should be an operation that checks for valid encoding, and returns an error otherwise, like Rust's str::from_utf8.
> Deleting the nth element from a slice sure doesn't look like deletion: [code snippet]
This is a fair criticism, but IMO way overblown. I cannot remember the last time I had to delete individual elements from slices. Most of the time I'm doing map() or filter(), or rather, the equivalent spelled-out for loops since Go does not have generics.
> If you import a library or declare a variable, but do not use it, your program will not compile even if everything else is valid.
And that's great. I don't want old libraries bloating my program needlessly. It's sometimes a bit annoying to have to write
_ = foo
into unfinished functions because otherwise it will not compile due to "error: foo is never used", but I like my compilers strict.Also to this point: You can set up your editor to run your code through goimports automatically, which will completely do all the adding, removing and ordering of imports for you, as well as format your code according to the standard style. More languages need this.
> If multiple channels are available to receive from or send to, the select statement picks a case at random, meaning that to prioritize one channel over another you have to write: [lengthy code snippet]
Having to prioritize channels manually is a bit of a code smell in my opinion. I would need a real-world example to judge, but it smells like the real problem is how you slice up your work into goroutines.
> Two implementations of random numbers, math/rand and crypto/rand.
Again, this is completely standard for any serious language. For mathematical simulations, you don't care about cryptographic properties of your RNG and want the extra speed that a simpler RNG like a Mersenne twister gives you.
> The errors package is twenty lines long, because Go's error type is simply an interface to a function returning a string.
You forgot the criticism here.
> Go's native package system does not support X
All of these are wrong. Go has added a proper native package system in 1.11.
> The contributors don't listen to the community.
One of the two examples shows that the contributors do listen.
> Almost nothing in this list can be fixed, because of the Go 1 compatibility promise. Until Go 2.0, that is, but that may never happen.
That's the problem with crystal balls.
TIL, you need a crystal ball to foresee the features and design decisions invented many decades ago and polished by the industry.
Who knew that living without parametric polymorphism in a static language would be painful. Of course this requires a handful of RFCs because nobody knows how to implement parametric polymorphism right as if ML didn't do it decades ago with soundness proofs and all needed extensions.
Who knew that `if err != nil` is error prone, verbose and noisy way of dealing with errors. Again, nobody knew how to do it right, there were no Common Lisp, Scheme, Haskell who already did this right. There are Monads, Exceptions, Effects, Conditions for error handling, lets throw all of this experience away and invent some obscure `try` keyword.
Reinventing the wheel is fun. Rooting your work in previous rigid and battle-tested (or even formally verified) experience is boring and complex.
So horrid that it's become essentially standard :eye-roll:
Not to mention “nonlocal”...
> C#: You can't perform any operations, even simple arithmetic, with objects inside a generic method (e.g. T plus<T>(T t1, T t2) { return t1+t2; }
Well... obviously.
This is actually one of the reasons I think every language except for JavaScript sucks for open source web-development. Not that I particularly like JavaScript myself, but everyone knows how to use it. I work for a Danish municipality, we use a lot of open source software, but we’re roughly 10 developers and ops technicians that support 7000 employees and around 300-500 IT systems. As a result we can’t use every tech-stack. This is pretty standard for average sized muniplacities by the way.
We don’t share tech stacks, but we do work together, and as you can imagine that leads to wasted resources. We’re a C# shop our direct neighbour does PHP, because of limited resources, we can’t utilise each others projects. That’s just a silly waste. Good luck convincing anyone to change their overall and often cohesive strategy for tech choices though.
The one technology we can all use, however, is JavaScript and everything build within that ecosystem can be put to good use everywhere. So while the language sucks at a lot of things, it’s also the only universal web language that we’ve got.
One suggestion, if you don't mind. When I did work for the DoD as a contractor, because of small business rules in government contracting we were pretty much forced to work with at least 10 other companies and all of them had their own preferences for tech stacks, their own expertise, etc. So we tried to go down the road of using things like thirft or avro so regardless of what language / stack you used, as long as you could interface with those on the same network (since we're talking about inter-service communication here) it was still pretty quick.
It's not ideal but sometimes you gotta work with what you have. Sometimes you can find unique ways to bridge gaps across platforms to re-use work. One time a group we worked with tried to use a web app with multiple iframes as the communication bridge between stacks. It _worked_ but it was pretty hacky and slow.
The problem comes when we want to contribute or run things ourselves, in which case I’m not sure stuff like avro would help us, because we genuinely can’t keep X server technology secure or even updated with our available resources. I mean, I’m sure our Azure and ADFS technician could learn how to operate a JBOSS server on red hat, but he doesn’t have the time to do that and do his regular job. He might not even be interested in doing it, being one of the most talented Microsoft technicians I’ve ever met, he might simply move to a place that let him work with what he enjoys. Those aren’t great engineering arguments for not running JBOSS, I know, but that doesn’t mean it’s not part of managing an IT team.
What about Wasm and transpiling?
Silly micro example to explain what I'm talking about:
JS:
const countUpFromN = (n) => () => n++
Py: def countUpFromN(n):
def add():
nonlocal n
n += 1
return n-1
return add
Keep in mind that you only need nonlocal to write, reads will work fine. So you can use a variable a bunch in some scope, then suddenly have things break when you try to simply write to it.JS:
foo(x => () => x)
bar(x => () => x++)
Py: foo(lambda x: lambda: x)
def countUpFromN(n):
def add():
nonlocal n
n += 1
return n-1
return add
bar(countUpFromN)
Fairly damning to python IMO. I'd be interested to see examples of where small changes in Python require large changes in JS.(1) Language x claims these as its design principles/goals: P1, G1, P2...
(2) These y things subvert those aspirations.
(3) Modus ponens &x suxxxorzzz_Aslang__
I'm not the biggest fan of Go for it's stripped nature or Rust for the complex babysitting the type system needs, but I much prefer both to C. so that is a win for me and hence both achieve that goal.
Similarly, I love how expressive Scala is, that seems like an unspoken design goal of Odersky's. You can't get that without there being a million ways to do something.
Under the PHP section you claim
"if an included file must return a value but the file cannot be included, include statement returns FALSE and only generates a warning. If a file is required, one must use require()."
How is that a negative? That sounds like expected behavior to me and I can't imagine it working any other way. $error = include('script.php'); // Return FALSE and continue anyway.
require('script.php'); // Die with error.
How is that undesirable/wrong? It lets you do things like... $error = include('script-A.php');
if ($error == FALSE) require('script-B.php');I really want to know who thought this was a good idea, because I've only ever seen this behavior act as a footgun.
def foo(bazzes=None):
_baz = bazzes or []
...I use a linter to catch this sort of thing and subclass `collections.namedtuple(...)` whenever I need to at least try to abolish mutability. It's one of the worst things about the language.
[edit] You can argue that there would be use cases for mutable defaults, and as with any edge use case you'd be right in asserting its existence. Unfortunately, I can't think of any solution (typing, changing function argument evaluation rules, etc) that wouldn't fundamentally change the language and break just about every sizable python library in existence. This looseness is baked deep into the language as far as I can see.
- how does it know you're not doing that on purpose?
2) good point.
Also omitted:
my_dict.get('existing_key', foo())
does not short-circuits foo(). That always felt like a bug to me. my_dict['existing_key'] if 'existing_key' in my_dict else foo()
You could also define a `get_lazy` method that does the above, taking a nullary function as its key and evaluating it if the key is missing. But then you would need to wrap your default values in a lambda, which would look absurd for the common use case of calling `get` with some constant default value. Seems unnecessary when the ternary operator works fine. my_dict.get('existing_key') or foo()
This would also easily let you do multiple alternations (much more annoying with the ternary operator). print(player_data['score'] or 'does not exist')
This will print "does not exist" when the score is zero.Does Lua count?
Did they actually had the patience to write such a long article in a single file, or did they write it in multiple files, and then compiled it down to a single webpage for others to suffer?
"Everyone knows JavaScript, but not everyone use it by choice, giving you a higher than average ratio of people who both dislike and know the language."
The only people using Clojure are those who really like it.
From my end, the worst things about Clojure were the JVM (making installation/configuration hellishly frustrating) and the poor error messaging (making debugging the configuration extremely difficult).
Why?