Gomacro: Interactive Go interpreter and debugger with generics and macros
github.com
github.com
function firstOf<T>(anArray: T[]): T {
return anArray[0]
}
interface thing<T, Y> {
key: T
anotherOne: Y
}
const things: thing<string, string> = [
{ key: 'hello', anotherOne: 'world'},
{ key: 'value', anotherOne: 'thing'}
]
const result = firstOf<thing>(things)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.
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.
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).
I honestly don't know what point you're trying to make here. This really shouldn't be controversial.
80/20 rule applies here. 80% of the boilerplate can be eliminated with 20% of the expressiveness.
There's really nothing in my comment that suggests I think otherwise, so I'm a bit confused by your comment.
I understand that it's being worked on. I won't disagree that C++ templates are sharp, pointy tools but there are some things that they enable which you can't get in other languages.
Similarly, the existence of header files at all is probably an anti-feature.
Last time I made a mistake stringing Tokio promises together, the error messages were straight from the C++ hell.
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)
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.
For other languages, you may be correct in all cases.
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.
-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.)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.
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.
> 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 . 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-21008...
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, ...
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?
C++ templates can provide you with the joy of debugging exceptions for which no source code exists! Been there. Done it for my job.
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.
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.
Could you point to some of those?
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
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>).
https://www.jetbrains.com/help/go/debugging-code.html#924cf9...
Generics operate entirely at the Type level. For instance (using java)
List<Integer> list = new ArrayList<Integer>();
list.add(1);
Integer i = list.get(0);
is under the hood equivalent to List<Object> list = new ArrayList<Object>();
list.add(new Integer(1));
Integer i = (Integer)list.get(0);
The differences between the two snippets are entirely at the type level - they compile to the exact same bytecode. Templates are strictly more powerful - they allow you to generate different code for each instantiation. The most obvious place where this is useful is when dealing with primitives that take up different sizes in memory. This is why you can't use generics in Java with primitives - you need Objects, because pointers to objects are always the same size. But if you want performance, so you want to be able to represent collections of primitives generically, then you need the power of templates because you need your code to compile to different things depending on the template parameters.http://www.jprl.com/Blog/archive/development/2007/Aug-31.htm...
In my quick read, it wasn't clear if it would be possible to open a channel in regular Go code, and pass that into the interpreted side.
Just being able to call functions in normal compiled Go code would be enough I suppose, but direct support for cross-domain channels would be cool.
Perhaps it's the lack of Generics in golang.
Besides the fact that generics is a up and coming feature in go 2 basically proves my point.
Not sure what your point about the runtimes is? We shouldn't use or build highly optimized runtimes? Go's original runtime sucked and there's been a lot of work put into making it better too, I guess they should have just stuck with it?
Could you elaborate on what the shortcomings of the original runtime were? Might you have any link regarding this? Thanks.
Nothing that surprising, really.
If you look at Go's release note history: https://golang.org/doc/devel/release.html look towards the first few releases for the bugs they mention. For instance, here's a 1.1 fix: https://github.com/golang/go/commit/d72c550f1c7e13c323f4507b...
One of the advantages of not mutating the language too much is that there aren't very many things like this in the last few releases. I would expect the first 2.0 release to have a few rough edges even in the first non-beta release.
https://www.techempower.com/benchmarks/#section=data-r17&hw=...
I consider a good number of modern platforms fast enough for most uses. When tight requirements arise, which is rare, I resort to PoC implementations rather than microbenchmarks. There's a lot to more to consider than raw performance.
And there are plenty of variations to chose from.
Here are some more microbenchmarks that show Go and Java at about the same performance, and the rules of the game prohibit idiomatic Go optimizations (hence the poor performance on binary-tree; regex-redux is slow because Go's regex library is famously unoptimized).
For real-world programs, Go and Java are in the same ballpark.