top | item 32027855

Go generics are not bad

232 points| ibobev | 3 years ago |lemire.me | reply

286 comments

order
[+] cookiengineer|3 years ago|reply
What I still don't understand is why golang has exceptions for "language constructs" like make() and append()... while those are unimplementable in golang.

It's pretty annoying that golang embraces static and functional patterns in its core, but it doesn't even have map/filter/reduce/forEach as generic implementations for all data types.

The lack of a "new" keyword and default values for struct's property syntax is also pretty annoying. Let alone the "hack" with annotations for decoding/encoding everywhere, just so that you can use reflect API in the encoder implementations.

For loop fatigue in golang makes it so unreadable in practice, and leads everybody to offloading manipulation of arrays/slices to helper methods, just to have the for loop there.

The library that fixes this as good as possible with generics is the "lo" library (inspired by lodash) [1]. It's worth taking a look at it if you're curious how to implement it for your structs.

[1] https://github.com/samber/lo

[+] danielscrubs|3 years ago|reply
Either you start making a language from a sound theoretical foundation and then implement it (Koka springs to mind) or you implement a language from some syntactic gripes and then try to find the theoretical foundation later… Go was impressive in the practical sense (compiler speed, channels in std and good tooling) but a shit show otherwise. I’ve completely lost any confidence that FANG will be able to make some great language because they always take the easy way (makes more business sense, see Dart). Academia on the other hand will make great languages that never reaches critical mass.
[+] throwaway234232|3 years ago|reply
I think in Java, you would need to do a type-class approach.

    interface Numeric<T> {
        T zero();
        T add(T a, T b);
    }

    static <T> T sum(Numeric<T> n, T[] v) {
        T summer = n.zero();
        for (int k = 0; k < v.length; k++) {
            summer = n.add(summer, v[k]);
        }
        return summer;
    }
[+] zaptheimpaler|3 years ago|reply
This seems very basic. Not to dunk on prof. Lemire, i know he has some great posts. This one is just barely scratching the surface of what makes a language good at supporting polymorphism or not.
[+] morelisp|3 years ago|reply
If I was a researcher focused on developing efficient algorithms for numerical data this example would also seem to be one of the most critical for my work.
[+] bediger4000|3 years ago|reply
This is good to hear.

There's something like a cognitive bias that makes me leary of genetics (in any language). Once you have some cross cutting feature, generics or object orientation, or in functional programming, those functions of type 'a->'a, or assembly language address modes, people get bogged down by trying to make everything in their work generic or object oriented or "orthogonal". Lemire's example is relevant to his topic, but it could be a poster child if someone else did it. They need to sum an array of int. So they spend 4 days getting a function and a generic type that can sum every numerical type, rather than 15 minutes to sum int arrays.

What's this called?

[+] avgcorrection|3 years ago|reply
Key phrase in the article:

> So, at least in this one instance,

Yeah, one example which is not even that useful. It might be cherry-picked for all I know.

[+] bouncycastle|3 years ago|reply
The site gives assembly code of the inner loop, but isn't the problem that there is additional overhead on the calling of generic functions?
[+] wahern|3 years ago|reply
AFAIU, as compared to pure monomorphization, yes, sometimes the model imposes runtime indirection. This is particularly the case for methods. However, for simple yet common cases, such as functions taking []T, the model permits inlining into the caller.
[+] chrchang523|3 years ago|reply
I believe Lemire's point is that the compiler is still able to inline the function despite the generic definition, so at least in this somewhat narrow case generics are a zero-cost abstraction.
[+] goto11|3 years ago|reply
Numeric types are weird in Java and .net. I'm more familiar with C#, but in C# adding two int's result an int, but adding two short's also result in an int. So addition is not polymorphic the way you would expect it to be. C# has a large number of numeric types, but below the hood it only supports arithmetic operations on three of them.

The problem is not with the implementation of generics per se, the problems is numeric types in those languages is a really leaky abstraction.

[+] davidkuennen|3 years ago|reply
Upgraded my production service from go 1.17 to 1.18 today. A nice 30% performance boost. Love go. It keeps on giving.
[+] glmdev|3 years ago|reply
I think Go's generics are reasonable, but not quite powerful enough to scale.

It's all well and good to generalize basic containers and functions (good, in fact), but I wish the language was better at inferring types.

I'm sure this is something that will improve with time, but a bit after generics were released, I tried to build a type-safe language evaluator in Go using generics and found them lacking the kind of type-narrowing necessary for fully-generic architecture.

Short write-up of my conundrum on SO, if anyone is interested: https://stackoverflow.com/questions/71955121/how-to-type-swi...

TypeScript has me spoiled. :)

[+] nemothekid|3 years ago|reply
I haven't looked into Go's actual implementation but isn't this kind of downcasted better suited for interfaces. Your code, as is, wouldn't translate to something like Rust which uses monomorphization; you are doing a runtime "inspection" of the types. This looks more in the domain of duck-typing than something that generics handles.
[+] bbkane|3 years ago|reply
Yes, I was trying to get it to infer the types for some code I was writing as well (a generic funcopt for two different types) and it couldn't - made me specify the type manually.
[+] socialdemocrat|3 years ago|reply
I think I am glad that kind of stuff doesn’t work, because that was pretty hard to read. Go is trying to keep things simple.

If you want a sophisticated type system with a language they complied to native code then use Swift or Rust.

I personally think it is good to have some choice in complexity level and that at least one language, Go, tries to carve out a niche in simplicity.

I am only barely convinced that generics was the right choice for Go.

[+] maffydub|3 years ago|reply
I don't seem to be able to post on his blog (400 Bad Request), so posting here instead...

Interestingly, it looks like Rust (and C via clang) both generate something like the following as their one-at-a-time main loop:

.LBB5_14: addl (%rcx), %eax addq $4, %rcx cmpq %rdx, %rcx jne .LBB5_14

It's one fewer instruction than the Go version (because it has combined the MOV and the ADD), although I don't know whether that would actually result in higher performance.

I say their "one-at-a-time" main loop, because they actually seem to generate lots of code to try to take advantage of AVX or AVX2 to add 4 or 8 values at a time if available and the array is long enough!

It might be cheating, but summing an iterator over numbers is built-in to Rust, so summing an array is just: v.iter().sum()

The fun thing with this is that it's not just generic over number types, it's also generic over any type of iterator, so I can even use it to sum over the values in a HashMap: hashmap.values().sum()

[+] wyuenho|3 years ago|reply
The title says Go generics are not bad, and then in less than a page this guy only compared one use case with Java's generics.

I'd expect a CS professor to be able to add a little more rigor in a blog post but I guess everyone is losing patience to read and write these days, even for the supposedly erudite.

[+] liuliu|3 years ago|reply
This is his blog, he can write whatever he wants. It is not a peer reviewed paper or even arXiv.

Someone submitted and people liked it, and it ends up in HN front page.

All I am saying, please don't put pressure on random blog writers. You can criticize the content without saying "CS professor can be more rigor".

[+] bborud|3 years ago|reply
If someone doesn't know how to address an audience you can hardly blame the audience for not responding. Academic papers are written to further academic careers. Their audience is not developers, but other people who are also not developers.

Of course there is going to be an impedance mismatch. What makes people important, effective and useful isn't what silly snobbery they use to put other people down, but whether they can effectively communicate their ideas to the audience.

[+] mirashii|3 years ago|reply
A lot of folks have commented on this being a cherry-picked example, but I haven't yet seen someone link to a set of examples that show the opposite: pathologically bad performance with Go's generics.

https://planetscale.com/blog/generics-can-make-your-go-code-... is a much more in depth dive into how Go's generics work that shows some of the easy cases, like this article, but also the more general cases.

[+] saagarjha|3 years ago|reply
This is like the most cherry-picked example that could've been chosen. Java can't do this because the primitive types are not objects. Mentioning the performance of this is hilarious because 1. it's Daniel Lemire, he should know better and 2. the performance of pretty much every other thing that uses generics is terrible because of gcshapes being passed everywhere and 3. Java literally has a JIT to make generics fast. And bear in mind I don't even like Java generics that much, it's just that Go does even worse. (But still better than when it didn't have it at all, I guess…)
[+] yarg|3 years ago|reply
> Java literally has a JIT to make generics fast

???

By the time it gets to the JIT, java generics have long since been erased.

Java's generic classes run at the same speed as everything else, because there's no actual generics there.

(Although I'd be very surprised if this doesn't change in the near future: https://openjdk.org/jeps/8261529.)

[+] margalabargala|3 years ago|reply
Most things first exist, then exist and are good. We've seen the start of Go generics existing, but we haven't seen them be as good or as fast as they will ever be.

Five years from now, Go will still exist. The implementation of generics in that future version of Go is likely to be much faster and better than what we currently have.

Put a different way, "Generics are slow in Go v1.18"

[+] socialdemocrat|3 years ago|reply
Cherry picked? When write programming code examples this kind of thing would be the first thing I would try.

I would rather call it cherry-picking if one avoided covering such a case to make Java look good. Seems like a pretty obvious problem to me.

Handling basic number types is pretty bread and butter.

[+] Thaxll|3 years ago|reply
How can Go does it even worse that Java where generics implementation is the worse of all modern language with type erasure.

Java is way worse than Go on that aspect.

[+] zamalek|3 years ago|reply
> Java can't do this because the primitive types are not objects.

So this excuses the awful Java genetic how? "It smells like sewage in the basement because the basement is full of sewage." In addition, generics over primitive/stack/value types (including mathematical operators) is working just fine in the latest iteration of C#.

[+] google234123|3 years ago|reply
You should write a comment on his post - he will probably respond.
[+] hrgiger|3 years ago|reply
Last time I check java didnt allow any generic array
[+] lma21|3 years ago|reply
It's been a while since i've went through any Java code. Why wouldn't the Java code sample compile?
[+] levoea|3 years ago|reply
you can't do `T summer = 0`, nor `summer += v[k]` as there is no arbitrary sum operation for Number (e.g a method `Number add(Number);` or `<T extends Number> T add(T t);`).

though this can be solved either with types, as mentioned in another comment (https://news.ycombinator.com/item?id=32029871), or simply by providing the sum operation:

    static <T extends Number> T sum(T[] v, BinaryOperator<T> adder) {
       T summer = v[0];
       for (int i = 1; i < v.length; i++) {
          summer = adder.apply(summer, v[i]);
       }
       return summer;
    }
    
    Float[] ft = { 0f, 1f, 2f, 3f };
    // which can also be written as
    // `float z = sum(ft, Float::sum);`
    float z = sum(ft, (x, y) -> x + y);
[+] saagarjha|3 years ago|reply
Java primitive types are not objects :/
[+] tapirl|3 years ago|reply
This article is too simple to get a full picture of Go custom generics. There are actually both good points and bad points.

To get a comprehensive understanding of the current Go custom generics, please read: https://go101.org/generics/101.html

[+] silisili|3 years ago|reply
Your statement may be correct, and your book may be excellent.

But insulting a free knowledge sharing article while posting your own site which is laden with 'buy buy buy' is rather poor taste, IMO.

[+] webkike|3 years ago|reply
> so far I am giving go generics an A

I guess I’ll wait until the full review then

[+] moelf|3 years ago|reply
"not bad" is a weird bar to cross for a language that:

    1. decided generics is not needed
    2. went on for a decade
    3. implemented a version of it that is not performance-sensitive
    3.5. $$ by google
[+] kgeist|3 years ago|reply
>decided generics is not needed >went on for a decade

The FAQ on the official site already back in 2013 (I couldn't find an earlier snapshot on webarchive) stated they were open to adding generics but weren't sure how to properly design/implement them without overcomplicating the language, citing also other priorities.

>implemented a version of it that is not performance-sensitive

It's less performant than the ideal but it's a tradeoff to avoid exponential code bloat found in C++. In our large C++ project which extensively used templated boost.signals the code bloat from using templates alone added a whopping 200 MB of machine code which we were able to shed off by switching to our in-house library. IIRC C# uses an approach similar to Go to share generated code with some type-specific conditions at runtime (at least Mono I remember shared generic code for all reference types)

[+] YesThatTom2|3 years ago|reply
The Go authors have never said #1: they said their waiting to decide how to best do it, and they want experience with the language to determine what problem needs to be solved before solving it

#3: they’ve clearly said that they’re doing “get it right before you make it fast”.

[+] zer0-c00l|3 years ago|reply
this article is hilarious:

1. go generics do this one thing 2. therefore they are good

Go generics are better than no generics but they're still limited, and that frustrates me.

[+] tgv|3 years ago|reply
The article really doesn't claim to briefly explore more than precisely that one thing. People just jump on the title plus some weird hate for Go.
[+] socialdemocrat|3 years ago|reply
Could you give some concrete examples? Something which requires a type of Go generics but which cannot easily be solved in another way?