Go and Simplicity Debt
dave.cheney.net
dave.cheney.net
I don't know if adding generics to Golang is the right thing to do but I don't agree with how Cheney has framed it. I wish programmers would use a more precise mental framework about "simplicity/complexity".
It's not important if generics make the "language specification" more complex. What's really more important is if it makes the real-world language usage more complex.
What matters is the sum of total complexity: the language spec + adhoc idioms/patterns/conventions/workarounds used in actual codebases.
As an example, if you make a super simple language like Javascript in 1995 with hardly any features, what you eventually end up with is complexity in npm with dozens of modules for things like leftpad(). Or you can insist that "prototypes are simpler than OO" but you actually end up with a dozen OO-simulation libraries with different syntax out in the wild. Instead of the overall JS landscape being simpler, it is more complex.
This does not mean you must put everything language feature including the kitchen sink into the language. The lesson learned from history is to talk about about simplicity/complexity in a more holistic way.
I do not know of any studies that explore this concept at large. I would be interested in them.
1) there are no parameters on the container types, every usage involves a cast to or from 'void*', 'interface{}', or the equivalent, and usage is effectively not type-checked.
2) there are parameters on the container types, subsequent usage isn't cluttered up with uninformative type-casts, and the types get checked by the compiler.
These lead to the same code at runtime (or can, in a Java-style implementation of generics); however, the Go maintainers seem to act as if the first is more maintainable and less error-prone. That seems ... odd, to some of us.
(There are other possible implementations of generics, of course. In fact, another post by the same author, [1], lists "everything is boxed" as a bad outcome that he'd like to avoid, in favor of specialized implementations for containers of, say, characters. But scenario 1, the Go status quo, effectively requires that everything be boxed when using a generic container type written the only way the language allows you to write them. Scenario 2 at least potentially gives the compiler other options. But the Go status quo doesn't avoid the "everything is boxed" bad outcome. Instead it mandates it -- with additional compile-time clutter.)
However, if I had a penny for every time I've taken advantage of the generic collections built into the standard library, instead of using the old shitty, type-unsafe ArrayList or HashTable collections, I wouldn't have to work for the rest of my life.
.Net generics are more limited than full C++ style templates, or maybe I've been lucky enough to never encounter any code where people have gone overboard with them, but that seems to be a pretty sweet spot.
https://en.wikipedia.org/wiki/Waterbed_theory
Users don't often have the luxury of discarding essential complexity in the problem their trying to solve. So, if you don't give them the right tools to tackle that in your language, that doesn't mean they no longer need those tools. It just means they have to build them themselves first.
Why is it that folks keep trying to formulate this as "the egghead 1% vs the sane 99%?" Stuff is constantly flowing out of academia and advanced production labs and our industry re-levels to that. It's the way a growing industry is supposed to work.
Go is not a good language in a technical sense. It's an expression of Google's business needs. Google's got 0 interest in making rank and file programmers better; they're tuned for revolving door recruiting on a massive scale. For them, their goal is to make programmers who only have domain knowledge and require no training. They're not even that interested in lowering shipped error rates; they have a ton of machinery and a whole legion of SRE folks to help manage deployments when testing is minimal and hand-written.
To be clear btw, I am not condemning Google for arriving at this conclusion. Their economics are totally different from everyone else's. Every Big Company(tm) has a unique angle of attack on the training problem. Ironically, google's SRE folks are aggressively in the opposite camp in terms of recruiting and practice because they're the ones who actually have to enable this model of shipping broken code safely.
Everything about Go is designed to maximize what Google wants out of mid-experience programmers. It's so weird how non-google people seem to find this state of affairs good, as they almost certainly cannot afford to do it this way.
If it let's them build products that serve billions of users in ways that that were completely inconceivable even 30 years ago then maybe it's not such a bad tradeoff. To be clear I'm talking more about the philosophy that produced go, not so much the language itself. Obviously Google is more than go.
If you think that approach can work for you in your job and that job is not at once of the top 10 tech find? Write an experience report about how you made it work.
They very much have an org for generating software that treats domain specialists as pluggable domain optimizers. Architects and SRE all enable that.
Which are still a minority compared with the amount of Google products written in Python, Java, C++ or even Dart.
So, all the products were already conceivable in some 30 year old languages.
This is a great one sentence summary of the history of C++. (Would also do for a history of programming.) Any hoary Ruby veteran probably has a story about the misuse of missing_method. (Likely one in which they were the antagonist.) The way that the programming field has shaken out, it turns out that there is a group of programmers who are fine with application programming and using well thought out libraries. However, only a minority of these programmers have the ability to engage in the thinking about other-POV and 2nd/3rd order effects that are needed to make a good library or meta-level tool. (Not to mention the community management skills needed to keep people happy while not giving them absolutely everything they want.) As a relative noob, I was listening to hoary Smalltalk veterans talking about this in the early 2000's. This situation is pretty analogous to the one surrounding crypto, except that the consequences of hubris are somewhat milder.
The lesson learned from history is to talk about about simplicity/complexity in a more holistic way.
Also from the early 2000's -- The Design Patterns guys were often saying that design patterns and other kinds of common incantations were often symptoms of deficiencies in programming language design.
When I reread the GoF book a few years ago, I was struck by the number of patterns that are just built-in to modern languages.
The structural patterns are however much more a pain in fp while they feel natural in oo. Facades and decorators need type hiding that's only nice(r) with existential types.
The creational patterns are a mixed bag. Builder, singleton fit with fp, Abstract Factory fits with oo and factory methods with both.
What do you think? How are the oo patterns improved by other techniques. Though a lot of those patterns are probably for C style oo.
* Best-in-class compile times
* Modules; preprocessor is relevant, but not dominant
* Generics, variant records, function and operator overloads, exceptions, safe string and dynamic array types
* Libraries for common things, with a reasonably conventional style: APIs, containers, bindings, etc.
* No GC; pointers are available, but language features discourage casual usage. RAII pattern is achievable.
* Strong + static types, nominal types and type aliasing.
* Good IDE support, mature tooling, compiler has very broad platform support.
The new languages do win on a lot of the language-feature aspects(particularly memory safety when performing deep optimizations), but they don't have as good a story on the tooling. The compile speed is a major feature and is only likely to remain advantageous over time - and the features already in place are quite effective at limiting source code size, boilerplate, and dangerous constructs. This despite "Pascal is verbose" being memetic.
And if I want more high-level power, I've found that writing a FSM is the most probable path to achieving it. A FSM that embraces the problem domain is a tiny compiler: input source constructs, FSM checks them, applies appropriate algorithms, then emits a list of actions. More power? Add a second FSM to make it a two-pass compiler. Add a stack, or attach it to a database. If it has to be fast, it can generate code in the last step, too. Those things add tons of power to model the problem and aren't heavily dependent on language-side support.
On those days outside UNIX, C was just yet another systems language, with most home micros being written mostly in Assembly.
Also Turbo Pascal OOP features were adopted from Apple's Object Pascal, used to write the initial versions of Lisa, Mac OS and known software like Photoshop.
This is what he was talking about when he mentions "more complex, less readable software." On the other hand, he doesn't mention the language specification once.
You're making a facile argument without realizing it. For example, when you allow the ability to parameterize a thing, it can make the source code a little "less readable" while simultaneously reducing overall cognitive load.
Generics is the ability to parameterize _types_. As comparison, the language feature we are all familiar with is functions which allow us to parameterize _values_. Illustration of abstraction over _values_:
This is more readable (using technique of copy & paste):
fmt.Println("Hello World")
fmt.Println("Hello Jane")
The is less readable: func greeting(x string) (string) {
return "Hello " + x
}
func main() {
fmt.Println(greeting("World"))
fmt.Println(greeting("Jane"))
}
Programmers may not think the function with passing parameters isn't less readable (because they are used to it) but it actually is. An extra layer of indirection will make the source text somewhat less readable but it's worth it if the net gain in comprehension, maintainability, correctness, etc is improved.That's what parameterizing values via functions allows: it's simultaneously more complex and also more simple at the same time. It's complex at a superficial level but actually simpler at a deeper level. Programmers have embraced the additional "complexity" of "parameterized values" because it makes the language easier to use. A similar analogy about "parameterizing types" can also be made.
>, he doesn't mention the language specification once.
Yes, if you do Ctrl+F looking for the exact phrase "language specification", you're not going to find it. However, if you understand the meaning of the quotes I pulled, that _is_ what Cheney is actually talking about.
The main reason for that is that golang lists and maps CAN be typed and while they aren't perfect, they are pretty darn great for most use cases. Certainly I understand some people have the need for specialized data structures, but I wonder how often a project needs that data structure to be totally generic, as opposed to just doing the work to implement it (yes even perhaps copy/pasting some other implementation) and moving on. That's ugly yes, but it just isn't that big a deal in my experience.
In any case, the gains from having a great runtime that compiles to a binary, an excellent standard library and awesome concurrency primitives far outweigh the negatives for me. Really enjoy Go.
The sync.Map in Go 1.9 is yet another example of the price to pay for lacking generics.
I was doing that with Turbo Pascal 6.0 and Borland C++ 2.x for MS-DOS with Turbo Vision and TObject as interface{} in 1993.
If you are sharing a map in this way across many goroutines you are probably "doing it wrong" in go anyways.
You can still write your own specialized error-prone repetitive containers for each and every type by hand.
Yet, once a language has generics, people don't use the old way even though it's still possible to.
Perhaps that's because it has no place when there's a replacement which is less error-prone, less repetitive, more usable, and can be implemented once in a library and used forever.
Outside of that, agreed.
Two lines? I think you underestimate what's going on in sync.Map: https://github.com/golang/go/blob/master/src/sync/map.go
In practice, bugs and safety aside, this tends to work swimmingly. Look at Redis, or git, or postgresql, or the many other projects getting by just fine without generics.
While I agree, and believe generics offer unparalleled power, they often aren't remotely necessary. If anything, their presence creates a tendency to over-use them, resulting in even greater complexity.
It is still possible to travel by horse, yet people have moved on to better solutions.
Frankly, early templates based C++ code was not all that great either. It was the introduction of the STL that made a huge difference in the usefulness of templates.
> Look at Redis,
https://github.com/antirez/redis/blob/unstable/src/adlist.h — generic doubly-linked list with void∗
https://github.com/antirez/redis/blob/unstable/src/dict.h — generic hash table with void∗
> or git
https://github.com/git/git/blob/master/hashmap.h — generic hash table with void∗
https://github.com/git/git/blob/master/list.h — generic doubly linked list implemented with macros
> or postgresql
https://github.com/postgres/postgres/blob/master/src/include... — generic doubly linked list implemented with macros
https://github.com/postgres/postgres/blob/master/src/include... — generic red-black tree with void∗
"It works" is an encoding of, "I can write tests which perform correctly" and not "My code is stable in the face of aggressive input."
Go definitely follows along with C in this tradition. It's better than C, because you have slightly more safety interpreting memory as executable and in bounds checking. However, it does very little to stop destructive bugs or let programmers escape copy-paste code hell where regressions keep resurfacing because it's most expedient to copy-paste code.
Can you really set aside one of the core arguments for generics while discussing how it works?
How many exploits have been caused by the fact that C code tends to have its own data structures (including bounds checking errors)? I don't describe that as working "swimmingly", at least when discussing how things work in practice.
I think that only types for which equality is defined in the language could be used as keys. Those types where numbers, characters and all other "small" fixed-size types, structs all of those members had equality defined, arrays of a type with equality, and strings. The size of an array is part of the type, so in that list of types with equality the only source of unlimited sizedness are strings.
I guess that cover most cases where you want a type of unknown size as a key, but I found it pretty annoying. If you want to use, say, polynomials with integer coefficients as keys in a map, you'd have to either choose a fixed maximum size (degree, for example, or number of monomials) for the polynomials or convert back and forth between strings.
It's not the only language that forces you to convert things to strings and back for use as map keys (AWK and Lua come to mind), but I wish it didn't have that limitation.
As it stands, they are recipe for different camps and sub-camps of programmers to talk past each other endlessly.
But, of course, nobody will actually do this, because it would expose the inherent complexity of designs advertised as simple. Many people's feelings would be hurt in the process.
It can lead to some misunderstandings, but I think those are usually given more voice than they are worth.
Also ironically given everything I just wrote, I found that an odd mark for a footnote. I instinctively look up when I see the caret. Usually for a superscript, but not seeing one my eye kept going.
You mean simplistic conversations right?
The problem with being fast and loose with terminology is that it lacks precision; and with lack of precision comes ambiguity and misinterpretation, which beats the whole point of good communication.
So, if communication is hinged on highly specific meanings of words, the odds go way up that someone will not actually hear what you think you are saying.
Instead, keep conversations high level and do not rely on the specific meanings. It requires more thought from the listeners, in some ways, but it actually relies on less pre existing knowledge from the listeners.
It is tempting to think you have narrowed your audience down to non laymen. This is often an incorrect assumption, though.
And in writing, this can go out completely. There is a place for highly specific and very precise language. It is usually best along side the non-specific language.
That's just a cost of retrofitting a paradigm-changing feature.
Technologies aren't only popular because of their quality, or shall we bring PHP once again to prove this point?
> There is a reason Go programmers choose to program in Go, and I believe that reason stems from our core tenets of simplicity and readability
Not the reason I choose it, I choose it for tight cross platform binaries and nice stdlib. Arguments made against generics should be technical, not political... complexity may be subjective, but the justification for the definition can still be technical.
So yeah, I hate the we-must-be-right-look-at-our-adoption-rate arguments as well. :)
It may be that choosing Python for a customer-facing web service would only be done now because of inertia or risk aversion, but there's tons of new users who don't care about the GIL and who like Python for its pleasant language design, good libraries, and ease of interfacing with C.
PHP got popular because it was easy to use, and then because of inertia and path dependence. I don't think anyone was really happy to "grow with" PHP, in terms of gaining in expertise and creating increasingly complex code. I don't see this problem with Python -- leaving aside issues with concurrency, the language largely doesn't get in the way of writing quality code.
(1) People are always inclined to one or the other language, even the non-technical people. I am sure most of the HN audience knows businessmen who read 3-4 short articles and felt like tech experts. Or a junior dev friend of theirs told them "man, language X is awesome". And it picks up from there.
(2) Python is a pretty good language, there's no arguing that. And if you have a team where even not-very-technical people can code an easy script with it, there's a subconscious peer-pressure to choose it for your next greenfield project. It's easy to pick up even if you're new to programming. That's a plus per se, but us the Homo Sapiens always prefer instant gratification over sensible long-term investments. It's our nature. That's why I picked Ruby -- after running away screaming from the Java EE world -- seven years ago. (Now I regret that decision, by the way.)
(3) Many projects don't care about concurrency. Languages with GIL who are easy to pick up -- Python and JS being the prominent examples -- are bound to be popular. You're correct on that point, 101%.
(4) Python has good C-bindings with awesome libraries. Facts are facts.
(5) I've known people who ran away from Python. I am not making this an argument against the language itself, I am just giving you perspective on your statement that "one can grow with Python". Yes they can, some choose not to. That's not an argument in either direction -- not for, and not against the language. It's a natural phenomena with humans.
---
I'm seeing popularity in general only loosely bound to quality. Many actors, software technologies, hardware pieces, TV shows etc. etc. became popular because there was a pressing need for something in the area and that entity arrived in time to satisfy the need. From then on the human nature of being creatures of habit kicks in and much better alternatives go unnoticed for years (or decades) and the old stuff gets replaced only when it becomes blatantly obvious that it's inadequate, many times in a row.
It seems our human systems hate gradual and constructive change. It always has to be semi-cataclysmic changes.
I digressed. Please note that I am in partial agreement with you, but felt compelled to give another take of your points.
Focusing entirely on the language itself is a failure. Keep on eye on the language's users,
The introduction of languages that have generics designed in from day one is key. Even though Go doesn't (yet?) have them, the fact that its designers wrestled with the question from the start is an indicator that the concept has become central to programming language design.
C++ got templates 27 years ago. Java got generics 13 years ago. C# got them 12 years ago. Go was launched 10 years ago. I can't think of a single statically typed language with significant use when Go was developed that didn't have some for of type parametricity. Maybe C, but you could argue C users can simply opt into C++ if they want that.
The Go folks aren't risking being on the wrong side if they history if they don't add generics. They were on the wrong side of history when the language launched.
I do understand that generics are very difficult in an ahead-of-time compiled language where fast modular compiles are a priority one goal. I'm not saying its easy, at all. But there's no shortage of brilliant folks on the team. If they put their minds to it, they could solve this.
Maybe it was because they had the luxury of controlling the language and core libraries. They could make the types and functions they cared about -- channels, arrays, slices, maps, append(), etc. generic since they could bake them right into the language. It's really easy to not empathize with a user's problem if you don't experience it yourself.
C++ got templates during the time it was being designed.
Between C++ ARM and ANSI C++98, C++ compilers experimented with them, and its design reflected on the standard work.
Borland was one of the first PC compiler vendors to support templates, still on their MS-DOS/Windows 3.1 compilers, with the release of Borland C++ 3.0.
Microsoft Research already had a .NET prototype with generics support, by the time v1.0 was about to get released, but they decided it was more relevant to ship v1.0 than waiting for the generics support to be fully done. Don Syme from the F# team has a few blog entries about this.
One of the first languages to get generics was CLU in 1975, shortly followed by ML and Ada in the early 80's.
Until then, it was a moving target, with C++ compilers catching up with CFront and ongoing work at ANSI/ISO meetings.
Ada was standardised in 1983 and had enough generics support to be used for the early work of Alexander Stepanov.
There are lots of examples with generics support since the mid 70's.
> By the time this decade rolled around, Node.js and Go had arrived on the scene, highlighting the need for concurrency as a first class concept.
Again, no mention of Java? Seems like an odd blind spot.
And FWIW, libuv is also available in Python.
https://magic.io/blog/uvloop-blazing-fast-python-networking/ claims significantly faster than nodejs.
I didn't know this; thanks! I'm not familiar with Python internals at all (in fact, I had to look up what GIL was when I read the article). Seems strange, then, that the author would imply a contrast between Python and node.js (which I'm now realizing was your point to begin with). Thanks for being patient with me :).
> And FWIW, libuv is also available in Python.
> https://magic.io/blog/uvloop-blazing-fast-python-networking/ claims significantly faster than nodejs.
Yep! There are bindings for quite a few languages now. The only reasons why I conflate libuv with node.js are because libuv was developed specifically for node (as a cross-platform libev implementation) and because libuv comes with node by default.
Even though modern VB-programmers manage to deal with them just fine.
So pardon my French, but what's the fucking issue?
var typesafeListOfInts = new List<int>();
How is that hard?Ironic, no?
And that's not saying that something like Boost isn't an accomplishment in itself. But it's beyond hard to read, and when your language allows to write code like that, it's bound to appear in your codebase as well, and make maintaining it much harder than it needs to be.
Lack of parameterized types leads directly to code-duplication. How is duplicate code more maintainable?
> The problem is that once you give people parametrized types, they're going to parametrize the shit out of them, leading to byzantine messes such as the C++ standard library or Boost.
An extraordinary claim with no backing what so ever.
Just because C++ turned out terrible doesn't mean everything else has to. Look to C# and Java. They're doing just fine.
Also: how is littering your code with "interface { }" any less Byzantine than simply using "T"?
[1] https://grisha.org/blog/2017/04/27/simplistic-go-web-app/
While concurrency is nice, some citation is needed here. For one, Python is not going anywhere, and remains spectacularly more popular than Golang.
>But, no matter how important and useful templated types and immutability would be, integrating them into a hypothetical Go 2 would decrease its readability and increase compilation times—two things which Go was designed to address.
Well, they sacrificed compilation times to get a native compiler (which has no real utility to the users of the language, on the contrary it makes bootstrapping a little more messy).
Sacrificing it for Generics seems like a no brainer, but it seems they're not yet ready to bite the bullet.
As for "sacrificing readability" I don't think that having 30 lines of code just because you need to make it work with different types is any easier than having 10 lines of the same code with parametric types. Not to mention NOT having to write any code at all, because the standard library can finally have code (e.g for math, collections, etc.) that works across all relevant types. With Generics people could just reuse a few generic implementations of channel uses to handle 90% of their concurrency needs.
And it's also much better and easier to parse than using dreadful string templating kludges or interface{} to get the same behaviour.
I mean, the author mentions all those languages with generics ("Rust, Nim, Pony, Crystal, and Swift showed that basic templated types are a useful, and increasingly, expected feature of any language—just like concurrency"). Does he see the users of those languages complaining about generics being a burden?
Why do people always have to lament the terrible burden Generics will bring to Go in advance, when millions of us (literally, just add Java and C# programmers) just use generics in other languages without thinking twice about it?
There's a proverb in my country, that "He who'd rather not knead the dough (to make bread), keeps sifting the flour for days".
In programming terms it would translate roughly to: "He who is too lazy to build a bikeshed or doesn't like the prospect of building one, will keep on discussing the color it should give it".
Which I think is the case whenever anybody from the core Go team discusses Generics.
>As it stands now, generics or immutability can’t just be added to Go and still call it simple.
It's 2017. Neither are particularly difficult to grasp for a modern programmer. Even enterprise Java programmers have been using the former for a decade already, and they're not known for the wild experimental nature...
And it's worth looking at the reasons for that. Firstly, age is a factor - Python is much older.
However, most Python growth has come from data science and machine learning fields. There is no reason Go can not grow the pie and make such fields more accessible to those with an interest in computing concurrently. And surely, if there is one field where concurrency might be a boom it might be data science and machine learning.
Now, Go has some problems in that area: principally it does not have data structures as "friendly" as Python lists or NumPy arrays, and therefore people will have to jump through hoops. Could it get them? Yes. Would that be more powerful than generics? Absolutely. Will it get them? It's not obvious to me it will, other than through a third-party library which is fine.
In fact Python's successes might not be Python's. They might be NumPy's and Pandas' successes. Go can have the same thing, and do it better.
Those things are not very good indicators. Cobol and Lisp are even older, but not as popular. Perl is the same age, but has fallen sharply. Smalltalk is a decade plus older, but fell from grace in the mid-nineties. And so on...
Interestingly the reason they have not blossomed into old age is because they have exactly the opposite reputations: Lisp is hard to create but elegant when you have it right, COBOL is hard to replace, and inelegant wherever it lives on.
Perl was trendy but unmaintainable and often replaceable, so it was replaced.
Smalltalk lives on mostly through its ideas manifesting themselves in Ruby. It is still popular in certain fields, particularly those where "minicomputers" once reigned: business critical 9-figure turnover manufacturing sort of businesses.
Python has had longer to build traction, and so far has kept it - I'm arguing it might be about to pass.
I am not sure the numeric data types thing is such an issue.
Python supports int, float, long and complex types. So does Go, with the slight annoyance you have to care a little more about the size of your number, and so you don't just label something 'long' and walk away safe in the knowledge it's kinda big. Numpy just makes Go's numeric data types available. The clever thing it does that Go does not is around arrays and operations on them - something we need to consider in Go land, carefully.
The other stuff Numpy and Pandas makes available could be made available in Go, too and could abstract away the horrible stuff under the hood where you're resizing slices and creating multidimensional versions.
I think the moment multi-dimensional slices are available, you can do everything you want, and with concurrency and therefore able to trivially exploit multi-core systems - that could be the killer feature. Maybe.
As I understand it, Python's Global Interpreter Lock means that no matter how many threads and processors there are, only one thread is going to be executed at one time.
Certainly it can be hugely popular in spite of that shortcoming, just like Go can be popular in spite of the lack of generics.
Well, the author of TFA disputes that "certainly". He says that nowadays a language kinda MUST have good concurrency support, or it will lose users.
Which I don't necessarily agree with, but it's a totally understandable position. So I don't see how one can say they don't understand it -- at worse, they don't agree with it.
The sad part is Google supporting Java, Kotlin, TypeScript, Dart and C++ progress o one side, all with lots of people knowledgeable in language design and then there is Go.
Node.js illustrates the need for concurrency, but is even more single-threaded than Python.
Somehow, in spite of the GIL, Python has continued to increase in popularity, probably because the workarounds are pretty easy (use multiple processes or c extensions that give up the GIL or jython or...).
Threads are not the only route to parallel execution, just as templates are not the only route to generic data structures.
- list[type]
- delete should work.
- len and cap should work. (rather than .Len() )...
- range should work.
- What do we do for iterators?
I think it would make code that uses lists more readable/grokable. So you do get something in return for the debt you possibly took on to be able to implement list. In a sense that's also the way C++ trades things off, the library code is more difficult but the code using the library is simpler/safer.
Or, make the compiler more powerful and annotate the code in such a way that the compiler does way more upfront on your behalf...now this means you don't have to work so hard to troubleshoot code in production.
Personally, I'd rather work harder up front--even if it means a higher learning curve.
In my case,
var foo map[<type>]interface{}
solves 99% of issues where I would normally need generics.The author also doesn't really specify what kind of generics, there's a huge difference between parametric polymorphism (i.e. Haskell type classes) and Java/C++/C# generics.
The sheer rapid pace of development and simplicity has made Go our primary stack.
> solves 99% of issues where I would normally need generics.
But it also opens you to to runtime conversion errors, spurious if checks for type information compilers with generics have, or slow reflection to find the type.
> The author also doesn't really specify what kind of generics, there's a huge difference between parametric polymorphism (i.e. Haskell type classes) and Java/C++/C# generics.
Java/C++/C#/Haskell generics all provide parametric polymorphism though.
Haskell type classes provide ad hoc polymorphism, though I guess maybe they provide parametric polymorphism as well.
Per Wikipedia:
> In programming languages and type theory, parametric polymorphism is a way to make a language more expressive, while still maintaining full static type-safety. Using parametric polymorphism, a function or a data type can be written generically so that it can handle values identically without depending on their type.
They aren't strictly needed; but that is not much of an argument.
Having Maybe and Either available is a huge help. Those are examples of generic types I've really benefited from in Haskell, Scala, Rust, Kotlin, Swift... But of course I can code without them if I have to.
interface{} and run-time type assertions everywhere, probably.
A language is simple if it decomplects unrelated concerns. Which go does admirably in certain areas, but its absence of generics certainly adds to cognitive overhead and unnecessary complexity elsewhere.