7 ms·
> Also, Go is currently lacking generics (read: C++-like templates) because of the rationale "we do not yet know how to do them right, and once you do them wron
by kouteiheika 8y ago
> Also, Go is currently lacking generics (read: C++-like templates) because of the rationale "we do not yet know how to do them right, and once you do them wrong everybody is stuck with them"
To be honest C++-like templates are probably the worst way to do generics. C++ "did them wrong" by picking C++-like templates and now everybody in the C++ community is paying the price being stuck with horrible unreadable error messages, abysmal compile times, an explosion of complexity (SFINAE, CRTP, metaprogramming using templates, template specialization, etc.) and a myriad of other problems.
I sincerely hope Go doesn't adopt C++-like templates. Fortunately C++-like templates are not the only game in town, and there are other, a lot more saner and principled flavours of parametric polymorphism out there.
- munificent 8y ago> To be honest C++-like templates are probably the worst way to do generics. I don't think this is fair. Any approach to parametric types has to make some very hard trade-offs. C++ sacrificed: * Fast, modular compilation * Straightforward error messages * Low cognitive overhead In return: * Templates perform as fast as hand-instantiated types. This is something some other languages like Java definitely cannot claim and is a key requirement for C++. * Template functions can have complex constraints on their template parameters. Deferring type checking until after instantiation means you can do all sorts of interesting things with the template parameter and as long as all of those operations happen to work with the instantiated type, it's allowed. Most other languages place much more stringent restrictions on the expressiveness of generics. For example, in Java, you can't do "new T()". In C#, you can, but you can't do "new T(...)" with any kind of argument list. Those are both really valuable features, especially in a language where performance is so critical.
- weberc2 8y agoRust's generics don't have overhead and the error messages are at least as sane as Java's. I think the problem is with how C++ implemented its templates.
- burntsushi 8y agoC++'s templates are more expressive than Rust's generics. Rust is adding stuff to try to catch up (specialization, const generics). You might see the extra power as an anti-feature, and that's fine, but it's undeniably useful to lots of folks and is undeniably a trade off.
- masklinn 8y agoBut doesn't the fact that Rust is managing to add these features mean the lack thereof is not a limitation of classical parametric polymorphism? Not that they're necessarily trivial to integrate, especially when the people working on it would rather try for the stars of genericity (e.g. dependent types versus const generics).
- burntsushi 8y agoI would be surprised if Rust's type system winds up supporting everything C++ templates do. But maybe I'll be wrong, or the only things not covered will be niche and easily worked around. I don't know.
- saghm 8y agoI feel like part of the reason that I don't run into issues with Rust having less expressive generics than C++ is that Rust has a ton of other useful meta-programming features; a lot of the crazy things I've seen solved by C++ templates can be solved in Rust via things like proc macros.
- htfy96 8y agoJust wondering if it's possible to implement something like expression template used in Eigen3 in Rust. C++'s template can be used in more than generics.
- weberc2 8y agoSure, but those are different tradeoffs than those listed above. And some of the warts with C++ templates might not be inherent to supporting the features that C++ templates offer over Rust generics--for example, what features are enabled by error messages that point into the expansion of a template rather than a type error at the template itself?
- bborud 8y ago...and for that we have C++. For me Go is a replacement for Java. It is my "workhorse language" for server applications and for writing stuff that other people have to deal with and understand. For this type of software Java and Go are fast enough. Go has the advantage of coming with a lighter cognitive load than Java because it isn't dominated by huge frameworks. Nor is the language itself complex. These are important reasons why we chose Go. If I were in the gaming industry or writing software that depends on wringing every last bit of performance out of the CPU, C++ would probably be relevant for me. If Go were to get C++ style templates it would greatly reduce the fitness for purpose due to the increased cognitive load and the likelihood that developers will resort to "cleverness". Keeping "cleverness" out of your code is of the utmost importance when developing code that people have to understand. I very occasionally do write code where every instruction counts too. Mostly on embedded platforms. Even there C++ is a bit troublesome because it is so easy for people to make mistakes. I'm not terribly fond of C++ there, but I can tolerate it. (Mostly I prefer to use C for embedded development and I really wish languages like Rust would be more ready for prime-time in embedded development)
- CondensedBrain 8y agoYou might like this presentation on the Curiosity rover's computer that goes into the OS, code (C++), and system design. https://media.ccc.de/v/35c3-9783-the_mars_rover_on-board_computer https://media.ccc.de/v/35c3-9783-the_mars_rover_on-board_com...
- marcosdumay 8y agoBesides pointing that typeclass based generics (Rust, Haskell) have all those pros and none of the cons, none of the gains you claim are inherently lost on Java-style generics either. It's just that Java has funny language limitations, that do not apply if you remove the incompatible requisites from the language.
- sreque 8y agogenerics in Haskell are done via type erasure, so they definitely do not perform nearly as well as c++ templates, especially for things like numerics. I don't know how rust implements parametric polymorphism to comment.
- whateveracct 8y agoI'm pretty sure there are mechanisms you can use in Haskell to reduce boxing despite erased generics. Specialize pragmas, for instance.
- munificent 8y ago> typeclass based generics (Rust, Haskell) have all those pros and none of the cons My understanding is that typeclasses incur a runtime penalty on invocations since the typeclass essentially carries around its V-table with it at runtime. > none of the gains you claim are inherently lost on Java-style generics either. It's just that Java has funny language limitations, that do not apply if you remove the incompatible requisites from the language. I don't understand how the last sentence of that does not invalidate the first. If you remove those limitations, what you get is not Java and would lose many of the other benefits that being Java has.
- steveklabnik 8y agoIn Rust, type classes do not have to carrry that overhead; when you use them as a trait bound, you get monomirphized code. When you use them as a trait object, you do carry the vtable around. For other languages, you may be correct in all cases.
- kouteiheika 8y agoI disagree. Yes, any approach to parametric polymorphism is going to make some tradeoffs, but the benefits of C++-like generics which you've enumerated (templates perform as fast as hand-instantiated types and the ability to have complex constraints) do not require you to simultaneously have the drawbacks which the C++-like generics have. For example, ML-style parametric polymorphism has shown us that it is possible to mostly have our cake and eat it too for 90% of cases where you actually need to use generics; unfortunately only very recently (e.g. with Rust, even if its flavour of generics is still limited compared to what you can find in functional languages) ML-style polymorphism has actually appeared in a more mainstream language.
- munificent 8y agoWell, ML doesn't have subtyping or C compatibility, so it's in a very different space. C++ had to deal with adding generics while also preserving all of the important other properties that make C++ C++. You can argue that those properties also aren't important, but they obviously are for C++ users.
- TJSomething 8y agoI thought that most MLs did type erasure, like Java, instead of completely reified types, like C++. Although I know that F#, which is an ML that can reify types at runtime using the CLR JIT.
- bogomipz 8y agoWhat is it about "parametric types" that wreak such havoc on error messages being intuitive? Is is it just the levels of indirection involved? If so is this also the reason for the slower/modular compilation? Thanks.
- munificent 8y agoWith C++ in particular, it's because type-checking happens only after a template has been instantiated, so the error messages you get are based on the instantiated types and not the original template.
- bogomipz 8y agoI apologize if this is a naive question but could or could have C++ used type erasure at run time similar to what Java does? Or is there some technical reason it's not feasible in the C++ run time?
- stcredzero 8y agoC++ sacrificed: -Fast, modular compilation -Straightforward error messages -Low cognitive overhead That's precisely the wrong way to go on every count! That's a perfect example of designing for the machine, not for the humans! (I say this as someone who wrangles C++ in my day job.)
- Godel_unicode 8y agoDo you truly think that no code should be optimized to run as quickly as possible over more human considerations? I think game engine developers might like to have a word with you...
- stcredzero 8y agoDo you truly think that no code should be optimized to run as quickly as possible over more human considerations? No. Of course not. It's mentally lazy to just push the lever all the way over to that side. By the same token, it's also mentally lazy to just push the lever all the way over to the other side. In some kind of design space over many variables, there are going to be some maxima. A bit more general than making the common case fast, is making the common case fast enough, debuggable, and straightforward, with the option of making something faster when and where you need it. I think game engine developers might like to have a word with you... I would like to think so. I'm developing a cloud resident game engine for MMOs. (Just a hobby project, admittedly.) All that said, those 3 decisions for C++ were 3 particularly egregious ones.
- hesdeadjim 8y agoI went far down the rabbit hole of C++ template metaprogramming path when I built a game engine years ago. Besides enjoying the challenge, I was able to make some APIs much cleaner and more flexible. The problem arose when I realized over time that I was the only engineer in the entire company capable of maintaining and extending my "clever" design. Later on I built our server backend in Go. As I built up the team I was continually impressed at how the enforced simplicity of the language made it easy to skill up new members and be able to understand code written months or years before. With that said, Go absolutely needs generics or at the bare minimum variant types. Too often I was forced to sacrifice type safety or add a ton of code duplication where very simple generic support would've been the solution. I'm content with the implementation of generics in C#. Copy it, move on, problem solved.
- hu3 8y agoI'd love to see C#'s generics in Go. There is, however, at least one plausible reason to not do it as @ianlancetaylor puts it very well in the main GitHub Issue for Go generics: > It is an essential feature of Go's interface types that you can define a non-interface type T and later define an interface type I such that T implements I. See https://golang.org/doc/faq#implements_interface https://golang.org/doc/faq#implements_interface . It would be inconsistent if Go implemented a form of generics for which a generic type G could only be used with a type T that explicitly said "I can be used to implement G." The whole Issue is worth a read. Here's direct link to the above comment: https://github.com/golang/go/issues/15292#issuecomment-210085246 https://github.com/golang/go/issues/15292#issuecomment-21008...
- hesdeadjim 8y agoI'm not sure I understand why types would need to declare the interfaces they implement to function with generics. If my generic requires interface I and T implements it implicitly why wouldn't that just work?
- candiodari 8y agoI find it a very weird reasoning, as inconsistency is everywhere in Go's type system. Every standard type literally has it's own type system, as do more than a few functions. Structs, arrays, maps, slices, ints, ..., make, new, range, delete, ...
- bogomipz 8y ago>"To be honest C++-like templates are probably the worst way to do generics." Could you or someone else elaborate on C++'s implementation and how it contributes to the negative effects which you've enumerated? Might you or anyone else recommend some links or literature on this? Lastly this might be a silly question but are generics and C++ templates strictly equivalent? Or are there some subtle conceptual differences?
- stcredzero 8y agoTo be honest C++-like templates are probably the worst way to do generics. C++ templates can provide you with the joy of debugging exceptions for which no source code exists! Been there. Done it for my job.
- naikrovek 8y agoIt exists, just temporarily and it's machine-written. C++ templates suck. They could have been tolerable but the way they are designed forces them to be awful. The people designing C++ either don't write code every day (too far away) or they do nothing but C++ every day (too close) and have become blind to better, non over-designed language feature implementations. It reminds me of the nonsense Java does that's clearly demonstrated in FizzBuzzEnterpriseEdition; a bunch of things that get in the way because one or more people were blinded by the language.
- stcredzero 8y agoIt exists, just temporarily and it's machine-written. The relevant point is that it may not be available for you at debug time. I know empirically that it can not be available, and the programmer will have to infer it by looking at the involved templates.
- lkschubert8 8y agoI dont think it's fair to say "Java does" because that can all be done in any OOP language. If anything it's more about people who over engineer.
- stcredzero 8y agoOr to be more succinct: If anything it's more about people. Extreme Programming took this into account. Left to their own devices, most coders tend to over engineer to impress their friends and coworkers.
- kawera 8y ago"... there are other, a lot more saner and principled flavours of parametric polymorphism out there." Could you point to some of those?
- atombender 8y agoOCaml, F#, Haskell, Rust, Ada, Scala.
- vram22 8y agoHaven't used them much (or C++'s at all), but (not speaking as a PL-designer-or-implementer), D's templates seem to be not bad. I could understand how to use them for some simple cases, at least, without much studying of the subject. And I liked what Walter Bright (D creator) said in (IIRC, in a DDJ article): something to the effect that, after working on the design of D templates for a while, a sudden insight he had, was that templates were (just [1]) a way of parameterizing a function not just by value parameters, but by type parameters too. [1] Not really "just", of course - there will be a lot to the implementation, but conceptually, that simple. Edit: Links: https://dlang.org/spec/template.html https://dlang.org/spec/template.html https://tour.dlang.org/tour/en/basics/templates https://tour.dlang.org/tour/en/basics/templates https://dlang.org/articles/templates-revisited.html https://dlang.org/articles/templates-revisited.html
- zamalek 8y ago> Not really "just", of course For C++. Tons of languages do the type paramaterization and it solves more than 80% of the common problems. 80% of the remaining 20% can probably be covered with compile-time code generation (or runtime code generation), for which templates aren't the only solution (D, C# and Lisp have different examples of how to solve this). The ultimate remainder of problems are likely really obscure, and are probably indicative of code smells (e.g. VectorF<3>).