47 comments

[ 2.9 ms ] story [ 35.2 ms ] thread
Am I the only one who’s absolutely shocked that Go finally is embracing generics?

Does anyone have a bit of an inside view into what changed in the perspectives of the language maintainers?

I’m not buying the “it took us 20 years to understand how to do it correctly” argument, as this is something you explicitly take into consideration when designing the language or not. And it was specifically not a part of language design, and is much harder to retrofit (backwards compatibility).

So what changed?

If bug free binary search implementation can take 16 years, I am ready to buy generics implementation could take 20 years.

> In his landmark book The Art of Computer Programming, legendary computer scientist Donald Knuth noted that although the first binary search algorithm was published by John Mauchly in 1946, the first bug-free version was not published until 1962—taking a staggering 16 years to get right.

and they're still worse than the 1970s state of the art lol
Unfortunately nothing changed. They wanted generics all along.

The Go ecosystem was a delicate, special thing. It was a wholesale rejection of the malignant consultancy takeover of programming that had festered and spread for the previous 15 years. Introducing generics was a grievous error, and they just keep making it worse.

It used to be you could look at any Go code from any author and pretty much instantly understand it completely. That’s no longer the case.

It used to be you would work on a problem, just writing the code from top to bottom. No time wasted fiddling with abstractions you’ll never use. You’d grumble about it, but succumbing to the temptation was impossible. That’s no longer the case.

Surely it was pressure from devs to make Go look like every other language they begged to change then abandoned for the new hotness.
> Am I the only one who’s absolutely shocked that Go finally is embracing generics?

Yes. Everyone else knows that they were always expected to be included at some point, as told when Go was first announced to the world: https://www.youtube.com/watch?v=rKnDgT73v8s&t=3267s

> So what changed?

Philip Wadler, of Haskell fame, decided to help? This was well publicized on HN at the time.

> I’m not buying the “it took us 20 years to understand how to do it correctly” argument

If I recall correctly, Pike suggested that they never would have been able to come to that understanding without the outside help. I can understand why you are surprised that it took 20 years for someone with the right expertise to show up. You can get the sense in the above announcement that even the Go team thought that open sourcing the project would attract the right talent far, far earlier. But, now that we have hindsight, we can see that all the experts on HN who could have been the missing positive contributor were too busy complaining that Go didn't have generics. There wasn't enough time left for them to lend a hand. There are only so many hours in the day.

This level of generics actually has me interested a bit in Go now.
You can take my place, as the same changes make me want to leave.
Those Generics syntax in Golang seems so hard to read.
It is verbose but inference helps a lot to keep it “tidy”. I always find myself increasing my focus a notch when I start dealing with generics. It’s one of the things I use only if I really “need”.
imho it is much better that C++ equivalent
It does, but at the same time it's not "normal" code; I see it much like Typescript's advanced types, ultimately it's something that mainly lives in libraries.
stared at it for a bit and im mostly certain i prefer it to java. at least writing other go = its not bad for me to break apart the signature line on a generic

java feels kinda unhinged the more that i look at it

    public static <T extends Comparable<? super T>> T max(Collection<? extends T> c)
:x i wonder if anyones done something like this, would be super unhinged

    Map<String, List<Map<Integer, Optional<Pair<String, Function<? super List<? extends Comparable<?>>, ? extends Map<String, ?>>>>>>> config;
go seems to get a lot of flack around these parts. i kinda lurv it though, just getting compiled binaries out of not much code and not needing a runtime to do shtuff. once i got a wrangle on goroutines i dunno i feel like its pretty solid for webapp backend which is mostly what i use it for
Can generics be used to improve error handling and eliminate the if err pattern?
No.

I kind of want to leave it there. But that will probably be looked on disfavorably.

I've seen at least a dozen attempts. It's not like it's hard to write it out. There's maybe a couple of variants but they're all just a handful of lines. The problem is, once you have an Option in hand, you end up trading:

    val, err := whatever(...)
    if err != nil {
        // handle error
    }
    // use val
for

    val := whatever(...)
    if err, isErr := val.Error(); isErr {
        // handle error
    }
    realVal := val.Value()
    // use realVal
What you win in nominal safety, you're definitely losing in convenience.

There's also no win in trying to offer a monadic interface like

    finalVal := whatever(...).OnVal(func (val Value) opt.Option[Result] {
        // use val
    })
because that's the minimal specification of an anonymous function in Go, so it's very inconvenient. Even if that was trimmed down, nested functions are still problematic in other ways. And you still have to unpack finalVal anyhow.

Really the solution is, install golangci-lint, turn on errcheck [1], use a pre-commit hook to make it a commit failure if golangci-lint fires, and that pretty much covers the problem in practice.

One of the problems with Option/Result/etc. advocacy... not the pattern itself, the advocacy... is that it is generally are presented, implicitly or explicitly, as if the alternative is C, with its errno and the need to not just check an error value, but remember to go actively seeking out errors constantly, making it easy to forget. But by modern standards, that's completely pathological.

If we rate error handling techniques on a scale from 1 to 10 (best), C here is a 1, and standard Option is maybe an 8 or a 9. The way Go does it is maybe a 6; it is completely true that you can neglect to handle an error (see errcheck comment in previous paragraph), but it is in your face that an error is possible, and that's really most of the problem. Putting Option/Result/etc. is not always a "go from 1 to 9" result. "Go from 6 to 8" is a much less impressive proposition, and the other inconveniences that come with it in Go tend to overwhelm the gain. I use errcheck all the time, and even in the Before Times when I was writing it all by hand it really didn't fire all that often. Especially if I exclude test code. In an AI era this hardly rates at all. AI never neglects the error.

Whether it does the right thing with it, now... that's another story entirely.

Read those error handling clauses if you're writing Go with AI. I really don't like what I've seen AIs do with them by default. What I've seen out of AI has been very thoughtless. Nominally correct in some weak sense, but thoughtless.

[1]: https://golangci-lint.run/docs/linters/configuration/#errche...

As of June last year[1] the Go team have pretty much drawn a line under this issue, with a very small amount of wiggle room to possibly reopen it at some point:

"For the foreseeable future, the Go team will stop pursuing syntactic language changes for error handling. We will also close all open and incoming proposals that concern themselves primarily with the syntax of error handling, without further investigation.”

Personally I'm OK with this, I didn't see any of the (many) proposals as a definite improvement. They all had trade-offs.

[1] https://go.dev/blog/error-syntax

> Can generics be used to improve error handling and eliminate the if err pattern?

With the newly added type parameters on methods you can now implement monads that do the function chaining and error handling like in Haskell. But it still wouldn't look as nice imho. This will probably become the next Golang anti-pattern.

Automatically draining http response bodies is a risky silent behaviour change. I think it will be an improvement for most applications, but it's very subtle if you were relying on the old behaviour
This is quite of a big release and I like the new methods on generics.
Adding simd in std and even being used in map is nice. Would have to look for places to experiment with it in hot loops in code I have.
generics were a slippery slope. give it a decade and Go will be indistinguishable from c++
Go's standard library has always been it's strength, especially the crypto package! Lovely stuff.
Tried running a couple of examples in the tour, but ran into a few errors.
This: "(b Box[T]) Map[U any](f func(T) U) Box[U]" is the type of cognitive weight I was happy that Go avoided.
>The quieter but bigger change

I really wish they didn't use such stupid LLM-isms.

> interfaces still can’t declare type-parameterized methods

What would an implementation look like? Wouldn't it be quite different from the existing one because it has to rely heavily on indirection because (limited) monomorphimization is not possible?

One thing I think generics in Go is missing is the <?> concept in Java.

If you're taking a List[T] and all you want to do is to call list.size() then you don't care what type of list it is. In Java you can write a function which takes a List<?> but in Go you have to write List[T] so then the question becomes what is T? You have to make the function (or type you're a method on) generic. If you make the type generic then every user of your type also needs to specify T, etc.

I don't think it would be impossible to add that to Go. Allow List[?], which matches a List with any type parameter. Calling functions which don't involve the type parameter like list.size() would be fine, calling a method returning the type parameter like list.get(n) would return "any", and methods taking the type parameter like list.set(n, obj) would probably not be callable.

I am all for using LLMs to generate value but a little more editorial review can't hurt.

> The quieter but bigger change: the classic encoding/json (v1) package is now backed by the v2 implementation under the hood.

This is fantastic content nevertheless.

>func (b Box[T]) Map[U any](f func(T) U) Box[U] {}

That's completely unreadable.

It's showing you it's blending the `Box[T]` with a function that takes a function that applies over `T` to get a new type `Box[U]`. It's a bit convoluted, and it's also unnecessary. I think it's more readable like this:

`func (b Box[In]) Map[Out any](f func(In) Out)Box[Out]`

Or

`func Apply[In, Out any](in []In, f func(In) Out) []Out`

Which can be used anywhere and is not tied to a "Box"

Honestly if it wasn't written in go it'd fit right in with Java, with a Verb in the kingdom of Nouns.

Instead of having each language bring progress in a different and/or novel, we get this, old java features that comes 20 years-in after the making.

They're probably useful, but clearly not sexy (as golang in general).