Hacker News new | past | comments | ask | show | jobs | submit login
Generics enabled by default in Go tip (googlesource.com)
264 points by dcu on Aug 21, 2021 | hide | past | favorite | 365 comments



I like Go very much, a huge role plays its simplicity. In my career, I have been much more often bitten by having to deal with the complexity of a language then by not being able to do things, because a language feature is missing. That is, why I like Go so much. It strikes a great balance between important high-level features (GC, first class functions and closures) and still being a simple language (like Scheme is, in a sense they are very comparable in my eyes). The interface concept is great for covering a lot of cases. That is, why so far, I have not missed generics much and was rather reluctant to the constant wishes to add them to the language.

On the other side, there are cases, even if they are not very frequent, where generics do help a lot. Just the new slices package allone almost justifies the addition of generics. Furthermore, the proposal looks very much Go-like. As a consequence, I am quite looking forward to try them out and used responsibly[1], they can be a great addition to the Go universe.

1: I think the situation is somewhat similar to Lisp macros. Normally, a codebase rarely requires macros and readability is better, if you avoid creating too many macros. But occasionally there are situations, where using a macro tremendeously increases code quality. Both implementation and readability-wise. There macros should be used and it is great to have them available.


I roll my eyes when someone claims "I learned Go over the weekend". It's one thing to learn basic syntax, and completely another to learn the customs of your new environment so most can understand what you are doing.

Go is one of the hardest languages to learn. First of all, some concepts in it are very different from "mainstream" languages, and it takes a while to get used to them.

Simple things that exist in almost every other ecosystem can be absent in Go. Want to run setup code before your test suite? You on your own, buddy. And, oh yeah, 2/3 of your code will be `if err` statements, because exceptions are passé.

I learned it by seeking out good Go codebases, which are incredibly rare. Hashi's Terraform comes to mind.

Infamously, the early Kubernetes code was a nightmare because it lifted and shifted Java idioms and that was at the company that invented the Language.

Or at Uber, where they passed on Go's native goroutines and channels in favor of homegrown concurrency, for SOME reason: https://eng.uber.com/go-geofence-highest-query-per-second-se...

All this to show that Go is not a simple language to do well, so let's dispense with that myth.

It has its uses, but having done it, I would never recommend Go for a greenfield project in my place of work - there are very few rules that come with it, and it requires tons of coordination and discipline among a team. And if you have to work with 2 or 3 other people who have strong opinions on how to do things in Go, watch out.


I have not claimed you can "learn Go over the weekend". Not in a deeper sense where you could expect to design large projects and take the right decisions.

But you can get a pretty good basic Go intro in a few days and be pretty productive after that. It isn't that different from most languages and has a rather small core. Of course, it helps if you have Scheme experience, to understand all the power of high order functions etc.


IMO, “learning the language” and “getting used to it” are different things.

The first is a purely theoretical exercise, the second a applied one that involves not only the language but also its ecosystem (implementation(s), library of functions and tooling)


I am not sure if we are agreeing or not, but I feel like we are. I think when people refer to "learning" a language, they mean they actually know how to use it properly. Learning the syntax does not count - that is just trivia any one of us can do.


(Nitpick: I assume you mean learning both the syntax and the semantics. That isn’t always trivial, as some languages have features one may not have seen before)

I think we mostly agree, too.

However, I distinguish between learning a programming language and learning to use it. I learn many programming languages without ever writing any line of code in them.

Once you’ve seen enough languages, if you combine that with reading about other people’s experience with working with the language, that can give you a decent idea about how the latter is without having to spend the effort (without being as good as doing it yourself, of course. Apart from “which one is the brake pedal”, I think I know how to drive a car, but I also know I can’t drive a car)

That’s why I posted my first comment: the poster you replied to may share my opinion between “learning” and “learning to use”.


I used to really hate Go's attitude to generics and type system features in general. I still do, but I appreciate that it's a matter of taste, and that different people have different tolerances for language complexity. People who like simplicity can use Go and people who like type systems can use Rust.


Weird to think of Rust as an alternative to Go imo. C# and Java are more similar to Go (mostly due to GC), but with generics etc


Super excited for this. If you haven't checked out the latest proposal, here's an example of what Option/Result box types might look like (obviously not ready for release, just an experiment):

https://go2goplay.golang.org/p/krvTH1_7lwX


> Viewing and/or sharing code snippets is not available in your country for legal reasons. This message might also appear if your country is misdetected. If you believe this is an error, please file an issue.

Wow, interesting. I'm in Japan.


Shame as a species we can't even share information (literally bits on the wire) globally, even if tangible goods and movements are circumscribed by political borders.


Works for me (also japan)


Result[T] plus a ? operator to provide concision would be a huge win for Go.


Each Unwrap() call needs two OK() calls? Letting Unwrap return OK would be better?

BTW, the readability is really not good as Go 1.


As it rightfully should. God forbid people start returning Result[T] types instead of (T, error) and splintering the ecosystem.

Option types are pointless if you don't have exhaustive pattern matching to ensure correctness.


Go's type system is actually expressive enough to ensure exhaustiveness, although it's non-obvious and syntactically heavy and impractical. I am working on a paper that will explore this.


> Each Unwrap() call needs two OK() calls? Letting Unwrap return OK would be better?

That would rather miss the point as you'd be converting the type-safe result to an unsafe version?

Go doesn't have pattern matching or any "structural" construct which would allow performing this in a single step, so the alternative would be to use HoFs and while Go has them they're quite verbose e.g.

    func (o Option[T]) Then(then func(T)) {
     if o.OK() {
      then(*o.value)
     }
    }
    a.Then(func (v int) { fmt.Printf("\tvalue = %d\n", v) })
which is… less than great.

An safe other option would be a method mapping to a slice of 0..1 element and you'd use a `for range` as an `if` but that seems way to clever, similar to Scala's comprehensions over everything which I rather dislike.

Also sadly the current proposal has no facilities for methods generic in their arguments independently from the subject, so no `Map` method.


This seems somewhat strange to me (and I don't just mean hard on the eyes):

    func (o Option[T]) Unwrap() T
How does Go know that T is a type parameter here, and not a concrete type named T?


Reading the specification (https://go.googlesource.com/proposal/+/refs/heads/master/des...) this flows from the type being declared generic, Go will (currently at least) require the receiver to be similarly generic, no specialisation, and no method constraints:

> Generic types can have methods. The receiver type of a method must declare the same number of type parameters as are declared in the receiver type's definition. They are declared without any constraint.

    // Push adds a value to the end of a vector.
    func (v *Vector[T]) Push(x T) { *v = append(*v, x) }
> The type parameters listed in a method declaration need not have the same names as the type parameters in the type declaration. In particular, if they are not used by the method, they can be _.


Because it can only be a type parameter. There is no specialization. (TBH I think that's a mistake because if prevents a solution to the Expression Problem.)


I don't think that's an issue, specialisation is a real edge case (e.g. Rust still doesn't have userland specialisation despite having always had generics) and it's easy to make mistakes with it (e.g. C++'s `vector<bool>` though it's probably less of an issue if you can only specialise functions).

That methods can't be parametric is still annoying though because it means utility methods can't have generic parameters unrelated to the subject e.g. can't define a map or fold method, because there's nowhere to put the output type, it has to be a free function instead.

But it's not like adding the generic spec thing between the subject and the method name would be a big issue so that can always be added later after the MVP has been exercised a bit.

A related "missing bit" which I'd guess they want more feedback before adding would be "conditional methods" aka methods which are only present if the generic types of the subject have a specific property (e.g. in Rust it's common for the generic container to be unbounded but for functions to be bounded on specific traits).


Adding it later is not impossible, but unfortunately is not that easy either. Ignoring the very high bar for changing Go in general, doing it this way has implementation consequences that would have to be undone. Also the spec change is not just a matter of adding more stuff, but it requires reformalization of the existing semantics because interfaces are already rank-2 types, you just can't see it yet. This feature interacts with interfaces.

It's very unfortunate thay didn't do this from the start. The original paper that introduced Go generics had this and used to to solve the Expression Problem.


Without language constructs for avoiding the pyramid of doom (e.g. Haskell do-notation) I think this will end badly!


Result isn’t an enumeration of sorts?


No, Go doesn't have discriminated/tagged unions or valued-enumerations or whatever you want to call them, but generics let you hack them in without much trouble. Personally I'd take ADTs over generics any day, but it's not a dichotomy maybe we'll still get them some day.

https://github.com/golang/go/issues/19412


I guess I'm understanding some of the concerns. It does make the code harder to understand. I wish they would use the angle brackets to match other languages.


This is actually my first week using go (from years of C++/Swift/js/etc) and I’ve been very impressed with the module system and simplicity so far. I’d definitely encourage others to try it if they haven’t.

As for generics, Go’s lack of function overloading and arg default values is really interesting. It ensures that a function is always easily found as it’s the only thing in the module with that name. I’ll be curious to see if generics are easier to follow without function overloading. It will still be only in one place, where as in C++ you could have hundreds of functions with the same name across many libraries and you just have to hope your ide knows which to step into.


> As for generics, Go’s lack of function overloading and arg default values is really interesting. It ensures that a function is always easily found as it’s the only thing in the module with that name. I’ll be curious to see if generics are easier to follow without function overloading.

There are already a number of langages with generics and without overloading (or defaults). Haskell, ocaml, rust, …


True of OCaml and Rust (and many others), but Haskell certainly has overloaded functions through type classes. In fact, once OCaml gets implicit modules, this won't be true anymore of OCaml either.


> True of OCaml and Rust (and many others), but Haskell certainly has overloaded functions through type classes.

That's no more overloading than Rust has through traits.


It absolutely is, because class methods in Haskell are top level declarations. Not only this is not true in Rust, but it doesn't even matter as Rust doesn't have polymorphic values, only polymorphic types and functions.

In Rust, unlike in Haskell or ML type inference stops at function boundary.


Not excited about this feature, I guess we'll see how frequently it shows up in unwanted places. Generics in C++ really damage the readability of the code sometimes, maybe the go devs have found a better way.

Very skeptical, but the go devs have given me plenty of pleasant surprises before, maybe we get another one here.


The key to readability is programming with readability in mind. Sure, languages play some part in that, but 90% of it is the programmer. Go is simple right now, and generics do complicate it, but even with the simplicity that Go has, people already make a giant mess of it. As soon as you get any advanced feature, people will abuse it -- colossal generic functions that take 5 interface{} arguments (but only ever operate on strings), one-off interfaces that have 35 methods, calling out to cgo just because they can, and giant tangles of goroutines, mutexes, semaphores, and sync.Pools all mixed together into one big ball of sadness. But, while everyone has the ability to create such a disaster area, some opt out and use the tool correctly -- making the advanced language features an asset instead of a liability. I don't think generics will change this; when used sparingly and at the right time for the right reason, it will make the code easier to read.

I did a quick look through all my open source projects to see where I have accepted or returned an interface{} in my own APIs, which is a strong signal that I'm looking for generics, or am using a library that wants generics.

One case is interacting with Kubernetes. I have two applications where I set up a reflector like `cache.NewReflector(lw, &v1.Node{}, store, resync)`, to keep a cache up to date; the implementation of "store" takes interface{} for all the arguments (Add, Delete, etc.), even though it only ever operates on a v1.Node. Generics will let me ensure at compile time that I only have to worry about v1.Node objects. Right now, that's handled with runtime assertions in the reflector itself, and in every implementation of the cache.Store. Generics clean this up.

Another case that comes up is processing random JSON objects from the Internet with no defined schema. Those end up as map[string]interface{}, and that won't change.

The last case is unmarshaling functions. I see some (in a JWT validation library) that are of the form `func Unmarshal(string) interface{}`. I don't really know what's going on there, and I don't know if generics will help. (Similarly, for the common case of unmarshaling JSON, I'm not sure generics can improve the `err := json.Unmarshal(bytes, &result)` API. I will have to look into it.

That's about it.


> The key to readability is programming with readability in mind

Imo Golang buried that theory. I’ve read a lot of Golang code and it’s always been the clearest and most readable code I’ve read.


The reason that people find Go to be so much more readable than anything else is because there's often only one way to do the thing you want. The entire language spec is like 2 pages long, and the standard library is relatively thin - generally there are not cases of two libraries that can be used to accomplish the same goal.

This has the general effect of making every piece of code familiar, because every piece of code is forced to use the same ideas to solve adjacent problems.

The foil to this is javascript, where you've got a bunch of different ways to set up functions and promises and async and threading and communications and error handling, etc. Javascript is a very flexible and high level language, but that also means everyone solves problems in their own unique way.


Even I know (not really being a heavy C++ user) that C++ doesn't have generics, it has templates, which is a much more powerful concept that's often overused.


> Even I know (not really being a heavy C++ user) that C++ doesn't have generics, it has templates

So… you know wrong?

Templates are a form of generics. What templates are not is an instance of parametric polymorphism.


Generics in C++ really damage the readability of the code sometimes, maybe the go devs have found a better way.

The culture might help. I think generics in Java are not overused -- partly because they are not so powerful and partly because Java, similar to Go, did not have generics for a long time.


C++ also went a long time without generics, and even when support started to appear it was a separate code generation step, requiring even more time before we saw first-class compiler support.

Powerful, though.


If you're really concerned about "clever developers", maybe you ought to ask for the removal of reflect and ast, because they've both been used to be way too clever about solving the same issue.


Correct. People will find a way to get rid of the verbosity and you either give them good tools to do it or you end up with a mess.


Generics introduce complexity. That’s pretty undeniable. There are also cases where generics provide much better solutions to problems. That is also undeniable.

As someone who’s working in a Go codebase at work, I’m happy they added generics, especially in the way they did. It’s a pretty minimal subset of generic functionality. I will apply it in certain cases, mostly to reusable code. I think it’s a big win overall though, without introducing too much complexity to the language.


Wow, it's happening! For others completely out of the loop on that there was even an accepted proposal that is now being implemented:

https://go.dev/blog/generics-proposal


Some related past threads:

Golang generics proposal has been accepted - https://news.ycombinator.com/item?id=26093778 - Feb 2021 (168 comments)

Go generics proposal moves to “likely accept” - https://news.ycombinator.com/item?id=26018649 - Feb 2021 (92 comments)

A Proposal for Adding Generics to Go - https://news.ycombinator.com/item?id=25750582 - Jan 2021 (270 comments)

The Next Step for Generics - https://news.ycombinator.com/item?id=23543131 - June 2020 (664 comments)

Generics in Go with Ian Lance Taylor (2019) - https://news.ycombinator.com/item?id=22361089 - Feb 2020 (98 comments)

Why Generics? - https://news.ycombinator.com/item?id=20576845 - July 2019 (254 comments)

Go's updated generic proposal (Contracts) - https://news.ycombinator.com/item?id=20541079 - July 2019 (61 comments)



As an outsider planning to eventually learn Go, I have two questions:

1. what's "Go tip"?

2. does this allow us to approximate when it could make it to one of Go's stable releases? I guesstimate it's something that would land not sooner than within a year, right?


The "tip" is the head of the development branch.

The plan is to have them available as a preview feature in the next stable release, which is scheduled in about 6 months time.


those aren't generics they're from the canadian aboriginal syllabic block


For anyone who doesn’t get it, https://www.reddit.com/r/rust/comments/5penft/parallelizing_... was a pretty funny workaround.


Language flamewars on internet forums are... strange.

Why have we all so strongly coupled our identities as programmers to the language we use? Sense of community and a perceived need to defend it?

I don't think Go needs generics, but I'm not about to invent obscure edge cases to justify for/against the idea. That's a recurring theme in all defenses of any language. It's not helpful.

Use Go if you like the "clarity", stay away from it if you don't like the "verbosity".

These are both valid points, but they're subjective. Pushing it as fact is dishonest and self-serving, as well as eventually detrimental.


> Why have we all so strongly coupled our identities as programmers to the language we use?

Because the viability of a language is linked directly to its ecosystem and big names using it, so people that want to use one language have to promote its use if it's not in the top 5. You don't see many people advocating for Java, but some people are advocating for C#, Kotlin, Scala. You don't see many people advoating for JS, but many people are advocating for Elm, TypeScript, ReasonML.

On the other hand, people that are "forced" to use a language want to bring features from other languages they'd rather use. You can see in this graph https://go.dev/blog/survey2020/missing_features.svg (from https://go.dev/blog/survey2020-results) that people that want Go to change want to bring features from ML, Java, Rust, etc. Other languages. Because they have to work with Go or want to work with it but want to bring what they already know with them.

Lots of people spend 8 hours a day using the programming language that their company chose. Adding a few things that they like to it seem like a good way to make their life better.


How is it strange? It seems completely natural to me. A programming language is a tool that software devs have to use for 8 hours a day. And even more annoyingly the individual software dev generally doesn't get to decide what tool he gets to use. That means the top programming languages are always under a strong pressure to adopt feature X from Y devs favourite language. Now Go is quite an interesting case I think. It's essentially an ancient language with good tooling support backed by one of the largest companies in the world. Go has now yielded to generics. I don't think Go is capable of becoming modern because the initial design decision have essentially made that almost impossible. I wonder though, what the next feature for Go demanded by the community will be.


There is no doubt that Go needs generics. Pretty much every Go container and data structure library on github will benefit from generics. The empty interface is used everywhere, leading to all kinds of panicking typecasts, hard to debug errors, and inefficient conversions. The vast majority of these uses can be made type-safe, more efficient, and clearer with generics. There is nothing subjective about the benefits of generics. Together with type-safe enumerations I miss generics the most from Ada.

To be honest, I'd use Ada if it had a significant infrastructure, but it feels pretty much dead. With Go I can at least get 3rd party libraries, as long as I check the source code to see if it's good enough.


There's some pain in writing generic data structure algorithms for sure. It would have been an interesting experiment though if the Go authors would have tried to improve the tooling around code generation and making it easier to use, as an alternative avenue to generics as code generation might satisfy some of the cases likely to trigger those complains.

Anyway, interesting to see how far they will take the generics in the path to Go 2.


It isn't as if previous languages did not used code generators before generics were a thing on them.


Yea but, it has never been a particularly popular approach.


It surely was in C, C++ and Java.

Borland BIDS 1.0 used such approach with pre-processor tricks, similar to many C projects do as well.

Java, well EMF was a common tool to generate such boilerplate, Velocity was another one.


Might be dead for FOSS, but still has 5 compiler vendors around and a new standard revision being done.


For me it's about paradigms the language represents. I try to extend the simplicity of golang all the way to the network architecture. I've learned that it's better to build many very very simple things and connect them. Not for any technical reasons, but because it allows other developers to quickly begin adding to and fixing your systems. You get to leverage the large amount of mediocre programmers when your systems are simple instead of looking for heros. It's definitely easier said than done though to make systems naturally intuitive and simple.

I've also learned that in most cases you don't need to invent some giant application. It's far easier to create extensions or wrappers to existing operations or service tools, with a bonus (again) being easier hiring and delegation. You can hire someone who knows a standard piece of tech and quickly ramp them up on a small extension or wrapper that you wrote. Golang is the language that made me always take this simple approach first and look for something that does 80% of what I need and think about problems very generically.


> I've learned that it's better to build many very very simple things and connect them. Not for any technical reasons, but because it allows other developers to quickly begin adding to and fixing your systems. You get to leverage the large amount of mediocre programmers when your systems are simple instead of looking for heros. It's definitely easier said than done though to make systems naturally intuitive and simple.

Creating a bunch of microservices and connecting them has just moved your complexity from the "monolithic app" kind to the "integration" kind. It's much harder to understand the potential impact of your changes when "anything" could be using a service via the network.

It's a trade off here. If you have integration complexity, you need integration heroes.

> I've also learned that in most cases you don't need to invent some giant application. It's far easier to create extensions or wrappers to existing operations or service tools, with a bonus (again) being easier hiring and delegation. You can hire someone who knows a standard piece of tech and quickly ramp them up on a small extension or wrapper that you wrote. Golang is the language that made me always take this simple approach first and look for something that does 80% of what I need and think about problems very generically.

This is a great idea. Leverage industry experience when and where you can. Minimize the amount of "tribal knowledge" that your team requires to be effective.


Where did parent mention microservices? NSI (No Snark Intended).

(edit) A parody about those: https://www.youtube.com/watch?v=y8OnoxKotPQ


I gathered the bit about microservices from

> I try to extend the simplicity of golang all the way to the network architecture.


That's a fair take from my words but it wasn't the intent. Really I just meant stick with consistent hashing, queues and the like. I guess really the "leverage existing tools and patterns" would have covered it sufficiently. I'm by no means a micro service fan. It reminds me of people abusing containers the way people abused the nosql movement. It has its place. I think we agree at the end of the day, and it's difficult to put into words.


I think this derives from the fact that a fair amount of programmers are told what language they will write in and are forced to adhere to the languages semantics even if they liked other language semantics.

The force part comes from a desire for business continuity and efficiency. If you're a large scale Go shop there's a fair amount of specialized infrastructure needed including a goproxy to prevent leakage, some apparatus to view documentation, and probably educational resources.

A large scale Java shop needs extensive artifact storage and a place to host generated Java docs.

If you employ both languages your infra cost is now going up for something a business sees as pure overhead rather than the conduit for innovation. In my experience this leads to language supremacy discussions, bickering over scarce funding, etc.

On my team, we're polyglot. Everything, including docs, are generated and self-hosted internally. We make the best of a brisk situation. What lets me sleep okay at night is that developers have a choice to use the best tool (language) for the job, not just the one that a company hired for ten years ago.


When I started programming in Go after a decade of Java[1], I found it painful to not code without generics but then over the past couple of years I've become used to it.

I understood the philosophy of Go and why it didn't have generics or may be I'm just convincing myself of that because I'm invested in it. I think when you accept the philosophy of something you already get coupled into it.

I don't really have an opinion about Go bringing in generics, I would use every time saving feature available in a programming language. On the other hand I really hope additions like these doesn't become a point of friction among those who really have an opinion, Especially those who have been contributing to Go and end up forking Go.

[1] https://abishekmuthian.com/i-had-to-let-go-of-java/


Go has generics. They're just not (yet) available for you. So to some it feels like a Tantalus punishment.


That's not what anyone means when they say that a programming language has generics.


If you're referring to map[K]V, that's not true. Go doesn't have generics, it uses some compiler magic under the hood specifically for the map type [0]. The generics proposal is being implemented from the ground up.

[0]: https://dave.cheney.net/2018/05/29/how-the-go-runtime-implem...


> If you're referring to map[K]V, that's not true. Go doesn't have generics, it uses some compiler magic under the hood specifically for the map type [0].

They're not generic (aka userland) generics, but they're still generics: parametric types, and functions able to work on them generically (you don't have a separate `append` function for every type you might put in a slice).

> The generics proposal is being implemented from the ground up.

Of course it is, the builtins are ad-hoc and half-assed. That doesn't mean they ain't a thing.


> the builtins are ad-hoc and half-assed

In what way are slices and maps half-assed?


I’m sure they mean that because they are not applicable to the rest of the language. It’s like, the Go creators recognized the situation where generics were necessary, but only used it for slices and maps.

I really don’t think they were saying that slices and maps themselves are bad. They function perfectly. I’m sure they meant that it’s just silly to confine quasi-generics to those 2 places.


> it’s just silly to confine quasi-generics to those 2 places

Why is it silly?


Human nature though. When a topic is extremely complicated, or there’s some kind of power to be gained, we resort to tribalism. Both are present in software.

We’ve made societal progress on a lot of things, but not so much on this one.

For me, I always try and be conscious of that, and I tell coworkers when something that I’m suggesting is just preference over objective fact. For example, I prefer Ruby as a scripting language. My first job used it for various automations, and I got used to it. It has absolutely no value over Python as a scripting language for example. It’s just my preference.


I've never used Go, and from the outside I take a lot of issues with its design choices.

But even without having used it I always thought the lack of generics was very interesting and I could see how it was desirable. It's fascinating to me that Go has gotten as far as it has without them (proving that it's possible to), and the mindset shift people describe having around them seems like a really important thing to pay attention to.

One thing I'm curious about: user-defined generics I can see going without, but the core language and standard library surely need to have them. How does that work in practice? Is there a syntax for using them and just not one for creating them? How are they defined in standard library code?


There are the usual operators and special functions like append(), which is implemented in the compiler and works on arbitrary slices. Not many of them, though.

They are defined in the builtin pseudo-package: https://pkg.go.dev/builtin


> How does that work in practice? Is there a syntax for using them and just not one for creating them? How are they defined in standard library code?

They’re special-cased in the compiler, with bespoke implementations.


> It's fascinating to me that Go has gotten as far as it has without them (proving that it's possible to), and the mindset shift people describe having around them seems like a really important thing to pay attention to.

Yes, the mindset of the people was certainly a key part of this, but another key part is that Go has interfaces, which are existential types, which are dual to universal types (colloquially called generics). If the type system is expressive enough (System Fω), you can express one in terms of the other. Of course, Go's type system wasn't expressive enough to realize the true duality, but it was expressive enough to express many things people would have used generics for in other languages.

Other languages which don't have generics don't have existential types either, and people see Go through that lens and don't understand how we got anything done. Well put on some existential glasses.

As an aside, one of the reason it was so hard to develop a good design for generics is that they inevitably interact with interfaces, precisely because of the duality above. Unfortunately, the non-language experts who complained on web forums about the lack of generics in Go for the past 11 years do not understand what that means, but that didn't stop them from complaining. Let me put it another way. There's a reason why Rust traits are not types, but Go interfaces are types.


> Yes, the mindset of the people was certainly a key part of this, but another key part is that Go has interfaces, which are existential types, which are dual to universal types (colloquially called generics).

The biggest reason was probably that it had bespoke generics for a bunch of core collections, significantly mitigating the need for userland generics.

Without the builtin generic slice, map, and channel, the issue would have been significantly less tenable on both efficiency and convenience fronts.

It's not like "we have interfaces and we still need generics" is a new thing.


This argument is often repeated but doesn't make any sense.

The value of generics is quantifications over types. In particular univeral quantification. "Generic" slices don't give you that, because the values are "generic", but there are no non-static type constructors. Go slices are "generic" only in the same sense than arrays in C are generic, or (if we go to the degenerate case) that a variable is "generic" because you can declare it with any type. I'm sorry, what?

Go has existential quantification, something which C lacks. Go is very unusual that it started with existential types. Languages based on System Fω have universal quantification as a native operation and implement existential types as higher rank universals. The fact that Go had existentials from the beggining is not some technical nitpick, it is what gives it type-level expressive power over languages like C. Ignoring this in arguments about generics is missing the point completely.


Java didn't have generics until version 5. They were added 8 years later.


Java didn't have user-defined generics, but it did have parametrized builtin types: arrays. Same thing for C#.


c# was also released without generics, they wanted it out the door and the generics were not ready. They landed in 2.0.


It is available in Go 1.17 with a flag, use:

    go run -gcflags=-G=3 foo.go


The support in Go 1.17 is very incomplete. A lot of work has been done in a branch (since merged) after the 1.17 freeze.


I'm not a Go user; I don't consider it serious as a programming language. True, it has great tooling. It seems perfectly designed for certain groups of devs to bang out lower-level tools and utilities, and they love it for being such a great fit.

That said, I think some of them may have a point that Go shouldn't actually try to tack on generics now. Consider that generics are only one piece of the puzzle when it comes to programming language power and abstraction capability. There are also ADTs, abstract types, pattern matching, immutability, lack of null, expression-oriented syntax, etc.

Go with generics would be only one step along that journey of abstraction power. It would help the set of people who desperately need it to solve their code repetition and type-casting issues. But it would solve these problems just to clear the way for them to reach the next set of problems on the journey of abstraction.


Not entirely getting the hate/hype towards generics. What about run-time reflection and annotations, arguably an addition with more drastic consequences, and all things considered, not for the better IMO. Has anyone an opinion on using GraalVM/Java vs Golang?


Pretext: I love Go and write most of my day job code in it.

To the people moaning about how generics will make their favourite language as awful and ugly as Java: all of the libraries and techniques you like and use today will always work. Little things like sorting will use generics pretty much transparently. All of the strongly typed code you write today that receives and returns concrete types will be just as valid tomorrow as it is today. Interfaces keep the same semantics and are a core part of how generics work.

I think it’s important to remember that the people who design this language like it for the same reasons you do.


> To the people moaning about how generics will make their favourite language as awful and ugly as Java: all of the libraries and techniques you like and use today will always work.

In fairness: just because that's true doesn't mean the libraries they like or need won't be migrating to generic facilities one way or an other, so they may be forced to the choice of interacting with generics or needing to reimplement their wheels (though also in fairness that's also common in the go ecosystem).

> I think it’s important to remember that the people who design this language like it for the same reasons you do.

That's not necessarily true, the designers of the language are not necessarily designing it for their use or like. See: Rob Pike's well known quotes on the target population for Go.


Finally catching up modern times!

Welcome to 1976.


Go needs custom generics for sure. But it is really a hurry-up to support custom generics for Go in version 1.18. Many problems have not been resolved yet.

* should the builtin generics syntax be compatible with the new custom syntax

* how many problems will be solved by the custom generics and how much complexities will be added. Is it a good balance?


The second question has been extensively answered by numerous mainstream languages adopting generics in the past, oh, 30 years or so?


Not to be contrary for its sake, but I'll say this is one change I'm really not happy about.

I feel like it's a change to placate many, while driving a lesser amount away. Which is fine, but still feels like the end of something, as I am one of the aforementioned 'lesser.'

As for why...I love above most the simplicity and readability of Go. Any change which encroaches that, which this does, is a net negative to me.


Back when generics were introduced to Java I felt the same thing. I had been doing Java for years at that time. I had also been working for Sun, so of course I was exposed to it.

I was very negative towards generics and had similar arguments as you. It didn't take long before I changed my mind. I'd never want to write Java without generics again.

I'm not saying you're going to have the same experience, but I would suggest you give it a try before dismissing its value.


Thanks for your experience, really. Here's hoping.


Does Go generics have type erasure? If not, OP can expect and even better journey.


There are two meanings of "type erasure". One concerns the user, the other the compiler writer.

When most users people ask about type erasure, they ask whether types are erased when compiling the reflection information for polymorphic values (like Java does). Go doesn't have polymorphic values, so the question doesn't apply.

Type erasure means something more general than that to compiler people. Type erasure refers to whether you can compile code while erasing its type, and it's generally a useful property. In general statically-typed programming languages are implemented with type erasure, while dynamically-typed languages are not. Of course, since Go has reflection, it can't do type erasure. Interfaces also conflict with type erasure somewhat, though as expected, during the code generation types are erased as much as possible.

As a comparison, Rust does type erasure fully.


So Haskell has type erasure only for data types that do not derive Typeable?


No, because all types have a Typeable instance! Haskell has type erasure, and then using Typeable adds the type information back exactly where it is needed.


No, they don't have type erasure.


Go is getting monomorphization, separate machine code for each type specialization of a generic function. I assume reflection will choose the one for the args you want to pass, or make you do that.


> I assume reflection will choose the one for the args you want to pass, or make you do that.

According to the spec:

> It's impossible for non-generic code to refer to generic code without instantiating it, so there is no reflection information for uninstantiated generic types or functions.

You won't be able to reflect a generic List or List[T] at all which… makes sense, I think? `reflect` works on values at runtime, so it necessarily works with instantiated types as you can't have a value of an un-instantiated (generic) type: what would you give to `reflect.TypeOf` which could return an uninstantiated generic type?


I was thinking of generic functions rather than (non-generic) methods on generic types. Unfortunately it looks like reflection can’t look up functions in a module at all, which is surprisingly broken.


> Unfortunately it looks like reflection can’t look up functions in a module at all, which is surprisingly broken.

The more time passes the more I think ubiquitous reflection in statically typed languages is what's broken (and a reflection (heh) of deep limitations of the language).

So while I could understand having issues with generics breaking existing reflection code (which doesn't seem to be the case), I'm not going to be sad over reflection having never supported toplevel functions in the first place.


What good are top-level functions if they’re unreachable from any script interpreter or management RPC server or other languages’ FFI?


I fail to see what any of this has to do with reflection.

> any script interpreter

What are you even talking about.

> or management RPC server

I would very much expect RPC endpoints to be opted in explicitly and statically.

> or other languages’ FFI?

I fail to see why Go-level reflection would be involved in that in any capacity.

> What good are top-level functions

You can call them? From your go code?

Are you saying rust or C can't be because they don't have reflection? That's a strange position.


When I type at a Groovy REPL, an interpreter is using reflection for nearly everything. Same goes for native code that has to use reflection via JNI entry points to call Java. It’s damn useful. At the heart of every debugger is a half-finished reflection API that deserves to be cleaned up and surfaced for language interop and novel kinds of tools.


Have a look at D and .NET 5 for compile time reflection.

Or in Java's case annotation processors.

Yes, not everything gets exposed, but it also does the job.


Functions are useful because you can call them from your program.


With Java, I didn't find it to be either a huge win or a major loss. And I still don't. It's basically window dressing that lets you avoid casts in certain obvious places where they weren't particularly problematic anyhow.


It's more than window dressing. A year ago I added correct generics to about 50 000 lines of Java, an old module in a bigger program. The change was mostly mechanical, removing casts and adding signatures.

I found a few places where refactoring had left behind a wrong type cast. Production logs demonstrated actual crashes corresponding to these cast. There were bugs in the issue tracker, marked unresolvable.


Interesting, I'd never encountered such bugs, but it makes sense


I can sympathize with this concern; constraints can force authors to just write simpler code which has some very nice benefits. But that leaves out some legitimate use cases like collections that were terribly served by genericless-go.

Yes there will be some pockets of libraries where authors take generics to an extreme. Besides being a bit of fun, it's good to explore the edges of what you can do with the language, so this exercise will be valuable even if you don't use them regularly in your projects.

But I don't really see pervasive gratuitous use of generics becoming a big problem for the average lib, Go devs tend to lean practical on potentially abused features like this, and there will be many users like yourself that will still prefer simpler interfaces. I guess we'll see over time.

Really, Go's lack of generics for so long wasn't an ethos (despite many taking it that way) but more that the right implementation just hadn't revealed itself yet. I think the one they chose is a pretty good match for Go, and I hope that it makes Go more complete.


How would you write a singly linked list who’s contents are arbitrary? What if you were to map a function over it? Say you want to use this data structure with third-party objects? How do you do that right now?


You wouldn't. Everybody goes into Go thinking that's absurdly confining. Some significant subset of Go programmers learn that they instead find it liberating. Programming is programming; you have an overwhelming number of degrees of freedom no matter what language you work in. It sometimes turns out that taking some of those degrees out of the language makes it easier to focus them on your problem domain.


While it's true that some find it liberating, a large number don't — personally, I've written a fair amount of production Go code and found it unnecessarily verbose and repetitive in ways that generics would've helped. I imagine some of this is based on problem domain; if you're writing a web application for example, maybe you don't really need generics much. After all, how often do you need a function that logs in a user to also log in... a book you're selling? Not very often. And any advanced data structures probably live in your database: how much do you really need a B-tree in a webapp when you've already got one in MySQL?

That being said for a lot of other uses, you really do want high quality data structures beyond "array" and "dictionary."


Sure, that's true, and it's fine. There are people who find s-expressions both liberating and clarifying, and others who get lost in a sea of parentheses. And there are problems that are especially amenable to s-expressions, or generics, or fine-grained control of memory allocation, or interwoven code and markup, object hierarchies, and those problems sort of "ask" for particular languages. But for the most part, this stuff is subjective.


There are certain things that just can’t be done without generics, though. Type safe higher order functions, type safe custom collections, etc. Of course, perhaps these are all just subjective to you, because you can still write any program you need without them. But not having this feature does constrain the set of type-safe programs you can write quite a bit.


I feel like it pretty much always turns out that the things you can't do without generics are, like, second-order things. Higher order functions, type safe custom collections, those are tools. What we care about mostly is what we actually build, and people build pretty much everything in every language, generics or not.


It depends what you’re building. If you’re writing a library to do those things, they may well be first order. Certain extremely useful patterns like parser combinators rely on them. And of course, the current answer is just “do something else instead.” But I don’t really see why this has to be the answer. Surely the decision to leave them out is just as subjective as a decision to add them.


By the same token, functions in general are second-order things - you can build things just fine with GOTO, and plenty of real-world software was built like that back in the day. But we don't do that anymore.


Yeah, but programming is the business of building little tools to help you build what you actually want to build, over and over again. E.g., you might have some API calls in your code and need to implement an error handler on some of them. You could repeat the error handler code on all of them, or you could extract it out to a generic function and just use it as a wrapper for the others:

    let call1 arg = ...
    let call2 arg = ...

    let error_handler call arg = ...

    let call1 = error_handler call1
    let call2 = error_handler call2
Python made this kind of technique famous, but languages with generics can do it pretty well too.


One example of a thing you want generics for is image processing procedures that operate on many different image formats with different color spaces. If you use the built-in image libraries (which is perhaps already a mistake), then you have an image that owns its channels, and you pass it to some resizing method or whatever, and then that resizing method's innermost loop chases pointers to find out which kind of image it was *for every pixel*. For performance reasons, you may not want to chase pointers in your innermost loop. Without generics, you need to copy and paste a bunch of code once per color space.


To add to your point, not using generics in Go is a choice too, even if it’s now an option.

But some people like offloading that to the language: not having it in a language means you don’t have to control for it on your team/project contributors + some 3rd party library.

I felt Go filled this minimalist category well. It’s always nice having a modern mainstream option doing so, not just a niche one on the fringes (ala LISP), even if I’m not personally a fan.

I guess it’s hard to keep saying no.


Yeah, we should have kept using macro assemblers instead of needless abstractions.


If you believe that absolutely, the way you imply here, then we're all just chumps for not working in Haskell.


I feel like I should probably admit that I am just generally a fan of functional programming and I do think there are big benefits to it, most of the time (of course, some algorithms are just more elegant with loops and mutation). But my day job is writing services with Elixir, so ultimately complaining about generics in Go is a little rich coming from me.

At the end of the day, whatever gets the product built is what matters. I just fail to see how generics could be a hindrance.


Not necessarily Haskell, although it would be nice, there are already enough ML influences in modern languages, with exception of Go.

Confessions of a used programming language salesman

https://dl.acm.org/doi/10.1145/1297027.1297078

> As a result, functional programming has finally reached the masses, except that it is called Visual Basic 9 instead of Haskell 98.


Sure, but programming languages are also tools. Why bother having this discussion at all, if you don't care about tools?


You can absolutely write type-safe higher order functions without generics. You can't write generic higher order functions, but that's a tautology.

Also higher-order functions are a moot point. Higher-order functions give you convenience, but no increase in expressive power (defunctionalization is an homomorphism).

Of course, generics give you a true increase in power.


> if you're writing a web application for example, maybe you don't really need generics much

All our microservices return a { result: ... } or a { result: ..., nextPageToken }

We would definitely benefit from generic SingleResult<T> and PagedResult<T>.

Instead, you copy-paste the same definitions over, and over, and over, and over again for every call.


I think I see what you're doing. Right now, each result type implements NextPager, which returns information about how to fetch the next page. You client can implement a utility like FetchNextPage:

    type NextPager interface {
        NextPage() PageSpec
    }
    func (c *Client) FetchNextPage(ctx context.Context, current NextPager) (interface{}, error) {
        ...
    }
Then for each type of paged object, you write:

   func (c *FooClient) FetchNextFoo(ctx context.Context, current Foo) (Foo, error) {
       next, err := c.client.FetchNextPage(ctx, current)
       ...
       if n, ok := next.(Foo); ok {
           return n, nil
       }
       return Foo{}, fmt.Errorf("unexpected type: got %T, want Foo", next)
   }
That's annoying. But, this problem has come up before with `sql`, which has rows.Next() and rows.Scan() to iterate over arbitrary row types, and you could use that as a model:

    pages := client.Query(...)
    defer pages.Close()
    for pages.Next() {
        var foo Foo
        if err := pages.Scan(&foo); err != nil { ... }
        // do something with the page
    }
    
Generics would let you enforce the type of `foo` at compile time, but it wouldn't save you many lines of code. I think you still have to write (or generate) a function like `func (c *Client) ListFoos(ctx context.Context, req ListFooRequest) (Paged[Foo], error) { ... }`. We hand-wave over that in the above example with a "..." passed to query (potentially possible if you retrive objects with a stringified query, like SQL or GraphQL), but that sounds like the hard and tedious part.

Let me conclude with a recommendation for gRPC and gRPC-gateway as a bridge to clients that don't want to speak gRPC. Then you can just return a "stream Foo", and the hard work is done for you. You call stream.Next() and get a Foo object ;)


> Generics would let you enforce the type of `foo` at compile time, but it wouldn't save you many lines of code.

You literally showed that "for each type of paged object, you write <multiple lines of entirely unnecessary code>".

Where with generics you just have a single generic function.

> We hand-wave over that in the above example with

The problem is: there's no hand-waving in reality.


I'm traveling, so can't properly expand on what I said, but without generics the choice was:

- repeat unnecessary bolierplate code for every single return type

- skip types completely and use a function that accepts interface{} as a parameter and assigns whatever to that variable

Neither are really acceptable in my opinion. And both will be greatly simplified and improved by generics.


You say "verbose and repetitive", I say "easy to read without any surprises".

The verbose patterns (if err!= nil for example) make the code predictable to read, you notice the code smell of missing error handling really fast.


I think readability has multiple dimensions, and it really depends what you are looking for.

For example here's a code in Go to look for a Prime:

    func IsPrime(n int) bool {
        if n < 0 {
                n = -n
        }
        switch {
        case n < 2:
                return false
        default:
                for i := 2; i < n; i++ {
                        if n%i == 0 {
                                return false
                        }
                }
        }
        return true
    }
It's readable as it is simple to understand what each line does.

Here for example is a code that does the same thing in Rust:

    fn is_prime(n: u64) -> bool {
        match n {
            0...1 => false,
            _ => !(2..n).any(|d| n % d == 0),
        }
    }
It's might seem more complex at first (what does match do, what 0...1 means, !(2..n) what is any() doing. But if you understand the language it actually this seem much simpler and you can quickly look at it and know exactly what it is doing. And because it is less verbose it is easier to grasp the bigger code.

I also noticed that while individual functions in Go are simple to understand and follow, you can still create complex, hard to follow and understand programs in Go.


Go is crazy verbose. Even Java can do it much more simply:

    static boolean isPrime(int n) {

        return switch (n) {
            case 0, 1 -> false,
            default -> !IntStream.range(2, n).anyMatch(i -> n % i == 0)
        }
    }


You changed function signatures from int->bool to uint->bool, which changes how long the functions are.

That seems unfair when comparing:

Removing negatives from the Go implementation removes 6 out of 16 lines, bring it from 3x Rust to 2x Rust in line length.


I used code from: https://endler.dev/2017/go-vs-rust/ good point, I overlooked that.

Anyway in Go (ironically because of lack of generics) if you use any numeric type other than int, int64, float64 you will be in the word of hurt. Rust doesn't have that issue.

So in practice you will likely use int, and I suppose you can add an assertion.

BTW: I only see that it would remove 3 lines though, where are the other 3?


I don't follow. I use unsigned ints in Go all the time. I've never been in a world of hurt with them. Mandatory explicit integer conversions (and the way Go consts work) are something Go gets right.


Ok, so you like that. I myself really hated when I used float32 and had to do this when I was doing calculations:

    result = (float32)Max((float64)a, (float64)b)
I ended up switching the type to float64, and wonder why they even offer float32 if it's practically unusable. I had similar experience when I needed to use int8 or int16 etc.

An alternative was to make own version of Max/Min and other math functions, but this is what generics would solve.


I'm not sure I follow, because Rust is also (thankfully) fussy about integer types.


I think what they mean is utility functions (e.g. min/max here) tend to only be implemented for one type, so if you’re using an other you keep casting back and forth.

Rust is very fussy about types and some operations are bound to a single one (e.g. stdlib sequences only index with usize) but utility functions tend to either be genetic or be mass-implemented using macros (or by hand probably for f32::min and f64::min though I did not check).


I don't have problem with being strong typed, I like that as well. The problem is that the stdlib doesn't really support other types and that's mostly due to lack of generics, so using uncommon types becomes quite annoying.


TBF the float Min/Max issue has nothing to do with generics, there isn't a "floating-point" generic class, and the entire reason why Go has an f64 Min/Max in the first place is that floats are not fully ordered[0], so you "can't" use regular comparisons (which you'd be told to do for integers) and thus you could not have a generic min/max relying on that even if there was one (e.g. in Rust `f32` and `f64` are not `Ord`, which `std::cmp::{min, max}` require, hence both `f32` and `f64` having their own inherent min/max methods).

So what your issue comes down to is Go's designers couldn't be arsed to duplicate the entire `math` package to also work on `float32`. Some members of the community did rise to the challenge[1] tho.

[0] well recent revisions of IEEE-754 have a total ordering predicate but I wouldn't say that it's really useful for such a situation as it positions NaNs on "outside" of the numbers, so a negative NaN is smaller than a negative number and a positive NaN larger than a positive number

[1] https://github.com/chewxy/math32


I'm aware of it, in the example given I used float32. Anyway Max/Min was just example I used other functions from math as well.

Anyway my point was that with generics, they wouldn't need to copy anything, the math package would work with both float32 and float64 and many functions likely would also work on all integers.


The difference here is that I can hand off the first code to any random freshly hired CS grad or cheapest outsourced coder and they can grok the code quickly. This is the advantage Go has to all other languages.

The Rust code needs maintenance coders of way higher caliber, not something you'd usually find. It's super fun for the top-tier developers who love to be expressive and concise with their code, but all code is pushed down to maintenance mode eventually when the hotshots move on to the new shiny project.

Go has removed pretty much every footgun by sticking to the basics. You have one way to do a loop, one way to do comparisons etc. There are very few ways to hide non-obvious functionality.

It _is_ possible to create complex programs, that are hard to follow but that's a larger design problem. Not something the language can force on developers.


That was one of the points from Java 1.0.

Thing is, care with what you wish for when easy to outsource is a goal, a welcomed feature mostly relevant to IT managers that don't care about the final quality of delivery, nor what consequences it makes to the home job market.

So yeah to all Wipros, Infosys, TCS, .....


Personally I find code with generics just as easy to read as (largely duplicated) implementations for each type. I might consider Golang again when this drops and there are things to be like about how low level it is…


Or use Option or Result, so your error handling is small and forcibly correct.


Error handling in Rust isn't always small or straightforward. I miss both options and match expressions when I switch back to Go from Rust, but there's also tangles of or_else's and maps and the fact that everyone uses third-party libraries to work out the types for errors. There's tradeoffs everywhere you look.


Zig is probably a better comparison for this:

https://ziglang.org/documentation/master/#Errors


In rust you have to swallow an error quite explicitly. In go it’s extremely easy to swallow one "err”, by assigning to some previous one which was already checked.


You're right that a templating language would reduce the code and make it easier to read.


Yeah, that's why Kubernetes had to develop code generators. So focused.


I wouldn't know, I don't use K8s. But I did write a code-generating ORM for a Go project and found it in a bunch of ways superior to the ORMs I'd used in dynamic languages, like ActiveRecord. And I've also worked with heavily parameterized Rust crates that kept 20 tabs open in my browser just trying to work my way through a couple function calls.

Don't get me wrong, I'd take Rust generics over codegen 8 times out of 10. But that ORM worked well, and was the only time I ever needed to write a code generator in Go.


Sorry but there's a bit of bias, every author of a library thinks that their approach is better than of their competing libraries.

If they didn't, they would just not create another solution.

Having said that you might be right with ORM though. One of great reasons why ORM might be superior on statically typed language is that you can rely on the type system to ensure you writing correct code (you also get benefit of autocompletion, refactoring in IDE etc). The problem though is that the type system including generics might not be sufficient to express it. So code generation could be still superior here.

The JOOQ (not exactly ORM though) generates java code, even though Java has generics.

BTW: I personally think though that actual proper way to handle this problem is to what JetBrains did. They integrated DataGrip into their IDEs (I think it's available in the paid version though) after you connect IDE to the database, it starts detecting SQL statements in the code and treat it the same as rest of the code (i.e. auto completion, some refactoring (they still need to improve that more) etc). It makes an ORM no longer necessary for me. I think that's probably the way to solve the impedance problem.


Is your ORM open source?


That made that to themselves, the original prototype was in Java and changed to Go thanks to some recently joined team members that were very munch into Go.


> You wouldn't

As someone who hasn't used Go, what would I do, then? The question about a generic data structure was very practical, your response was philosophical, and I still need a linked list.


You would use a slice as your non-associative container, and you would write a loop over it. You just wouldn't use a linked list.


And honestly, 9 times out of 10, I'm better off rebuilding the list because vectors have lower memory overhead and the memory is contiguous. But that one time... I've also been spoiled by Java's very rich set of collections.


Linked lists aren't the best example because they're almost never the right tool. But in the last month alone on a rust project I'm working on I've used:

- Option, Result, and other stuff from the standard library.

- My own B-Tree implementation w/ domain specific enhancements, in about 4 different contexts

- A heap based priority queue

- A custom 2 level vec, to support arbitrary insert & delete without shuffling elements around. (This turned out to be faster than my b-tree but less memory efficient.)

- Several different custom iterators, and iterator combinators. Eg, I have an iterator over some data which gets computed on demand. I have an iterator which consumes and run-length encodes each item in another iterator. And so on.

All of this stuff has generic type parameters everywhere. The b-tree is generic not just over the stored data, but also over the way its index works. I can specialize it to do a bunch of different tricks by just changing a type parameter.

All of this code is fancy and hence difficult to read for the uninitiated. And that isn't the Go way. But there's a big, valuable middle ground here. I'm glad Go is adding some options to let people lean a little bit into cleverer code when it becomes appropriate for the problem domain. Emulating generics with interface{} seems worse than just adding generics into the language.


And even if you assert that the use cases for generic structures are limited (which is debatable but why not) there's definitely something to be said for generic functions over existing generic types e.g. currently if you want to build utility methods over slices or maps (like… set operations because everybody uses maps but maps don't have set operations) you have to pick between:

* manually instantiating (handrolling) every version, possibly duplicating it if you don't realise somebody else did

* same except using codegen, which is easier to maintain but has more semantic overhead (now you need to add codegen to the project and there's an extra build step to consider)

* or manually type-erase and cast back, risking type safety and most likely performances

A flagrant example from the stdlib is the `sort` package with its weird and alien interface (and Interface), inability to use key functions, and which can only work in-place.

Talking about in-place but circling back to generic structures / collections, a big use-case I expect to see for generics would be type-safe (immutable) concurrent collections e.g. HAMT, RRB, …. Current Go significantly limits the ability to "share by communicating" (as well as safely "communicate by sharing") as there's no good way to build and make any sort of high-quality concurrent collection available, whether persistent or mutable.


It's not like this is some kind of new or unfamiliar experience. Many programmers had to deal with something like that in Java or C#, back when they didn't have generics. There's a reason why both got them eventually.


The anti-generic folks usually ignore that the industry was built with non generic languages, and largely decided to adopt generics.

It isn't as if we never coded without generics before.


I could not have said it better myself, despite trying. Thank you.


This is one of the best definitions of 'minimalism' I've seen.


Find a different way to solve the problem. I've written close to half a million lines of code in Go and the lack of generics has been a pain point in maybe 1% of that? Usually, if you are thinking about generics you are reaching for abstraction when you don't need to be.

If you really need generic structures you can use the interface type but generally there's a simpler solution that doesn't require generics.


It's not as if it's a niche problem requiring a niche solution though. The problem is "how do I write my own data structures" and to the best of my knowledge Go's answer is: copy-paste everything and edit for each concrete type, use code gen, or pay runtime cost for interfaces. All of those solutions seem more complex than just having a type parameter.


They seem more complex compared to a type parameter when you are only thinking about this narrow situation. However, generics are not limited to this narrow situation and will cause complexity far beyond what you are imagining.

Having said that, I honestly don’t know if generics are a net positive or not. What I can say is that go is one of the few languages where I can jump into an arbitrary go code base and make sense of it relatively easily. Risking that is a scary proposition for me.


I'm well aware when they are useful, I've been using them in languages for the past 10 years. By the same token though, leaving type parameters out of the language has had far reaching implications outside of this "narrow" situation (I'll put forth that implementing a data structure isn't a narrow situation). Error handling and the lack of sum types also seem particularly egregious and are heavily influenced by the lack of generic types.

> Having said that, I honestly don’t know if generics are a net positive or not. What I can say is that go is one of the few languages where I can jump into an arbitrary go code base and make sense of it relatively easily. Risking that is a scary proposition for me.

I don't see how type params would make this harder, I daresay I can think of a lot of instances it's much easier. The caveat is that you simply don't understand them, in which case it's a good thing to learn as many languages do have them.

In any case all these arguments are well trodden so I'm probably wasting my breath re-hashing here.


I’ve been using them for the past 30 years and have come to a different conclusion. You have plenty of languages that do what you want. I hope go doesn’t lose what made it different


Oh sorry, you were saying lack of generics were the reason you could make sense of code easily, implying that if a language has generics you can't make sense of it. That seems juxtaposed to having used them for decades. In any case I can tell you're passionate about this so I think we can just agree to disagree.

That said I don't think there's much "hope", from the looks of things it is coming to Go.


I'm hoping that Go still keeps it's relative simplicity. It's lack of inheritance gives me hope that generics won't be as complicate as it is in other languages.

I understand generics well, I just find code bases that use a lot of abstractions harder to read than Go.


You know? Original Java was a fairly simple language :) Generics were added in 2004 in J2SE 5.0.


Just call it parameterisation, it's less scary then. The unknown is always scary but having the ability to parameterise - including types - is well traveled ground.


This can be said for any programming language and any community: there will always be bad code written by someone.

Generic data structures are provided in many programming languages and there are many people that are very experienced in writing some of these, so being able to reuse, it's precious.

In general the lack of generic price surfaces when you write libraries, not applicative code. But libraries are a big portion of a codebase.


I’ve been coding for decades. I’ve used all the fancy functional languages, written production code at scale with complex type systems, etc. my experience, they don’t add a lot of value compared to the costs. In my old age I’ve grown to prefer go for it’s simplicity. I hope we don’t lose it, and you all have the option of using the myriad languages that already do what you are looking for.


I think you misunderstood my point (maybe): I totally agree with you. I've been coding for many years and coming from Ruby where you can do "the worst stuff you can think off" (really bad stuff: monkey patching, building DSL etc.), I came to Go exactly to get relief from all of that. I don't trust people having their hands on all that power.

Still in my short Go career, I worked on at least 4 libraries, one of those needed generics to gain a huge performance boost, the other worked around the lack of generics, but I still wish it had it (a special kind of logger). In applicative code, the main issue with lacking of generics is the lack of generic slices functions (map/select). Initially I thought it would be fine, but then I wrote a piece of code that was visibly involved in copying data from one slice to another with some changes and that "shadowed" the "central" part of the code behind a bunch of loop codes. In those cases, to improve the ability to easily scan through the code, I wish I had some generic slice function to deal with it. I appreciate doing loops, but sometimes they are verbose enough to hide the interesting part of a piece of code. This is especially visible in applicative code where usually performance is not as important as much as the business logic.


Go has some "special" built in functions like len() that work across different structures. What if instead of adding generics they added more of those special functions for working with slices as you describe? Would that have solved your problem?


Of course, but remember that `len` has constant return type, while in case of slices you'd have a return type that's different on the input slice


I see. I guess I assumed that the category of special "privileged functions" would have their powers extend to cover stuff like that (I haven't used Go, I'm just going off of what I've read)

I'm very intrigued by the no-generics idea, so I'm prodding at what might've made it workable enough that this reversal wouldn't be necessary :)


All good!

Well in Go the assumption is that you jump to interface{} (equivalent to Object in Java or void* in C) when you start having these kind of problems, but there is a performance penalty in doing that, which shouldn't be there if generics are present (and makes the code DRAMMATICALLY worse with all the casting)


> Usually, if you are thinking about generics you are reaching for abstraction when you don't need to be.

That's pretty laughable considering the language designers included type-parameterized collections in the language. Apparently they recognized the need for them; they just didn't think you were smart enough to make your own. After all, Go was explicitly designed for programmers who are, in the words of its creator, "not capable of understanding a brilliant language".


Probably a bunch of that code was needless boilerplate you could have gotten away with not writing if you had abstractions such as parametric polymorphism.


Maybe you could have gotten that work done with fewer than 500,000 loc if you used a language with a better feature set.


Lines of code is a complete red herring. It takes me less time to read and review 500 lines of Go than it does 100 lines of JavaScript.

Sometimes - especially in other languages - you see a piece of "elegant" code that does something complex in line 3 lines, and you think "hmm, this is a puzzle, and I'm going to be staring at it for 20 minutes before I'm convinced that its 100% correct."

We actively tell our engineers not to be clever. Write boring code that is obviously correct, and don't worry if your boring code is 25 lines when the elegant code is 8. It takes you longer to write the elegant code, and it takes the reviewer longer to read it, and often times (though not always ) it's also more difficult to test.

I love Go because of how boring and consistent and easy to read it is. The language and the task of "programming" melt away and instead you get to focus on solving problems.


Study after study has shown people fail at repetitive tasks, it is likely you do a much poorer job reviewing that 500 lines of Go code than your javascript.

Using generics isn't 'clever'. It's like saying a loop is clever. It's abstraction, the opposite of clever.


And it would have been far harder to read for those new to the generic code base. Complex abstractions make people feel smart, they rarely make code easier to understand or maintain.


Optimizing for people that are new to a codebase seems like a mistake to me: onboarding costs are relatively minimal and finite (per developer) whereas maintenance costs have no fixed bound: if generics let you exclude invalid states by design (and they do: this is one of the biggest advantages of parametric polymorphism vs. interfaces), they will be useful for keeping maintenance costs under control.


You definitely want to optimize for people who are new to a codebase. Over enough time, the codebase grows to a point where essentially _everyone_ is new to each area of the code, because nobody has touched that code in 2-3 years and the person who wrote it may not even be with the project anymore.

Even for your own single-person projects - if you get fancy with the code, 6 months later you find it's a lot harder to get back into and mess around with than if you had written the code as though you were presenting it to a beginner.


Management is responsible for making sure that doesn’t happen, by retaining experts and demanding documentation and investing in ramping up new experts. Making the code bigger because each line does less is not going to save us from nobody understanding prod, and a short learning curve puts a low limit on the value of our staff (who quickly run out of tools and stop improving in clarity and productivity).


You want patterns that are well-known to the maintainers, but this is different from “optimizing for the new”: consistent idiomatic use of a library like XState or Ramda in a JavaScript project can cause a high onboarding cost (because the new developers don’t know the library well) without any corresponding ongoing cost.


I’m guessing you like Haskell and similar languages, because that’s the natural conclusion to your line of reasoning.

I don’t agree with you, but I could be wrong. There are plenty of languages that are aligned with your point of view. I like go because it was going a different direction, and I hope that doesn’t change. You can use Haskell, scala, typescript, etc to get what you are looking for.


How is a generic data structure a COMPLEX ABSTRACTION?


If people would stick to common generic data structures (like a map/list that can handle any datatype), i'd be fine with abstractions.

But some people have a tendency to play code golf with their codebases.

I have, for example, encountered a "generic data structure" that looked like a normal linked list on the surface. BUT, it actually sorted the largest three items in the first 3 cells and the average in the 4th.

That was multiple days of work wasted because someone decided to be cute with their data structures. And that wasn't even the only one of such "generic" monstrosities in the code.


So blame goes to the hammer instead of the carpenter?


What does that have to do with generics? Surely they’d have written the same bad code monomorphised?


Generics (and other abstractions) are not the root cause. Go was a pragmatic defence against mediocre developers. The majority of developers are mediocre by definition, and will abuse _any_ abstractions to create Rube Goldberg contraptions and monstrosities.


20 years ago those mediocre programmers were using Visual Basic and Java, so look for the past to see where future goes.


Well there is a valid point of abuse in abstractions. The ruby world is a disaster because of the power provided combined with people reaching out to all sorts of abstractions the entire time for purely experimental reason.

It's true that applicative code shouldn't need generics in probably more than 90% of the times, however the lack of it affects library authors quite heavily


They are complex. I’m guessing you haven’t thought about them much beyond the trivial use cases.


No, it's relative. For the top 5% - 10% of developers, generics are a useful tool for doing their job efficiently. For the bottom 50% of developers, generics are complex and confusing, and only provides more footguns.


For 0.5%-1% C++ is a powerful tool which allows to quickly (thanks to rich abstractions) to write high-performance code.

For merge mortals it is a tool hard not to misuse full of hidden traps and debugging of code written even by the very best developers (surprise - it has bugs too) is a challenge.

Go just did a step towards C++, even if a tiny one.


Not everyone is entitled to be a sushi master.


And the top 1% of developers know that you are most productive when you stick to the simplest primitives ;)

I jest, but I also don't. All the best developers I know strongly prefer footgun-free libraries and primitives. The advantage is that you don't _need_ to spend brain cycles checking and double checking that it was written correctly, and when you want to make changes you don't need unwind an elaborate abstraction to stretch it further.

In martial arts (I'm a second degree black belt), the third move you learn is the round house kick (turned leg kick - the first two moves are the punch and the straight leg kick). At the olympic level, 70% of all points are scored using a roundhouse kick.

The other funny thing about the roundhouse kick is how much you can learn from watching someone do a single one. You can tell the difference between someone with 2 years of experience and 3 years of experience, and you can _also_ tell the difference between someone with 15 years of experience and 20 years of experience.

The point of this story being that masters are masters not because they can do elaborate techniques, but because their command over the simplest techniques is superlative.

I've found in life that this applies to pretty much everything. Martial arts, programming, painting, cooking, and effectively any task that definitively has some people who are better than others.


Generics use the type system to make the compiler check the code for you. It makes the code easier to understand, more correct, and concise if you use them as they were intended. It avoids tedious and error prone duplication of logic. Create ONE efficient debugged implementation and reuse it as much as possible. The crappy workarounds for generics introduce their own complications and issues.

But I know what you mean. I've worked on Scala code for years and seen less mature developers over-engineer things and get way too clever with the type system. Scala is a really practical and powerful industrial language that requires some maturity to use the abstractions sparingly. It's only good for the top 5% of developers.

However, Go doesn't have such a powerful type system. It looks like the Go designers implemented relatively simple generics. You have to draw the line somewhere. If simple Go generics are too confusing and complicated then that developer should probably find another job, as generics are a basic concept in programming. The Go designers are pragmatic people, and they've decided that relatively simple generics will make things easier in mainstream commercial codebases.


so as an example, I've used priority queues a lot. you need them for Dijkstra.. apparently golang has a heap package which lets you push and pop `interface{}`. sure you can cast, and I guess people have to, but why couldn't Go just call `interface{}` object or any? the awkwardness of the convention suggests an unwillingness to accept failure.


Parametric polymorphism is a better fit for container types IMHO. There are some interesting notes here…

In Go prior to generics, interface{} is an escape hatch less frequently needed but not necessarily much safer than void*. Post generics, interface{} is suddenly more useful and will be aliased by ‘any’.

The way the std lib heap works in Go, an implementation doesn’t have to mention interface{}. Using the Go std lib solution is about satisfying a few interfaces, defining some methods for sorting and swapping over the element type. The use of interface{} is internal.

Go’s generics solution is going to have type constraints, which I think will be very familiar to some and probably new to others … So, the Go generics PQ should still require some constraints on elements, not ’any’thing will work. I’ve really enjoyed constraints in languages and it’s not quite natural in C++/Java, but Go’s interfaces already do some ‘constraint’ work conceptually and can be used as constraints in Go’s generics syntax. I’m interested to see how this plays out.


>not necessarily much safer than void*

You can cast void* to anything you want. With interface{} you get a type check, either through an assertion or a panic. That is a big difference in safety.


Fair point. Maybe there are contrived cases that can get nasty (an interface{}-typed variable boxing a function is possible, not so with any non-empty interface ), but in practice the pathology would be ‘panic’ more than ‘here are the keys to the exploit kingdom’, if this is what you were thinking.

I was thinking more about silence where someone expected a greater degree of compile-time type safety than they really had, or relatedly e.g. the subtleties in JSON encoding / decoding where there is silent data corruption that could fall through the cracks - mostly I feel like I avoid these things by not using interface{}; I certainly did not grasp that without some experience with the language.


I also wrote hundreds of stuff from 1986 up to 1994, my first experience with generics, on Turbo C++ for Windows 3.1.

Doesn't mean many of us want to keep living on that world.

I advise reading books like "From Mathematics to Generic Programming"


You don't write singly linked lists of arbitrary data :).

In my 20+ years of development, I've definitely realized everyone's brains and approaches work differently.


You do if you want your language to have a reusable Linked List data structure so that everyone doesn't have to re-implement their own version of it for each content type.


There are languages that do this already though. Use Python. Reusable everything, just wait for exceptions.

I've spent time in C, Python, Go, Js, and some lesser known languages. I was really big into Python. Go seemed restrictive at first, but was immensely more safe and predictable.


Linked lists are the one data structure that you never end up wanting a generic version of. They aren't really used as containers. (Except badly.)


They’re the easiest stack structure to implement. All your operations are against the head of the list and you either have a thing, don’t have a thing, or add a thing. I’d like to be able to maintain a stack of things whose types I don’t have to manually reify and erase. That’s not a tall order, and it’s certainly not “complexity”. It’s markedly weird that I can’t have that same structure and associated operations be usable regardless of the thing I’m working with.


If you want a stack you'll just use a growable array (in Go, a slice). A linked list is suboptimal.

Linked lists serve a real purpose in some situations where you really do want O(1) behavior. One example is when you are performing the operation while holding a mutex. But it's never the sort of thing where the right tool is a linked list generic container.


I agree linked lists might be suboptimal for a stack implementation, but for a different reason: data locality.

You can have O(1) stack with both implementations. Slice-based will be easier to follow, and will bust the cache less often (less pointer jumps).


Singly linked lists are something from CS1 where you learn about data structures. You rarely use them in practice. In most cases slices and maps do the job fine.


They also have absolutely terrible performance due to poor cache locality and putting pressure on GC


There are many ways to implement a list, including backed by arrays.

That is what those CS algorithm and data structures lectures are for.


They make a great stack, though. Cheap to use and easy to implement.


Use a deque at least and amortize the heap allocations...


With generics I could justify the extra engineering work to make such a deque!


Source?


CS201


Ok genius, explain how exactly what the OP meant?


Nobody needs to do that, but it would be nice to have sorted collections without dynamic casting or rewriting half of leetcode in every project.


You can do something similar to container package which has a double impl.


I fully agree, after writing go for the last 7 years. I recognize that generics solve some problems, but there’s a cost in doing so. In that period I’ve had only a few cases where I wish I was back in a language with this feature (Java being my previous typed language), however most of the time the interface with an application specific data model was more than sufficient. Obviously you need to wrap that abstraction for typesafe code, but that’s pretty trivial.


Without generics, I have seen terrible and unreadable golang code. There is no tool that can not be mis-used.

Generics are adding a tool to the toolbox. They can be used well or they can be mis-used. I recently wrote a gui app in Golang using Fyne and I really wish I'd had generics for some of the UI handling - instead I ended up having to write some really ugly and not simple code to handle the case. The end result was worse and less-simple than if I had been able to use generics.


Can you give an example of terrible unreadable go code due to a lack of generics? I’ve grown to love go because it’s one of the few languages where I can jump into a new code base and relatively easily understand what is going on.


Because you will already have seen that precise piece of code in a thousand other contexts?


> Can you give an example of terrible unreadable go code due to a lack of generic

https://news.ycombinator.com/item?id=28254411

The "solution" to this is copy-pasted boilerplate code with casting for every call.


I have seen terrible and unreadable generic code, people building abstractions just for the sake of abstractions.

Hello-world level projects with 20 class deep hierarchies of generic classes, just to make it "generic" in case you need it later.


I agree. It’s true for all languages. I once had to untangle a small Java app that had about five packages with two classes per package (I refactored it into a single package).

Some people will abuse generics, no doubt, just like my colleague abused packages. But Go has such a strong culture around idiomatic code that I’m not too worried that generics are the end of the world. In fact, I don’t like keywords like “append” and I’m hopeful generics will replace them.

TBH I was more upset about the Go 1.17 ability to panic during a type conversion from slice to array pointer. That one really grates my gears.


I personally think this will be a great thing for the quality of the language (no typed higher-order functions is a massive pain point as-is), but at the same time one could argue that this is an equally massive bait-and-switch for all the developers who prefer Go's original take on "simplicity".


Go is still pretty “simple”. They didn’t introduce a while loop. Adding generics would arguably “simplify” the language even further since you would presumably throw away a lot of duplicated code.


> Go is still pretty “simple”. They didn’t introduce a while loop.

What are you talking about? There is already a while loop, it's just under a different name. I don't think reusing keywords for other purposes is an example of simplicity.


They can simplify code, but they don't simplify the language.


The langage which already had generics except special-cased to builtins the langage authors really wanted to be generic and they know better than the peons so they have the intellectual capacity to make this determination?


you sound bitter


I'm just believing what Rob Pike told me about his target users for Go.


Either a language changes to keep interest up and bring in a new audience, or it's left behind by advances to and the status quo (and what's considered a minimum set of features) and stagnates.

Just look at Perl. Stuff written 20 years ago is likely to work without change on the newest release more often than not. For certain work contexts, like system utilities and long life core programs, this can be amazingly useful.

And all it will cost you is the slow decay of your community until eventually they're almost all gone, and nobody releases supported libraries for you anymore, and there's not even enough community left to provide community support for many new services and technologies.

If you stick around one thing long enough you're destined to be disappointed one way or the other.


For some strange reason many developers lose sight that programming languages are software products just like anything else than one can sell for installing into a computer.

While FOSS might have changed the way to sell those products, they are still products looking for attention, market share, ecosystems, conference talks, consultancy, trainings.....


C


C is only used because it has a widely deployed set of libraries and by it's nature as an underpinning of most foundational software because it was the language of choice when we really started hitting exponential growth in computing. If there wasn't an enormous set of software and source code out there for it already, would there be any reason to use it over anything else?

And even then it's latest language standard update was in 2018. You still have to deal with the fact it's changing and keep up with it if you plan to read or use others code. And of you don't, then you can safely not care about advances in C, just as you can safely not care about changes in Go.


To add to what other people said, the tooling around C has changed a lot since C was created, partially because there's already so much code in C. The Linux kernel is 20/30 millions lines of code of C mostly, Kubernetes is below 2 millions. So at this point you can still change the language a bit. With C it's harder, so instead people invest in tooling (and replacements).


C11 introduced type generic expressions.


Not in the commonly understood sense of "generics", no. You get to switch on a type, but you don't have generic functions or types. A reusable, type-safe Vec or HashMap are still not possible in C...


Kind of, now that typeof is going to be part of the language, that coupled with the preprocessor, does allow exactly that.

Although in typical C fashion is a bit of kludge.


"coupled with the preprocessor" is a bit of a cop-out :-). Do you have a concrete example of what typeof would enable? Like a basic implementation of a vector type?


True, it is basically //go: generate, before Go was born.

Here is an example, https://codereview.stackexchange.com/questions/101816/generi...


ISO C23 is coming up, and until the last UNIX stops working, C's existence is assured.


If anything generics will make go code simpler. No more copy pasting everywhere and the possibility of making useful collection methods like map, filter and reduce.


More concise, yes. Simpler? Maybe[0].

And for the record, I'm mostly in favor of adding generics to Go.

[0]: https://talks.golang.org/2015/simplicity-is-complicated.slid...


It also depends on your use case. Kubernetes and its pile of code generators will be much much simpler.


I agree about placating. Go is fine as it is. I don't need or want generics, but the addition of generics won't stop me from using Go.


I didn’t need generics till I wrote a mathematical library, and wow they would be handy


I've used templates in C++ to do that and I agree that it is handy. I had to write far fewer functions than I would have without them. Sometimes, it's nice to not care or worry about how many bits are in a number.


> I don't need or want generics

I do need and want generics. So... Whose needs and wants are more important?


Neither, of course. I'll leave so you can join. I don't think either of us is wrong.


You sound like, for you personally, generics are like a poison atmosphere. You simply can't live in that environment.

Why? Why so negative?

I work in C++. Generics exist there. I don't use them except for some STL containers. I just... don't use them. I don't have problems where I need them. How does it hurt me if they exist?

You may say that someone else will use them, and make your code more unreadable. They may, but... if they really help the code base, use them. If they don't and someone uses them anyway, teach them some taste and discernment. If they can't learn, then you've got worse problems than a language that has generics.

If it's borderline, but against your personal taste, then yeah, you're kind of out of luck. On the other hand, personal taste changes over time, often from experiencing new things. You could try it for a while, and see how it goes...


I don't mean to be negative only, and appreciate your reply.

But I know what happens in real codebases. Before long, scammy tutorials pop up showing Go as an essentially dynamic language, and that's what bootcampers write. As of today, they are forced to write simple, boring code.

I never mind a carefully added thing, for carefully thought of situations, as this probably was.

The problem is that I see the abuse from here. And even if not abuse, it changes how you approach problems. And approaches matter as much or more then spec.

I knew a person who insisted on a lot of things when working in a Go code base...one of which was mixed typed tuples. It was ugly, awful code of interfaces all the way down. It's ugly and Go let you know that. A voice of reason would say...what are you doing? That's not how you do it in Go.

Readability goes beyond the syntax. It's a mindset. And now it's not.


> Before long, scammy tutorials pop up showing Go as an essentially dynamic language

Sorry are you saying adding generics to Go makes Go closer to dynamic languages? Shouldn't that be the opposite?

> I knew a person who insisted on a lot of things when working in a Go code base...one of which was mixed typed tuples. It was ugly, awful code of interfaces all the way down. It's ugly and Go let you know that. A voice of reason would say...what are you doing? That's not how you do it in Go.

Do you have an example of this (code)? What's wrong with tuples with elements of different types? What's the alternative?


So, mixed typed tuples was the idea, but Go doesn't support either of those things, so it ended up as a [][10]interface{} or some such. So for each item in the slice, type assert all 10 things inside the subarray. Luckily the comments had a key of expected types of each.


That would be exactly the thing generics are supposed to fix (except that Go doesn't have tuples yet, but at least you can define your own now and return a generic `Tuple[A, B]`).

Regardless of that, returning a tuple of size 10, especially with different types, would almost always be a bad idea.


Your problem isn’t generics or any other language features; your problem is a lack of effective code review or technical leadership.


Go doesn't even have tuples, where would those even come from?


It sounds to me like the simplicity of the language in this case didn't prevent a bad programmer from writing bad code, and that it was a problem that should have been addressed with code review and training, not by expecting the language to keep things in check.


It might me, in time. As mentioned, my main interest in Go is in how easily I can read others code. This kinda ruins that, even if I never use it.


Learning something is also an option.


Thank you.


Learning things outside of complex computer science topics often adds more value to the world. The ability to write useful programs without dedicating ones life to esoteric comp sci topics is a net positive for the world.


Are generics really considered as "esoteric comp sci topics"?


I needed to use C# for some project. Used generics without actually being aware what they were. Helps that C# implements generics without type erasure.


> Helps that C# implements generics without type erasure.

For about 95% of "using generics" that makes no difference, and you'd have had no more issue in Java.

That aside, Go is implementing reified generics (there is no real backwards compatibility concern as the "core" collections are already ad-hoc parametric types, and the rest is not a great loss).

Java had a lot more legacy code using a much higher number of standard but not-generic-at-all collections, I'd bet there were also concerns around the forward compatibility of legacy reflection code, and they'd probably considered a lot of collections-related goodwill spent with Java 1.2's Collections Framework and the deprecation of the limited set of original collections.


If you want coding to be accessible to people without comp sci degrees, and those who are NOT employed as full time developers.


I’m not sure Go has ever been a great language for non-CS people, but it’s been a very long time since I was new to programming. Go is pretty clearly a “get shit done” language catered to developers: a productivity tool. Why should it have its capabilities restricted?

English is used in both reality TV shows and scientific journals. The vocabulary used in the latter doesn’t prevent it from being useful in the former.

Go will still be usable the exact same way it is today after generics are brought in. It’s not like all the existing more approachable code written without generics won’t just disappear overnight.

That said, I agree with GP. Generics aren’t that complex anyway.


If people can understand functions, they can understand generics. Nothing more complicated, generics are just functions on a few type arguments.


Here’s an example from this very thread that shows how complex generics get relatively quickly.

https://news.ycombinator.com/item?id=28255738


That example is essentially the most basic use of generics. There are much more complex examples that would support your argument better.

What do you find confusing about that code?


I don’t find it confusing, but it is certainly more complex than a simple function call. I chose it because it is a basic usage of generics, to highlight that basic generics are more complex than basic function calls.

A code base that makes heavy usage of generics takes more time to understand than one that doesn’t make heavy use of generics. The readability of go is what I love about it, amongst a few other things.


Without generics, you'd have N functions instead of 1. Are you sure N times the complexity of non-generic function is less than 1 time the complexity of a generic function?


No one knows for sure. I’ve seen a study or two that found that reading and writing generic code is a lot harder, while using a generic library is relatively easy.


Writing generic code is indeed harder than writing non-generic code, but what I wanted to point out is that you can't compare generic code to a non-generic piece that handles only one type (which is often what people do), because the functionality is not the same, it is apples to oranges. What you should compare is non-generic code that handles the same cases as the generic one. So the non-generic code would also get a lot of additional complexity due to that - e.g. instance-of checks or repetition that would be absent in the generic one.

I think genericity is like any other abstraction e.g. functions or interfaces. Applying it blindly can lead to more complexity, but in hands of someone who knows what they are doing it can be a great tool for reducing complexity.


“ in hands of someone who knows what they are doing it can be a great tool for reducing complexity.”

It really boils down to this, but what does that really mean? I’ve been coding for a very long time, and go is one of the very few languages where it’s relatively easy for me to jump into a code base I’ve never seen before and make sense of it. Maybe the majority of code isn’t written be people that “know what they are doing”?

I like to think in terms of optionality, the magnitude of possible upside and down side. I’ve come to the conclusion that heavy abstraction has a large magnitude of downside risk and relatively small upside benefit for large teams and institutions.


Proper abstraction can have a huge upside by allowing people to understand a part of the project without the need to understand all of it. We're using abstractions all the time really, you don't need generics for that. An iterator, a collection, a file, a network connection, a database table are all abstractions. You don't have to read N millions of lines of database code to learn how to use a database. Abstractions like these are good.

Abstractions where the description of the usage (API) is just as complex as the implementation are bad, and I bet you can create them in Go quite easily without generics. At least I know it is possible in Java, and also was before Java 5.


Again, a lot is packed into your usage of the term “proper”. I find new code bases in go far easier to read than new code bases in every other language I’ve used. Are you suggesting that most code bases are written by people that don’t know what they are doing, and are improperly using the language features?


I learned C++ when I was 13, Haskell at age 15. I don't have a CS degree nor do I work as a developer.


I learned C++ templates during high school, back when C++ARM book was the official standard.


No generics, and lack of error handling are why I bounced off Go.

Can I write a generic data structure? No. You have to cast from interface{}.

Java 1.4.2 is dead, and rightly so.

And manual error checking? No. Get that garbage out of my control flow. I'm not going to a language that has worse error handling than C. At least in C you can factor it out.


Writing stuff in C... one thing I would like is an error return keyword. And the ability to set how you'd like unhanded errors to be dealt with. Like unhanded error means program aborts. Let the programmer decide how sloppy he wants to be.


If i wanted or needed generics i would use another language. I have been able to write great code without them for 5 years now though. But now i get to defend my codebases from the unneeded introduction of them from largely developers who think they are too smart.


You can totally do fine without generics depending what you do. It mostly affects code that supposed to be reusable, especially libraries.


you sound like you're too smart for generics.


No I’m likely too dumb for them but my code base is clean and easy to onboard people to. I take great pride in new hires telling me something is easy to do after only working in it a week

And like i said generics have a place its just a way smaller place than many people think


Same here. But I think it's more laziness than 'too smart.'

I like to liken it to house construction. Imagine if a number of people demanded that natural gas and water be combined. Why do I have to make two separate pipes? They even look the same!


There are just things you can’t do without having generics in your language. User-defined collections, for one. Statically typed higher order functions, for another. To paraphrase (I believe) Bob Harper, you either have a statically typed language with generics, or you have a dynamically typed language. That’s the result of not having generics. Code that needs them becomes more dangerous and error-prone.


I agree. I feel like generics make more sense in a language like Rust or D or C++ (templates) where you need the performance of compile-time constructs, and generics specifically have that semantic. In Go, it doesn't make as much sense to not simply use an interface for the same purpose since the language is less bare-metal (or, rather, implementation-specific) by design.


It's not just about performance, or really not so much about performance - e.g. when Java added generics, they didn't make it any faster than it used to be when you had to use Object everywhere.

It's about type safety - i.e. catching bugs earlier and more reliably. Every time you have to write an explicit downcast, you are throwing away the benefits of using a statically typed language in the first place. Which is fine, but then why stop halfway, and not just go full dynamic? It doesn't really make sense to have strong typing for scalars but not for collections.


I agree with you strongly. No generics please. It is already hard enough to try to steer a group of developers towards a shared vision without giving them the ability to show off how "smart" they are.

Abstractions in libraries? Ok maybe.

In the higher level code that the vast majority of programmers actually write? No. Thank. You.

Besides, interfaces cover the majority of generic problems on the ground.


Isn't that a fairly easy policy to enforce, though? Basically you blanket ban them in your linter, and only create exceptions for specific cases like containers, parsers, situations where it saves you a cast, whatever.

I know that doesn't help you when you have to interact with the larger Go ecosystem on Github, but at least within an org, it shouldn't be terrible. Certainly the culture of golang is well established— millions of lines of it have been written generic-free, so I'd expect that it will only be applied where it really is an obvious improvement.


Glad to see this finally happening. I've been writing a decent amount of Go lately and there are plenty of instances where this will make the code more readable.


Every HN thread about go: go is useless because it lacks generics.

Go adds generics.

HN thread: I don't want this.

Good case study about the people drawn to comment on a topic.


Isn't that the response you should expect on any topic where the population's preference is split across a decision line?

When go didn't have generics, those in favor commented. Now that it will, those against are commenting.

It sounds funny at face value but I don't see the insight.


> Go adds generics. HN thread: I don't want this

If generics are a good idea for a language, then it's better to add them in version 0.01 of the language and build up from there with generics as an intrinsic part of the language and standard libraries.

No, I don't want _tacked on_ generics.

Generics are not in 2021 a "bleeding edge" feature, that _might_ be useful later. They are established and proven in ways that they were not when Java v1 or C# v1 shipped - both added generics in later releases, with associated compromises. Now, you can and should choose upfront if your language needs them, or to choose a different route. There's no need to repeat that history.

YMMV, people have different preferences in programming languages, I have always preferred strongly typed languages. Generics suit me, but pick your lane.

And if you're at version 0.01 of a language, while you're at it, look at being immutable by default, not null by default. Those things can be opt-in for when you need them.


These don't seem tacked on. They spent so long on them specifically so they don't seem tacked on.

The issue with Go is if you're not shuffling around binary data it has horrible developer ergonomics. Network data is usually untyped, but if you start needing to care about what is in that data and need typed datastructures built around it you stop having fun quick. Same issue with C, really.


There is lots of fun to had when debugging mismatched dynamic structures sent around the network.

It is no wonder that any new format introduced with hype to release developers from type safety boilerplate chains, a few years in production ends up getting its own flavour of schema definition.


> Generics are not in 2021 a "bleeding edge" feature, that _might_ be useful later. They are established and proven in ways that they were not when Java v1 or C# v1 shipped - both added generics in later releases, with associated compromises.

Generics were hardly bleeding edge by 2002. Doesn't mean the average developer understood the point, but that's hardly a benchmark, and really most people have a hard time understanding what they have no experience with.

And work on .net generics had been going on since 1998, and by the release of 1.0, generics were going to land in 2.0. It simply wasn't done at that point: "Design and Implementation of Generics for the .NET Common Language Runtime" dates back to early 2001, but by late 2001 the spec was still incomplete and debated (https://docs.microsoft.com/en-gb/archive/blogs/dsyme/some-hi... used to have a link but it's dead).


> Generics were hardly bleeding edge by 2002.

yes and no. It's clear from the history at the time that they were not a proven, obvious win.

> Generics for .NET and C# in their current form almost didn't happen

> being told by product team members that "generics is for academics only"

https://docs.microsoft.com/en-gb/archive/blogs/dsyme/netc-ge...

My argument is that this particular question has, since then, been settled in favour of generics, outside of academia, and in mainstream languages such as c#. Generics are now "established and proven" in ways that they were not then.

Hindsight: https://twitter.com/matthewwarren/status/920667986108846080

C# is IMHO mainstream, deliberately so. Features in it typically come from other languages. It is a "populariser" of promising programming language ideas for mainstream productivity, not a testbed.

> Doesn't mean the average developer understood the point

at the time as a junior dev, I got the point inside a minute: I was already in the habit of when declaring Customer class adding a strongly typed CustomerList class, for their address, an Addess class, followed by the AddressList, and Orders needs OrderList and OrderItemList. Reducing this repetitive "plug in the type" code to List<Customer> etc was such a clear win.


> I was already in the habit of when declaring Customer class adding a strongly CustomerList class, for their address, an Addess class, followed by the AddressList, and Orders needs OrderList and OrderItemList. Reducing this repetitive "plug in the type" code to List<Customer> etc was such a clear win.

TBF a common rejoinder back then was that this specific pattern made the opportunity cost of domain-specific operations very low, so you could better those collection interfaces to the specific needs.

Of course 95% of the time they extended the corresponding class or implemented the interface so that wasn't actually true.


> this specific pattern made the opportunity cost of domain-specific operations very low,

Yes, I did miss the "added value" operations declared on that list class, e.g. AddressList typically contained a "GetCustomerPrimaryAddress", etc.

But there were ways to bring that back, by subclassing or (later) extension method on that list. Indeed, 95% of the code was tedious forwarding to the non-generic non-typesafe "list of objects".

Around the same time the dominant pattern of saving or loading data to a data store moved from "active record" where these methods were common, to "repository and DTO" where they were not. Might be co-incidence.


No, I don't want _tacked on_ generics.

What languages in your opinion have good, non-tacked-on generics? Are they generally better than other languages?

Here are some pretty successful languages that gained generics after maturity: C++, Java, C#, TypeScript (based on JavaScript), Objective-C, even Python.


The libraries are better because they're designed with generics in mind.

Most C# libraries in use today were built after generics were added, and there never was a large ecosystem already developed at the time

Java has had lots of libraries thrown away due to initial lack of generics and a lot of painful migration e.g. https://docs.oracle.com/javase/tutorial/extra/generics/conve...

A good parallel would be async-await in JS and nodejs. The entire core library and a huge percentage of the ecosystem ended up being really awkward to use after the introduction of async await. The whole ecosystem is now one big giant mess.


The ecocystem did start moving forward, but it will take a long time for the older pre async-await libraries to fade away and there will always be some strangeness resulting from the fact a lot of the libraries you find use a totally different API convention that you have to call via promisifying tools.


> C++

The parsing rules for templates alone probably make a decent door stopper. They can also trivially kill compile times. The bloat from page long symbol names also isn't something to ignore, just std::map<std::string,std::string>::find() results in a decent chunk once the compiler is done expanding it.

> Java

That is a can of worms, haven't professionally worked with Java in some time, but from memory:

* Compile time only, reflection or serialization heavy code will get raw Object types, yay type safety.

* They do not support primitive types, resulting in object boxing and null-ability issues by default as well as third party copy paste libraries that provide collections specialized for primitives.

* You cannot create an object or array using a generic type as that is unknown at runtime, as result some methods require a concrete array as argument just so they can create their own array with the correct runtime type.

* It is fully backwards compatible, so nothing will tell you if you have a compiled library that uses raw types somewhere in your application dumping Integers into a List that should only contain Strings.

* Calling code has to verify object types using runtime casts, you may not want to pay the cost of that.

I could probably go on ...

> Python

Aren't those just hints that the interpreter completely ignores? I have a python 3 toy project that could have benefited from better type checking - the contents of its sqlite database are a mess.


Fwiw generics in Java should have less holes once they start releasing things from Valhalla.


Yes, optional being the best example, it will be a proper primitive class.


> What languages in your opinion have good, non-tacked-on generics?

It's cliche, but it's clear that generics like Result ( https://doc.rust-lang.org/std/result/ ) and Option ( https://doc.rust-lang.org/std/option/ ) are fundamental to Rust's design from the start.

> some pretty successful languages that gained generics after maturity

I'm very familiar with C#, of course it is a "pretty successful language", no kidding. I did not say otherwise, I said that there are "compromises" associated with adding generics in V2.

And there are: e.g. starting with collection types, interfaces and base types in pairs, such as List<T> and List, IList<T> and IList, IEnumerable<T> and IEnumerable, etc ad nauseam. Then Delegates that precede Func<T> and Action<T>.

I suggest that you reread my comment above; the point is not just that adding generics later causes complication (historic record is clear that it does).

The point is that generics were _not_ proven in mainstream OO programming when c# 1.0 was released in 2002, but this has changed; now they are. Designs done in the last few years can avoid this complication: Ether your language wants them from the start, or it intends other ways to deal with the same kinds of problems.


Ada, ML, Modula-3 and CLU got generics since day one.

In fact, so much research and Go generics are mostly similar to CLU.


I forgot to mention Eiffel, Sather and BETA as well.


> What languages in your opinion have good, non-tacked-on generics?

ML and its derivatives, Haskell, Rust, Nim.


From what I have seen over the years, the "I don't want generics" crowd showed up just as often as the "Go is useless without generics" crowd. This time, though, only one of those groups can reasonably continue to comment.


You’re making it sound like these are the same group of people and they’re just really fickle, whiny and impossible to please. But my memory of these older go/generics threads were that commenters were split between “I love go, I wish it had generics” and “I love go, I don’t think it needs generics”. The former have no need to comment anymore, the latter will still want to express their feelings.


This is why you have to be a tough leader when you’re the designer of anything. But especially languages which naturally draw an endless barrage of comparisons and tribal opinions.

You see this a lot in web design, everyone thinks they’re an expert at what makes a good website - simply because they are a consumer of them. Even if they only have minimal experience building one IRL.


Or good case study of squeaky wheels. I never wanted generics. I never thought to spam the development lists about how much I liked how things were going.


a couple high profile projects (k8s) needed generics, there are limited use cases outlined in the planning docs that detail the holes in the language they're filling, it wasn't just squeaky wheels.


Maybe you can get rid of some runtime.Object casting here and there (I suspect at the cost of major compilation time penalty) but pretty sure the codegen is here to stay


Shame how k8s was a failure without generics. Think what could have been.


UNIX was re-written in a clunky unsafe systems programming language, by being free beer due to legal impositions on research work, its adoption settled the future of this language across the industry.

Yet even C supports lightweight generics, so they must be worth something.


The thing is, Google is paying most of the development costs for Go and Google controls Go too. It makes sense to adapt the language to their needs. They already do stuff like creating new network protocols because it will reduce their costs.


Google have alternatives to k8s already. So it's more about making a market.


I've heard multiple times that Google Cloud has the best support for k8s, and that people moved to it because of that. Is that what you meant by making a market?


That and open sourcing it.


It’s almost like HN is not a single person


That’s because “HN threads” represents a lot of different people with different needs and opinions. It’s not a monolithic block at all, and people are more likely to react to complain.


> people are more likely to react to complain.

You hit the nail on the head. This is broadly true across virtually everything--at least in most of Western culture (as this is the only one I'm most familiar with).

Anger seems to provoke a more actionable response whereas satisfaction does not (generally speaking). Is someone more apt to call a business over lousy service or great service? Why is it that giving a compliment to someone in customer service sometimes provokes a brief reaction of surprise? I try to do my part as a positive force when I can, but I usually fall short. Besides, is one happy person likely to make that much of a difference over the course of a day? Maybe, maybe not.

FWIW I actually like the idea of generics in Go and have a few use cases where they'd be exceptionally helpful in cutting down the amount of code I have floating around in some libraries. I just don't feel strongly enough about it to jump into a thread to rush to its defense.


My guess would be it has biological/physiological reasons. Negative stimuli create a greater urge to act. Tabloids selling more newspapers with bad news than good ones, because the individuals that gave higher weight to birdsong than the signs of a predator approaching did not procreate, evolution at work.

It is possible to change though, patiently putting your own focus on positive things, which there are so many of. Takes work, but sooo worth it. Life is more fun that way too.


I am okay with generics as long as they are not affecting compile time. I love Go for blazing fast builds and testing and afraid generics will degrade it. Otherwise, generics are adding some cognitive complexity to code, but I don't have to use them and will just occasionaly have to figure them out in libraries.


Check ML languages with its multiple backends, Eiffel or Delphi/FreePascal.

Go compile times, while great, weren't any novelty to old timers before the .com wave that brought scripting languages into the spotlight and Sun's gigantic Java push.

Thankfully the 20 years detour seems to be getting over.


Yeah, too bad I can't build a decent team to develop a distributed scalable application in FreePascal.


That is a failing on your part given that the tools are available.


As much as i value your insights and historical addendums, your tone is often offputting to me.


As it may me, that is how I see the world.

Tooling is available, books are available, 30 years of libraries are around.

If with all of that you can't find a team, it certainly isn't because of tooling.


Parametric polymorphism pretty much never adds cognitive complexity..it's the only true abstraction there is in CS.

Go could easily fuck it up tho - that's fair.


I don’t know, at this point my guess is that most people just don’t care, so those that can be bothered to comment are a bit fringe.

When Go first came out, it seemed interesting, but when I checked it out it felt more like throwing out the baby with the bathwater than introducing anything of value. Not every idea since 1972 is bad. So I tried to force myself to use it for a project or two, got frustrated by exactly the things people were complaining about, and never looked at it again.

I haven’t seen any meaningful adoption, so my hypothesis is that by not having the features mainstream users want, Go missed its window of opportunity and the world moved on. It’s neat that they’re adding them now, but.. well.. let’s just say that I was an absolute die-hard Perl fan for a long time so I’m familiar with this pattern.


> I haven’t seen any meaningful adoption

You must live in a different world. Go has been an immense success since its inception. Cloud, DevOps and SRE are nowadays unthinkable without it.


I must be doing something wrong with Java, .NET and Powershell.


Been reading through comments and wanted to say exactly this :)


The people who cared about Go not having generics (myself included) moved on. The time for a well designed language with Generics was five years ago.

The only people who care about Go now are the ones who convinced themselves that casting from empty interfaces was okay.


Dunno about the rest of the world but London definitely seems to be embracing Go - many more jobs going now than even a year ago.


Yes and no. I dropped go after a short but tumultous love/hate relation, but decided to take a fresh look if generics had a decent answer. There's a lot to like about go. So I'll look back soon.


My employer has not moved on and it’s steadily encroaching more even on strong Scala/Java teams, so I’m glad to see the worst impediments start getting fixed.


What syntax did Go settle on, in the end?



the generics mafia has ruined rust and looks like go is next.


I shudder to think what Rust would look like without generics.


Hey, instead of “return value, err”, we can now write it as generic Option!

Kidding kidding, please don’t do it kids


Sadly go is losing the spirit it was built with and alienating the experience that it needs to regain that spirit. This has been my top feedback every go survey is that it is being designed democratically where it needs to be led by experience.


If you want all features of language X (I doubt we will stop at generics) use language X. Stop trying to make all languages the same.

I work with programmers from OO background (java) and they can't even grasp the utility of functions as value or closures. Every damn "service" has an interface/generated mock and anemic model. They're desperate waiting for generics for go to "complete".

I fear the influx of OO programmers.


OO !== Generics, at all


I didn't say they're the same. I'm saying people coming from OO complain all the time of lack of generics, inheritance (besides composition) etc.

I teach basic go at my job to help people wanting to migrate, they start the go journey thinking go will do "less" because we don't have all features as their main language. Which reminds me of this phrase:

> 'You cannot reduce the complexity of your problem by increasing the complexity of your language.'


Two thoughts:

1.) In a "simple language" (e.g. without generics) each line of code is easy to read/understand. But reading/understanding the whole program or application is difficult. In "not-simple languages" it's the other way around.

2.) An important criteria to judge the future of a language is how foresight the language authors have. I read that the go authors said in the past that they were not ready to add generics because they didn't know how to do so in a good way. True or not, retrospectively adding generics is not a great indicator for good language design to me. Even if the addition of generics is a net-positive thing, I expect that Go will go in the direction of C++, having a lot of accidental feature complexity in the language.

I think it's better to do it like Lisp and keep a simple (but nonetheless flexible) core. Or do it like Haskell and design the language to elegantly allow as much abstraction as possible, moving carefully towards that goal. For these kind of languages, you better have people who have years long experience in exactly this: designing powerful programming languages. A developer can be the best in their own field, but they are doomed to fail when trying to build a future-proof, well designed programming language on their first attempt. (btw, not relating to the Go author's here)

An example where this didn't work is Angular - from the first moment that I got in touch with it, I knew it was built by amateurs. It's only a framework, but the difference to a programming-language is minor in the case of these kind of all-encompassing frameworks. It is not surprising to me at all that the completely redesigned Angular later on.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: