Hacker News new | ask | show | jobs
by grahn 3328 days ago
The Go people don't have an aversion for generics, just a very conservative approach to adding language features. To quote their FAQ:

"Generics may well be added at some point. We don't feel an urgency for them, although we understand some programmers do."[1]

[1] http://golang-jp.org/doc/faq#generics

1 comments

Sounds like an aversion to me. By the time Go was developed, there was no doubt in my mind that generics (or some kind of polymorphism along those lines) were an essential feature for any new statically typed programming language. The fact that Go's developers "don't feel an urgency for them" to me makes it sound like they are living in the 1980s.
> were an essential feature

Do you mean, "this feature is required for me to write code in that language"? Or do you mean, "this feature is required for any project in the language to flourish"?

If the former, why do you think your preferences generalize? If the latter, how do you explain the large number of successful Go projects? Are we all stuck in the 1980s? And if so, what does that even mean?

Well, at the time I would have thought it was required for the language to become widely used. But since that's clearly not true I suppose I have to downgrade that statement to say it's required for me to write code in the langauge and not feel like I'm constantly banging my head against a wall.

Frankly, yeah, I think Go programmers are kind of stuck in the 1980s in some respects. This isn't something I'm completely clear about, but I feel like Go's developers are biased against anything that smells at all academic. So for instance they didn't want to implement a super fancy, cutting-edge type system. Which is somewhat understandable... but as a result they ignored 30 years of programming langauge research and implemented a primitive type system that basically provides nothing over C. I do not understand this mentality.

The payoff of the simpler type system is that people don't write posts like OP's about Go. The language has its rough edges, but it's not a brick wall the way Rust is -- you can learn the idioms and become a productive Go programmer very, very quickly. On top of that (or maybe as a related consequence), most Go code looks roughly the same. If you dive into the source of one of your project's dependencies, you aren't likely to find some esoteric or too-clever-by-half coding style. There are no fancy macros to untangle, because the language doesn't support them.

Writing that sort of clever, super-concise code scratches an itch that a lot of people have (myself included) but it's not something I want to encounter when I'm trying to debug something. When you're working with other people's code, you want it to be simple and consistent. That's what Go's primary strength is.

btw, I'll take this opportunity to plug my own "Go generics" solution: https://github.com/lukechampine/ply. It's like a Coffeescript for Go that lets you use stream HOFs like map/filter/reduce without any runtime cost.

I've used both Rust and Go daily for years (before they reached 1.0, respectively) and I'm generally happen with the experience that they give me. Not all languages need to have a type system as sophisticated as Rust's or Haskell's.

> but as a result they ignored 30 years of programming langauge research and implemented a primitive type system that basically provides nothing over C

As someone who also has a decent amount of C experience, I don't see how this is a reasonable conclusion to make. The first point in favor of Go is that it's mostly memory safe (sans data races), which is achieved not just with bounds checks, but with a stronger type system. The second point in favor of Go is that Go does have a limited form of polymorphism that is checked by the compiler. In C, the only way you get polymorphism is by subverting the type system entirely. Both of those points are huge, and there's undoubtedly a much longer list that I could craft of smaller benefits if I were so inclined.

As we march toward complex type systems that move more runtime errors to compile time, we must also be vigilant of the trade offs we're making. Moving things from runtime to compile time isn't necessarily free; there's almost always some new cognitive load that is implied. It's important because if that cognitive load is "too high," then people aren't going to switch to it, and no amount of navel gazing is going to fix that. This entire thread is a perfect demonstration of that trade off in action. On the one hand, we have the "clearly superior" Rust approach to error handling that checks a lot more at compile time than Go does, but on the other hand, Go programmers don't ever need to "learn error handling" at all. They have a simple convention with a reasonably low bug rate (IME, anyway).

We can't just judge programming languages by their theoretical strengths. We must also judge them by their practical strengths. And then we must do the impossible: balance them. Snubbing our collective noses isn't going to do any good. Our only hope is to understand why people are attracted to languages like Go (such as myself) and figure out how to reduce the aforementioned cognitive load without giving up those sweet sweet compile time checks. Rust is clearly pushing that boundary, and I'm happier for it.

>On the one hand, we have the "clearly superior" Rust approach to error handling that checks a lot more at compile time than Go does, but on the other hand, Go programmers don't ever need to "learn error handling" at all. They have a simple convention with a reasonably low bug rate (IME, anyway).

I don't think you misjudges the differences in complexity here at all. But wouldn't that have more to do with the different approaches to memory management?

>aforementioned cognitive load without giving up those sweet sweet compile time checks

I would argue the cognitive load comes mostly from nominal typing and lack of type inference. A counter example would be the Crystal language which has a very strong type system, but is exposing barely any of it as 'added cognitive load'.

This correlation between 'cognitive load' and 'expressiveness of a typesystem' seems unfair. Having to keep track of all the patterns that are valid but not supported by the type system is also a type of added (hidden) complexity.

Even the poster-feature of Rust has a (IMO) much easier cousin with the same (and more) advantages called 'the Clean programming language' in the shape of 'uniqueness types'. And most of the complexity in Rust is the result of the type system not being expressive enough for how they are attempting to use it, rather than the opposite.

> But wouldn't that have more to do with the different approaches to memory management?

What makes you say that? Rust's approach guarantees more type invariants.

I don't buy for a second that type inference has much to do with this. Firstly, Rust has type inference, it just isn't global. Secondly, I've found that type inference everywhere makes the cognitive load much worse, not better.

And I wasn't actually drawing a correlation between cognitive load and expressiveness. Namely, expressiveness isn't the final word. Much of PL theory is devoted not just to improving expressiveness, but making that expressiveness more accessible to the masses. Compare Haskell with System F, for example.

>And if so, what does that even mean?

The lack of generics in Go simply means that you will for common algorithms and functions end up with either of these scenario's:

1. You were forced to duplicate code under a different name with a different type signature (even though the body would be identical)

2. You are forced to use the 'empty interface' which negates the advantages of static type-checking (and seriously: why isn't it the default when you open an accolade?)

Not having generics in a statically typed language means you combine the disadvantage of static typing ('having to specify types and doubling the cognitive complexity of the language with a special type syntax') with ('not having the compiler be able to check the validity and risk run-time type errors, ie. things like cast exceptions').

>If the former, why do you think your preferences generalize?

Generics is not a platonic ideal such as 'object oriented programming'. Type systems are as solid as math itself. And just like Math there are multiple systems with different trade-offs in how expressive they are. This expressivity is not subjective! One type system can be strictly more expressive than another type system. Consider this pseudo-code without any type annotations:

    def example(a,n)
        return a.something(n)
In a dynamically typed language such as Ruby, Erlang, Python or JS there are programs that would call this method that would make it throw an exception during run-time. For example because it doesn't know how to do '.something' on the provided argument.

The challenge: It barfs during run-time and we want it to barf during compile-time.

Now imagine all the possible usages of this method in a dynamically typed language that would be provable correct. All the potential combinations of a's and n's that would not make it barf. How many of them are legal Java, Haskell, C# or Go programs? (some, most, some, few)? The goal of a static system is to barf during compile-time. Not to exclude valid, legal programs just because the language author thinks type systems are hard to implement.

Whenever you can write provable correct code, that a type-system is complaining about: how is that not a bug?

>If the latter, how do you explain the large number of successful Go projects?

1. The projects are successful because of the people who make them

Well for starters by not assuming that people who program in Go are completely incompetent drooling idiots who are only able to deliver working code because Go is so great. As if the same developers wouldn't have successful projects right now, if Go didn't exist.

2. Any language released by a famous language author at Google will get a large minimum cult following by default

Much like with Angular who releases something has a strong impact on the adoption, regardless of the actual quality of the product, language design or implementation. I'm convinced the same set of programmers would be more productive in other languages. I'm also not surprised they themselves aren't aware of it.

3. Go has nice competitive features, the type system just isn't one of them.

Finally, Go isn't all shitty, for example, it has a very nice IO and concurrency model and ever since it finally got a precise garbage collector, one could finally use it long-running processes.

4. Your logic has clear type errors that a strong type system would help you catch

The existence of successful Go projects isn't an indication of _anything_. With the same logic you could argue VHS is superior to Betamax or that that Kanye is a talented musician. It's like the Silicon Valley variant of religious logic of 'moral people don't get sick'. Sometimes people, projects or products are not successful because they did everything the right way! Sometimes they just get lucky. Sometimes they just plain conspire against civilisation and cheat. Sometimes the things they did correctly ended up being more important the the things they did wrong. Sometimes they just get to announce on a bigger stage than somebody else.

> Are we all stuck in the 1980s? And if so, what does that even mean?

It means that there has been a large research initiative since the 1980's in software verification but because science is hard and thinking is hard and we are all getting paid anyway, we rather prefer to cargo-cult, bike-shed and gloat about our own ignorance. Because what you haven't learned yet is hard and what you already know is easy, even if it's all wrong.

It doesn't help that most of the academic researchers don't care enough about engineering to turn their research into actual production tools (with a few exceptions).

But i'm starting to warm up to the common notion that all progress in programming language design comes not from people learning, but from generations of developers eventually dying of old age. There is just too much money to go around for developers to not act like spoiled ignorant little children, and as a result most technology is just fundamentally broken and for no good reason.

I think you kind of missed my point, and crucially, missed that I was specifically questioning usage of the word "essential." You very clearly read far more from my comment than was said or implied. See: https://news.ycombinator.com/item?id=14286020

Personally, I think a lot of the language you chose to use in that giant wall of text was quite unfortunate. I'm not interested in being a party to your ax grinding.

But Go does have polymorphism. It is achieved through the "interface" concept, which allows dynamic binding of any statically typed objects that match a given set of function signatures. In my experience, with the way it's been done it actually gets you pretty far in terms of problems you typically solve with generics in other languages.

That said, personally I'd love to have generics on top of that. Consequently, I have been following some of the discussions on the topic, and so far I haven't seen anything suggesting that the language developers have an aversion to it. What they do have, however, is a fear that an improperly designed generics concept could badly screw up the language in a way that can't be reversed by any practical means once it is launched.

They are basically very careful about adding stuff, without fully understanding the consequences at all levels. You (or I) can disagree with that approach, but it's not accurate to say that they have an aversion for generics, or even to say that they don't want it.

It is exactly the same kind of polymorphism that languages like Java and C# had before they adopted generics. In practice, what this means is that you have downcasts all over the place, which is tedious, non-typesafe, and is why both C# and Java have generics now.

The fact is that Go is the only statically typed language, with any claim to being mainstream, that doesn't have generics. And it's not like generics are some kind of a new and radical concept. Java had them for 13 years now; C# had them for 12. There's literally millions of lines of code written in popular languages that utilize generics, which can be used as a guide to proper design, and understand its consequences.

The idea that there needs to be more "baking time" for generics simply doesn't hold water at this point. It amounts to insisting that structured programming (loops etc) should not be adopted "without fully understanding the consequences at all levels", and meanwhile we'll just use if+goto - in 1980.

If you want to understand the reasonings the Go maintainers have about generics, there is a GitHub issue[1] on it that is probably a better place to start than to argue with me. The thread references both recent academic research as well as other programming languages' take on the subject.

I don't think their crux is so much that generics "as such" needs more baking, but the specifics of how to implement them with the Go language. Mind you that some of the core goals of Go is to be simple, easy to parse, fast to compile, support good tooling, etc. so the question they're battling with is how to add generics to that mix without sacrificing any of those goals, and without making some mistake you can never go back from once every code base out there starts depending on it.

Now, by all means, we can argue that those priorities are wrong, or that yours would have been different. But I think it is disingenuous to suggest that they are effectively idiots who don't understand how to apply basic concepts, or are unaware of other programming languages.

[1] https://github.com/golang/go/issues/15292

No-one is suggesting that they're idiots, though. The original argument is that Go designers are very conservative, and extremely averse to some language features that aren't even "new" anymore - not that they're unaware of those features or how they work.
> It is exactly the same kind of polymorphism that languages like Java and C# had before they adopted generics.

That's false. Go provides a smattering of blessed polymorphic types (slices, maps, chans, pointers) and functions (len, append, delete, chan send, chan recv) that go a long way. They are horrifying to civilized PL enthusiasts, but they cover a lot ground.

As I said in one of my sibling comments, navel gazing isn't going to get you anywhere. And Go isn't the only statically typed language without generics. C has that designation as well.

C is one of those things that hasn't meaningfully changed in decades now. So yes, technically it is a mainstream language without generics (and many other things) - but I don't think that's a viable role model. The only reason why C is what it is, is because of all the legacy code written in it.
Who said anything about C being a role model? I think your chosen language is somewhat misleading, so it's worth pointing out.