Hacker News new | ask | show | jobs
by hood_syntax 3146 days ago
Are the complaints about lack of generics really a minority thing? Writing separate functions to sort different types just strikes me as ridiculous.

EDIT: My original tone was a bit nasty in retrospect. Did a little research and while I still am on the generics side, the current situation seems at least workable for a good number of use cases.

7 comments

My concern is less about writing a bunch of separate functions, but rather the way this hampers the abstractions that are available to you everywhere in the language. You don't have map or fold! And that's only the very tip of the iceberg.
Yeah, I wish go had more functional stuff, like .map(), .filter(), etc.

On the other hand I enjoy that when I pickup a package someone wrote there is not a bunch of different meta-programming using templates.

Generics sometimes lead people (including myself) to over-engineer solutions... usually because we want as much compile safety as possible. But at what cost in complexity.

I'd rather read x.filter().map() than read the loops someone had to expand them into by hand.
I groan every time I have to implement the 15th type-specific loop implementation of what I'd do with a single map or fold in Ruby/Elm/Haskell/JS/TS/Crystal/every other language I use. It's not a lot of effort but it's a lot of mess and cruft.
Check out goderive
linking this for you: https://github.com/awalterschulze/goderive

i wish i could laugh, but i can't. the lengths people will go to work within such an impoverished language, and by means so antithetical to the go philosophy.

Apparently, the project leaders see it as a lower-priority (though not unimportant) issue. Looking at the adoption figures, they seem to be right.

I personally stay away from Go due to lack of generics and other expressiveness issues. People who have to work with it write code generators on top of the compiler, because the compiler team won't include it into the language. (I can see how it's not an easy thing to do; Russ Cox wrote a nice piece about it.)

Java prior to version 5 had the very same problem. It was wildly popular nevertheless. It took Java 8 years to gain support for generics, though, about as long as it took Go to not yet obtain them. (By that time, Java was widely considered the Cobol of 21st century; I think Go must be successfully stealing that crown now.)

> Looking at the adoption figures, they seem to be right.

Not really, for me Go is a bit like JavaScript.

I have to deal with Go thanks to Docker and K8s, doesn't mean I would use it when given the option.

Would you rather have them use C++? In this regard, Go is a lesser evil.
Yes, as long as it was modern C++, not C compiled with C++ compiler.

Or any modern natively compiled language.

That's kind of part of the problem - with C++, or Java, or C#, you can write in 15 different styles, syntactically valid but quite possibly unreadable to another programmer who is experienced in the language. That's because there isn't one idiomatic standard for the code, there's 15++, and the language has the complexity that enabled that mess.

If you want to work in a kitchen sink language (and I often do haha), just use one of the many available.

But perhaps you're missing the value of simplicity? It is after all the main strength of Go, and a rare commodity in software engineering these days.

Overall I think it's a shame to have less choice, and we lose something by making all languages too similar (in both features, and at the meta level in complexity).

Or Rust, if you really really want to avoid C++.
But you don't need to do that. It's a bit clunkier than generics, but it's usable: https://golang.org/pkg/sort/
I have had the need to sort data rarely enough that it has not been a real problem to me (most of the time that data came out of a relational database that did the sorting for me).

But when I did have the need to sort stuff, I have found it annoying. Even C has a more convenient solution for this problem. (Admittedly, Go's sort.Sort can work for data structures other than arrays/slices.)

Like I said, it has not been a sufficiently large problem to really bother me, but it is not a pretty solution, IMHO.

Of course, one might argue that the creators of Go knew that sorting things was not such a common problem for their target audience, and thus they made the trade off to make sorting suck in return for overall simplicity; Go's type system makes it practically impossible to implement a type-generic sorting function like C's qsort(3) without sacrificing performance or making the language more complex.

So maybe that is one of the trade offs we have to make. I still wish for a better solution, even if that may be impossible without turning Go into another C++. And if you like C++, that is totally fine, it has a number of very big advantages. But then you do not need Go to become another C++ if you have the original right there; and even if you wanted to get away from C++ without giving up the benefits it offers, D looks like a more promising alternative.

You're still writing separate functions (implementations of sort.Interface) to sort different types.
For sorting slices you only have to pass one function:

  sort.Slice(s, func(i, j int) bool { return s[i].X < s[j].X })
But this is something you often need to do for any non-trivial type, even with generics. There's a reason that C++ std::sort<> takes a comparator, or why Rust requires you to implement the 'Ord' interface.

Again, it's a little clunkier in Go, but not unusably so.

You need to define how to order things in other languages (though not in languages where you can automatically derive implementations like Haskell or languages with built-in polymorphic comparison like OCaml) but you don't need to define how to swap elements over and over.
Yes, I'm aware. That's the clunky part that I was referring to (although, you can still avoid it for slices, and simply pass a single compare function.)

Go is far from my favorite language, but the sheer amount of bad criticism by people that clearly don't use the language annoys me.

A little boilerplate hasn't killed anyone. If you're talking about "100s of different Enterprise Business Objects (TM) structs", something is probably off in the overall program design and/or code-gen should probably be introduced regardless of the 'sort' (and related typically-generics use-cases) question, as that sounds like something to be largely derived from pre-existing schemas of some sort..
> A little boilerplate hasn't killed anyone.

It's annoying, especially since basic generics are simpler than interfaces.

> If you're talking about "100s of different Enterprise Business Objects (TM) structs"

I don't write enterprise software. I sometimes need to sort things.

I did a little research after I posted my original comment and came across that. It does seem usable even if it's not what I'd prefer
Yes, it is minor. Go has interfaces, so you just write a Comparer interface and sorter that takes two Comparers and then anything implementing Comparer is sortable.

*Note: author has written basically nothing in Go and only has a passing familiarity.

So you define the Comparer interface with a Compare method. What does it look like?

If I have a struct X, then I might write it as:

    interface Comparer {
        Compare(other X)
    }
But wait, now I have to define a new interface for every type since "Compare" takes the type X in its signature so it doesn't work for type Y... If only I could define an interface that was for an unknown type T and then Compare was for that.

But that's exactly what generics are.

You know how Java has .equals? Go doesn't have an equivalent concept. Test code is neigh unreadable because there is no generic way to compare two structs of the same type. This is a similar problem.

I am admittedly thinking of an interface from how they work in Java, so I may be making incorrect assumptions about how they work in Go, but wouldn't I have to do that anyway? In what sense can the compiler work out how I want to compare my types? If I hand it some vec3s, for instance, does it know I want them sorted by x value, or by length?

By defining an interface that assures the compiler each type has a compare(x) method, I can write a generic sort function that takes any two comparable objects and sorts them based on whatever the class of those objects decided was the ordering criteria.

If your `Comparer` is an element type (as opposed to Go's `sort.Interface` abstraction over collections), this is going to be horribly unperformant. Suppose your `Foo` type implements `Comparer`, and you have a large `[]Foo`. Then you'll first have to iterate over each element in `[]Foo` and add it to your `[]Comparer`, probably with an allocation per element. Then each invocation of `foo.Compare()` is going to be indirect as well (in other words, an interface lookup and a function call, i.e. no inlining). Mind you, this is still faster than many languages, but it's not the kind of performance people expect from Go.
From this description, I'm getting the impression that Go's interfaces are not as much like Java interfaces as I had assumed.

Never the less, I find that generics usually are just an over-engineered solution to any given problem.

The primary differences (I think) are that everything in Java is already a pointer, so there isn't an extra alloc to make an interface. The same is true in Go if your comparables are all pointers. Probably the more significant difference is that Java has a JIT compiler, which may be able to inline all of those calls to Compare(). This is largely why Go's `sort` package abstracts over the collection, not over the element.
I don't see any reason why the interface lookup couldn't be done at compile time in this instance.
I don't think there is a technical reason. It's probably not something they'll do for a while because it probably requires some significant reorganization of the compiler and making a passably fast implementation is probably quite hard (the Go community places a premium on fast compiler times, rightly or wrongly). Also they seem to value predictable optimizations. Not sure how keen they would be to add an optimization that works until a seemingly inoccuous code change prevents the compiler from guaranteeing that all elements in the slice have the same concrete type, making performance (probably) significantly worse.

Again, I don't necessarily agree with the Go team; I'm just guessing the are some of the concerns they're weighing.

Ah I misunderstood the previous comment. Sure, if you have a list of pointers to values that satisfy a given interface, dispatching at compile time would not be trivial.
I've been using Go at work and for my personal projects for over 4.5 years. I rarely have to sort. It's not a big deal for me.
It's not about sorting, it's about every single abstract operation on a collection or other type of container. You either have to use primitives, or reimplement everything yourself on each new type. Most people just use primitives. This proliferation of primitives is what I actually dislike about reading and writing code in Go, but it's caused by not having a way to define abstract data types that work well.
There are a few good templating (gengen, genny, etc) solutions for generics in Go, but yeah, it would be nice if it was baked into the language.
how often do you sort? most of the time you would do a database ORDER BY instead.
I...I can't even believe you just said that. Go really does have a unique core audience.
(This is not endorsement of the parent post; I don’t agree with it either.)

This is how you can sort slices of arbitrary types in Go:

https://play.golang.org/p/VHJW9lVY9b

I’m not sure about you, but it feels very reasonable to me. It’s not anywhere near the top of my list of pains that I feel when working with Go everyday.

more like tree_of_interface{}

interfaces are a poor substitute for generics, but that's by design. the minute they add a reasonable implementation of generics Go will begin to metastatize into something unrecognizable as people will no longer be bound by the limitations of the already existing core generic types. the core developers really do not want that to happen.

it's a highly opinionated language and for some reason some people enjoy writing mountains of for loops so that their code can remain "simple" as they produce a maze of twisty funcs all alike. i mostly see a for loop or two per func with a map and some slices repeating the same patterns over and over. that's the way the language designers want it.

i work with a lot of go programmers and they are all fine and good programmers. that makes it even harder to understand why they enjoy all the boilerplate and even defend it.

> Go really does have a unique core audience.

I..I can't believe you just said that. Such generalizations based on a single comment by an anon.

Ignorant here (I've never done database work): What's wrong with asking the database to do the sorting for you? (assuming you do work that involves a database. Perhaps this assumption itself is the problem?)
I understand why that would make you uncomfortable. ;-)

But to be honest, more than 50% of the cases where I needed data to be sorted in a certain way, that data came out of a relational database, and adding an ORDER BY-clause to the SQL query was so much easier than doing it myself.

I do agree with the second part of your comment in that Go has a certain type of application where it really shines. But that type of application is not uncommon, and when you hit the sweet spot, it really shines.

care to enlighten me? if you have an index you could get the sort for free, also you never pull all results, so why would you pull all, just to sort, on the web I would love to work more in ram, but since you assume a server will crash, you need to save the actual state in the db.
I didn't down-vote, but what jumps out to me is the assumption that you'll generally be pulling data from a database. Yeah, that's almost certainly true in a web-application, but it's not going to be nearly as universal in a systems-programming niche which is where Go appears to be excelling (as perceived by someone outside of the Go community).

So ease of implementing sorting in your core language without making assumptions about the operating environment of code written in that language sounds like a reasonable demand.

Thanks, that was great. :D
can't tell if joking
Assuming developers are rational in their choices, by far most of the Go developers still prefer to use the language without generics.

I think its just a minor problem compared to all benefits.

> by far most of the Go developers still prefer to use the language without generics

I've been writing Go regularly for at least 5 years, and my guess is that at most 60% of Go developers prefer no generics. I also think that number is climbing.

I write Go at work but have never heard anyone say this.

Can you explain why you would prefer no generics to this dumb junior engineer who wants to be enlightened?

Not the OP, but here are my 2¢. Introducing generics in a language implies a deep revision of the type system, which risks to become much more complex to understand. Moreover, this might have a bad impact on compilation performance (compare C++ compilation times with Go compilation times and you'll understand what I mean).
C++ compile times are huge for many reasons, but templates are only one of them. And note that templates aren't the same thing as generics.

Java has near-instant compile times and has generics. Likewise for Kotlin. Generics don't have to mean slow compiles.

It sounds like you're saying Java's generics are somehow superior to C++ templates.

For those who don't know, C++ generates a separate instance of the code for each type which means that it can operate directly on a value instead of through a reference/pointer and it also makes it possible for the compiler to inline the type-specific code in many cases. Ie., it has the potential to be much more efficient. That is why it works the way it does. The downside is that the code generation slows down compile times, as you noted.

Java simply casts a reference to an instance of Object to the type on behalf of the user. So you effectively have generic code the way it's done in C (void*) and Go (interface{}) but without the explicit casts.

Maybe the JIT offsets many of these inefficiencies but making that comparison is beyond me. I only bring this up because you made it sound like Java got generics completely right and C++ got it all wrong, and with the amount of flak C++ gets these days I think it deserves a little help now and then.

I was not referring to templates only, but to the overall complexity of the C++ language. Examples: syntactic ambiguities, the preprocessor... Templates are a big part of the problem, but not the only one for sure. Languages like Free Pascal have outstanding compilation times because they privileged language simplicity, and IMHO Go falls in the same ballpark.
To be clear I prefer generics. The cases for why other Go developers don't want generics seem to mostly fall on "it will change the language / encourage people to write stupidly complex code" to "it's too hard to implement". The first one is valid--Go's constraints give it this nice property that there is (more or less) one obvious way to write most programs, and that will go away with generics; however, I think that cost is worth the gain. The other criticism seems like a cop-out; the Go team has far and away more than enough talent to slap generics onto the language.
Not OP, and this is only one aspect to consider, but it's from experience - when generics were added to C# the language became nicer to use, but it caused a lot of churn and added complexity. The base libraries have the original non-generic classes as well as newer generic ones.

As I've written above, there's huge potential to be the language that doesn't churn, when just about everything else does these days.

yes, but those of us who would desperately like to use Go because there are vanishingly few GC languages that compile to native exe AOT, won't because of no generics. So it is a bit of a tautology.

How useful they are depends greatly on what you are doing, and programmers do a great many different things. For many kinds of library development, they can save you massive amounts of time and code, and/or lead to much better performance vs the workarounds available.

I wonder how many people who prefer Go without generics are coming from C++ templates, or Java generics, vs C# or F# generics.

Have a look at .NET Native with .NET core, it support AOT compilation with GC, and a very fully featured language and cross platform targets.

see https://blog.rendle.io/what-ive-learned-about-dotnet-native/ https://stackoverflow.com/questions/29609993/difference-betw... https://docs.microsoft.com/en-us/dotnet/framework/net-native...

To be clear, I agree with you. I was just responding to the OP's claim that the Go community overwhelmingly rejects generics with my perception that at most 60% of the community feels that way. I am very much in favor of generics.
What's the big need for an AOT native EXE for you? You just don't want to copy a directory tree instead of a single file?
Not OP, but I can't execute a directory tree. I always seem to need a runtime, and often additional libraries and a bunch of `$*PATH` munging so the compiler locates the _correct_ dependencies.
Performance is often the answer.