Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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…)


> 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.)


I mean, they’re definitely not specialized and optimized before they hit the VM.


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"


Someone will read a blog post about slow generics, ignore how it evolves, and tell every coworker for then next 10 years that go generics are slow


The authentic HN experience.


"Java is slow"


Not as slow as .Net !!


I'm hopeful, but the blog post doesn't say "Go generics have room to improve", it says "they're not bad". And they kind of are right now. I can see a road to how they can get better but it's not really very honest to present just this example as indicative of how they work.


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.


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.


Type erasure has little to do with the power of generics at the language level. Haskell, with one of the most powerful type systems around, erases types to a greater extent than Java does (concrete types are still encoded in JVM bytecode, Haskell erases everything).


For example, by not allowing one to create a parametrized method. `func (self SomeNonGenericType) Foo[T any](param T) T` is not allowed, best one can do is `func Foo[T any](self SomeNonGenericType, param T) T`.

The reasons are explained here: https://go.googlesource.com/proposal/+/refs/heads/master/des... but the point is, you can have this in Java, but not in Go.


Why not use a free function? That is more flexible. Go isn’t Java, it is not a deadly sin to write free functions.

You look at something like the Reader and Writer interfaces and there are very few methods. The Go approach is to put more functionality in free functions such as fmt.Fprintf which allows more code reuse across multiple types.

In fact I view the heavy Java use of methods as an anti-pattern.


One surely can, and probably that's about the only option they have.

But for some `foo.Add(bar)` can be considered nicer than `AddFoo(foo, bar)` or `foopkg.Add(foo, bar)`. The semantic differences are mostly in in an aesthetic/stylistic/personal preferences realm, so it's hard to argue for or against it.


Except that in Java, the former is more common and in Go, the latter is more common. Really, the latter looks more like idiomatic Go, which makes the original complaint somewhat redundant. You might stylistically prefer the former, but then you're probably not going to enjoy using Go anyway.


Well, yes, name "Add" was a bad idea - collection management in particular tends to use free functions somewhat more - starting from the built-in `append`, hah. Although even that is not universal - consider e.g. github.com/deckarep/golang-set, which I believe is one of most popular set packages, based on number of imports. It doesn't strike me as non-idiomatic), it's all methods there and not free functions.

And either way, I'd disagree on the general principle. I'm not claiming to be well-versed in Go styles and patterns, but most well-designed libraries I've seen had exposed `foopkg.NewFoo()`, useful structs and constants (e.g. `foopkg.FooParams` or `foopkg.NoSuchFoo`) and the rest is typically interface methods.


Well, IIRC the strings package for example is pretty much all functions, and so is fmt (IIRC). You might not consider these packages “well-designed” but they are certainly idiomatic. I don’t have the inclination to go trawling through the standard library for other examples, but I will say from experience, as a former Java developer and now a Go dev, I’ve sometimes found Go‘s approach confusing because it’s more function oriented than OO.

So I guess our experiences differ, which of course is fine, but I respectfully don’t concede the point. :-)


Well, it means you can't have generic methods in an interface.


You can certainly use generic parameters in methods—the [T any] just has to be on SomeNonGenericType, even if it's not used inside the type itself https://rakyll.org/generics-facilititators


Yes, but that's completely different thing. If you move T you would change the semantics.

- func (self SomeType[T]) Foo(...) is a method for a class parametrized on T. So if you want foo.Foo(1) then it only works for foo that's SomeType[int]. You cannot then easily call foo.Foo("one") on the same instance.

- func (self SomeType) Foo[T any](...) - if that'd be a thing - would be a generic method on SomeType that works for any T. So you can foo.Foo(1) and foo.Foo("one") on the very next line and that would work.


You can instantiate it with [any] then use whatever you'd like https://go.dev/play/p/JeSuB_xYNEf, but I don't think it would work with a type constraint such as `int|string`, which would be a fair point.


Sure, but it's no longer generic, you're just using runtime types again.

This is extremely important, because it means `func (someType[T any]) foo(T x) T`, `x.foo(1)` would return `any`, not `int` like a generic function would.

In fact, if you're going to instantiate a generic type parameter with `any`, I would very much doubt you wanted a generic type in the first place.


Oooh. My bad. Thank you! I honestly thought that wasn't possible at all. Today I learned.

However, it won't work for return values, right? Because e.g. `func (some something[T]) hi(b T) T` would return `any` and not whatever it was provided (`int` or `string` respectively).

https://go.dev/play/p/IYoPUQg04sg

Also, I don't think this can work well with types narrower/fancier than `any`, e.g. I had trouble figuring out how to deal with

    type IntOrString interface {
        int | string
    }
    
    type something[T IntOrString] int
https://go.dev/play/p/acwjxdpHTJO


Type erasure is interesting. It turns out that not doing type erasure (combined with instanceOf-like things, like pattern matches on type parameters) breaks parametricity. Parametricity is a very powerful reasoning tool.

Of course, type erasure isn't the actual culprit in that case, but once you don't do it, it gets very tempting to allow pattern matching on type parameters...


I read an essay by a CS post doc taking about type erasure. He said getting to the point where you can do type erasure is golden. And then everyone makes the mistake of actually implementing type erasure. He said you consign yourself to no tooling and terrible debugging.


This.


You do realize that type erasure is the common way of dealing with (generic) types? Haskell also does type erasure, as well as basically every language outside C#.


You can have it both ways in C++ depending on your compiler flags!

And people wonder why we complain about C++..


> 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#.


It doesn't. Picking an area where Java is weak and where Go happens to be decent is the very definition of cherry picking. Go happens to do poorly in almost every other area and Java does meh in many (erasure, unsoundness, etc.) and excellent in others (profile-guided devirtualization).


really? Is Go that bad? Write a server that does communication or emulate select in Go in other languages. Go is really effective at writing things fast at the same time that it has value types and can scale not only in I/O but also in multi-core in a way that is easier than anything I saw before. I am a mainly C++ person but I must admit that the cost/investment ratio in Go is really good for writing server-side stuff.


> Write a server that does communication or emulate select in Go in other languages.

I just did that - I've been working on a very similar thing to goduplicator (a mirroring proxy), however with an additional requirement that it must not add latency to the primary communication path, e.g must connect mirrors asynchronously and buffer data instead of waiting for a slow mirror.

I chose Rust and the resulting code has been at the same time simpler, faster and uses 3x fewer memory than the Go implementation. I have not only select!, but also things like join! or try_join! at my disposal, way simpler to use than channels and waitgroups.

Also looks like closing connections in Go is a mess. In Rust I just remove a connection value from the vector and I'm done. In Go they have to close them manually, but defer is quite useless in async code, where the connections are passed to a coroutine.

In Rust I can also interleave work concurrently on a single thread with async, avoiding synchronisation. In Go I'd have to spawn goroutines and then coordinate them through channels which would be both more complex, more costly to execute and more risky.

So IMHO Go is definitely an interesting language to write concurrent networking code in, but feels quite incomplete to me. You get products but no sum types, you get select (which is kinda sum for channels) but not join (which is an analogy of a product for channels). You get semi-automatic cleanup with defer, but it is tied to a lexical scope ignoring the fact that goroutines can outlive it.


Well, for latency you do have to deal with the GC very carefully, but the investment/ratio in Go is very good. It is just not for absolutely every use case, like everything else in your toolbox.


Investment ratio in Go is only good because of how little you have to invest. In Rust the required investment is bigger, but you get a lot more in return. Not sure about which ratio is really better.

GC is only a minor reason I chose Rust over Go for a networking related project. Despite having a GC, Go definitely feels more low-level and less structured than Rust, and leads to code that is longer and harder to reason about.

It is very similar to how a language with only a goto instruction to do control flow would be definitely simpler/smaller than a language that supports functions, loops and conditions, but the actual programs written in it would be brittle and harder to understand.


> Not sure about which ratio is really better.

That is going to depend entirely on requirements. Fast delivery, fast execution, correctness and others. No tool for everything :)


> Is Go that bad?

I'm personally no fan of Go (mostly due to the ergonomics of the standard library), but all this about generics is throwing the baby out with the bathwater. Go has a fantastic generics design.

> cost/investment ratio in Go is really good for writing server-side stuff.

And this 100%.


I’m a big fan of Go, and I’ve never been bullish on generics, but I really don’t see what is good about this design. I’m really not interested in generics with a runtime penalty even if it makes binaries smaller. I also think the constraint system needs work, and there needs to be a mechanism for generic methods. It’s possible for Go to get all of these things, but the current form leaves a lot to be desired. Rust’s trait system is best in class as far as I can tell, and I’ve criticized Rust often in the past.


Some more context would be appreciated on why you think that Java generics are awful?


The context is what was said above: Java generics can't abstract over primitive types, they only work for objects. That is awful, and it is important, because this one limitation is the only thing that makes the implementation of generics in Java possible and simple.

If generics had had to actually support ArrayList<int>, they would have required significant changes to how ArrayList is stored in memory, and to how a lot functions which work with generic types are actually compiled. Instead, Java has the easy path in the implementation: since everything only works with reference types, there's no need for multiple copies of a function to work with different types.

Of course, this means that sum<Integer>(new Integer[1000000]) is going to be orders of magnitude slower than sumInts(new int[1000000]).


It is unfortunate, but that was the only way Generics could ship at the time in a backwards compatible way. But I don’t think it is fair to call it awful, especially knowing the context. Also, when it does show up as a performance bottleneck, it is trivial to fix.


You should write a comment on his post - he will probably respond.


I could but it would probably starve him of the context of people disagreeing with me


Does he read HN?


Why do you care about forwarding negative comments for author?


Negative doesn’t mean it isn’t constructive?


I'm pretty sure he does and has commented here before.


Last time I check java didnt allow any generic array




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

Search: