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.
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.
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?
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.
> 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.
Or in Java's case annotation processors.
Yes, not everything gets exposed, but it also does the job.
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.
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.
That being said for a lot of other uses, you really do want high quality data structures beyond "array" and "dictionary."
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.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.
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.
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.
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.
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.
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 ;)
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.
- 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.
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.
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.
static boolean isPrime(int n) {
return switch (n) {
case 0, 1 -> false,
default -> !IntStream.range(2, n).anyMatch(i -> n % i == 0)
}
}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.
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?
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.
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).
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
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 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.
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, .....
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.
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.
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.
- 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.
* 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 isn't as if we never coded without generics before.
If you really need generic structures you can use the interface type but generally there's a simpler solution that doesn't require generics.
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.
> 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.
That said I don't think there's much "hope", from the looks of things it is coming to Go.
I understand generics well, I just find code bases that use a lot of abstractions harder to read than Go.
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.
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.
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 :)
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)
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".
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.
Using generics isn't 'clever'. It's like saying a loop is clever. It's abstraction, the opposite of clever.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
Doesn't mean many of us want to keep living on that world.
I advise reading books like "From Mathematics to Generic Programming"
In my 20+ years of development, I've definitely realized everyone's brains and approaches work differently.
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 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.
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).
That is what those CS algorithm and data structures lectures are for.
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.
https://news.ycombinator.com/item?id=28254411
The "solution" to this is copy-pasted boilerplate code with casting for every call.
Hello-world level projects with 20 class deep hierarchies of generic classes, just to make it "generic" in case you need it later.
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.
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.
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.
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.....
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.
Although in typical C fashion is a bit of kludge.
Here is an example, https://codereview.stackexchange.com/questions/101816/generi...
And for the record, I'm mostly in favor of adding generics to Go.
[0]: https://talks.golang.org/2015/simplicity-is-complicated.slid...
I do need and want generics. So... Whose needs and wants are more important?
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...
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.
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?
Regardless of that, returning a tuple of size 10, especially with different types, would almost always be a bad idea.
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.
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.
What do you find confusing about that code?
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.
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.
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.
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.
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.
And like i said generics have a place its just a way smaller place than many people think
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!
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.
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.
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.
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.