7 ms·
I am not quite fond of the idea of having C++/Java Style Generics (I simply don't like the syntax that comes with it), but I see that the current system is brok
by JepZ 8y ago
I am not quite fond of the idea of having C++/Java Style Generics (I simply don't like the syntax that comes with it), but I see that the current system is broken.
From my current perspective I think fixing some things related to Go Interfaces and Container types (Maps/Slices) should make them even more usable than they are now and making them pretty much the thing people want to use Generics for. Currently, it looks like hell to build something with the empty interface and using a lib which is built around the empty interface doesn't look nice either.
Using Go maps is sometimes awkward too as they require some weird attribute 'comparable' which some types posses and others don't. Why??? I mean why can't the Go authors use their own language construct and use an Interface like:
interface Comparable {
Comparable(interface{}) bool
}
Similarly, I find it awkward that we still can't use Interface Slices[1]. I mean I understand it isn't simple to implement but certainly not impossible.
One thing on my todo list is writing an experience report with better examples. It's priority just got up-voted ;-)
[1]: https://github.com/golang/go/wiki/InterfaceSlice https://github.com/golang/go/wiki/InterfaceSlice
- heavenlyhash 8y agoCounterpoint: coming from a Java background, I find totally freely-definable interfaces for some features used by core container types to be a little worrysome. In Java, I can't count the number of times I've debugged either correctness bugs or 'merely' horrible performance bugs based on incorrect implementations of Equals or Hashcode methods. Implementing a non-commutative Equals method is a colossal footgun, and incredibly irritating to debug, and yet somehow it happens time and again. And there are other, subtler, arguably "correct" and yet but extremely unwise things one can when do given Java-like maps implemented on the Equals and Hashcode interface methods. For example, one can define two types T1 and T2 which both claim to be (commutatively) equal, and define some interface TX which is implemented by T1 and T2. Using a Map<TX,Object> and inserting members by T1 and T2 now has an interesting form of semi-defined behavior: if inserting "equal" keys, whichever type was used first will be persisted. Imagine doing this in some concurrent map, and imagine that T1 is 8 bytes and T2 is 400 bytes; enjoy debugging those occasional OOMs. I've been very happy not to ever have this particular problem when defining the key types in my Go maps.
- zlynx 8y agoGo could automatically create hidden Test methods for methods used by core types like maps. If Equals and Hashcode have to be correct, then these tests could ensure it.
- dtech 8y agoJava does equals and hashCode horribly. Granted, they did not have Java to learn from but it's what we have. Kotlin and Scala solve the problem pretty decently. Equals and hashCode are automatically generated for data/case classes. I've never encountered a messed up equals in Scala and only very rarely felt the need to override the generated equals, and then you make a very conscious choice to do it. C# does it even better, there is a IEquatable<T> interface that defines the `bool Equals(T other)` method. It partially solves the irritating universal equality. If I would design Java today Object would have no methods and there would be a stdlib interface defining `equals(other: T)`, and data classes would automatically implement that interface
- naasking 8y ago> If I would design Java today Object would have no methods and there would be a stdlib interface defining `equals(other: T)`, and data classes would automatically implement that interface This polymorphic equality is the default in some functional languages, like OCaml. It's fine for simple cases, but you really do need overloadable equality. Equality as a method in OO is a problem because of the asymmetry though, and often an even bigger problem because such languages typically permit pervasive null.
- dtech 8y agoThere definitely are cases where that's not enough, but it's a 95-99% solution. The problems you mentioned: Classes could change equals to accept subclasses. Then subclasses could not alter equality themselves. T implementing `equals(other: U)` for some U solves a lot of other use-cases, but could make equality asymetrical. Reference equality would still exist, but would not be the default, e.g. Java's `equals` is Scala's `==`, and Java's `==` is Scala's `eq` operator (which you almost never use)
- naasking 8y ago> C++/Java Style Generics C++ and Java generics are nothing alike, so there's no such thing as C++/Java-style generics.
- wtetzner 8y agoI think they were just referring to the angle-bracket syntax.
- gshrikant 8y agoCan you point me to a resource with details about this? I'm relearning C++ and a comparative evaluation of a language feature like that would be very useful.
- pjmlp 8y agoCheck Tour of C++, 2nd edition is updated for C++20.
- JepZ 8y agoIn fact, I am not completely sure about the state of Generics in C++. AFAIK the term 'Generics' was coined in context of Java, but C++ has a similar concept they call Templates. Maybe you could elaborate on what is the exact difference between those two concepts?
- mike_hearn 8y agoTemplates can do a lot more than generics can, but come with corresponding downsides. Templates are basically a form of copy/paste style substitution with fancy bits. When you instantiate std::vector<char> in C++, the compiler creates an entirely new class with "char" substituted in everywhere the type variable appears. This class has nothing in common with std::vector<MyStruct> - there's no common superclass, the compiled code may or may not be shared depending on low level details of your toolchain and the types involved, etc. That in turn means if you want to write a function that works for any std::vector you must also use templates, and your function will also be substituted, and your final binary will also have many similar-but-different copies of the compiled code, etc. However because of how C++ templates work you can achieve some pretty astonishing things with them. In particular you're getting code compiled and tuned to the exact data type it was instantiated with, so you can go pretty fast with certain data layouts. Java generics seem superficially similar but in fact are different. In (erased) generics, a List<Foo> and List<Bar> are exactly the same class at runtime. They're both just List and both just contain objects. The compiled code is the same, you can cast between them if you know what you're doing, etc. Likewise if you write a generic function that works for any list, there's only one copy of that function. Generics have different tradeoffs: they're less powerful (you can't write a regex library with them for instance), and they don't auto-tune themselves to particular data types ... at least not until Project Valhalla comes along ... but they're backwards compatible with pre-generic libraries at a binary level and they avoid an explosion of compiled code bloat, which is a common problem with templates.
- stcredzero 8y agoI see that the current system is broken. Just where is the line between "tedious but workable" and "broken?"