> The reality is that generics aren't free. They result in difficult-to-understand error messages and more cognitive complexity.
> To quote Yossi Krenin, "You may think it's a StringToStringMap, but only until the tools enlighten you - it's actually more of a... std::map<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::less<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >"
The Kreinin quote is in relation to C++. For historical reasons (reverse compatibility with C) and interoperability with other parts of the language (manual memory management), C++ types are way more complicated than types in a more recent language such as C#, where a Map<string,string> is really not much more complicated than you would think it would be. One might be able to argue that generics make things more complicated, but they certainly don't have to make them as complicated as C++ does.
You can't blame C++ generics on compatibility with C, because C doesn't have them. The culprit isn't manual memory management either.
The issue is just that the complexity of the type grows exponentially as you compose more generic types together. The string was templated on three things: the character type (usually char), the traits, and the allocator. Fair enough, but now the map is templated on the key type, the value type, the comparator, and the alocator. So that's 4 x 3 x 3 = 36 different parts in the StringToStringMap. Java has similar "russian doll" types where you have many different layers of generics within generics. You need to read all of them to fully understand the behavior.
To be honest, overuse of generics is a code smell in both C++ and Java. One of the early warning signs that your code might have this problem is if you feel the urge to template on bool. I do think that generics may come to Go some day, but I think it will require a lot of thought, and it may not come in quite the same form as it did for Java and C++. No doubt there will be many angry posts about how Go "has" to do it the C++/Java way at that point.
> You can't blame C++ generics on compatibility with C, because C doesn't have them. The culprit isn't manual memory management either.
This is arguable. C doesn't have generics but if you look at the std::string example posted above as a "bad example", it indeed has some C backwards compatibility hacks.
In particular, std::allocator<...> is repeated many times. That is an attempt at making the string type (and other containers) at least partially compatible with manual memory management.
std::string is an overengineered and complicated solution compared to string types in other languages (and C heritage plays a part in it), it is not a good example of generics.
Posting that litany of C++ template code (which is essentially std::map<std::string, std::string> fully expanded, not what you'd actually write) is not a very good example of generics use.
I don't know Haskell, but I do know OCaml, a strongly, statically-typed language with type inference. OCaml does avoid a lot of the pitfalls of C++, but it shares the problem of difficult to decode error messages. Oh boy, does it ever.
Adding explicit type specifications helped a little bit. Adding parentheses around everything helped a little bit more. For some reason, the parentheses trick seems to be little-known online, but it's something I really had to do sometimes in OCaml. I once spent a half hour trying to figure out why a file was getting a parse error at the end. I eventually did a kind of binary search, commenting out half of the file at a time until I could narrow it down. It was a missing brace halfway through. The problem is that without the need to put all functions in the top-level, things just float in a sea of declarations, and if two things are next to one another, it's assumed to be curried... and so your error message about a missing brace shows up on a line number far removed from the actual problem.
It's possible that with more development effort put into the language, OCaml and other functional programming languages would have better compiler error messages. It's also possible that there's just a fundamental difficulty trying to teach a profoundly stupid computer to give reasonable error messages about a super-clever system. Hopefully the popularity of functional languages like Scala and OCaml will give us a chance to find out the answer to tihs.
Clang has excellent error messages for C++ - it even spellchecks your code and can suggest the function you meant to type when you typo something. So this is almost certainly a case of development effort, and not something intrinsic to the type system.
Yeah, clang's error messages are excellent, especially given how difficult C++ is to parse, let alone compile. However, they are still not as good as Go's. And try doing the kind of automated source transforms that Go can do with "go fix." IDEs for C++ are still fairly limited due to things like conditional compilation, unit-by-unit compilation, macros, etc. etc. You can make a pig fly, by shooting it out of a cannon, but ultimately it is still a pig.
I've been meaning to try the clang plugin for vim... context-sensitive autocomplete for C++ would be nice.
Ok, then blame it on the STL. Its API gives you extreme flexibility, but at the cost of extreme complexity.
In a language like Java or C#, as the parent stated, a Map<String, String> is really more or less just that.
In "real" terms, I don't need a string of anything but char (say I only use utf-8, or perhaps I just don't care what std::string uses as its internal encoding and will expect there to be methods on it to give me encodings that I want), so std::string being a std::basic_string<char> underneath is useless to me. Kill that flexibility.
I don't care about the allocator used. Kill that flexibility.
Etc.
Ok, so then std::string is now just... std::string. Cool.
On to the map. The comparator? Not sure why that needs to be a template parameter (though I guess the type-safety is somewhat nice); why can't I construct and pass one into the constructor or a setter at runtime? Kill that from the template.
Again, with the allocators. I don't care. Kill them.
Ok, now the MapStringToString is really just std::map<std::string, std::string> and nothing more. Awesome.
Sure, you can't blame the complexity of C++ generics on C compat or manual memory management: it's the STL's very powerful API. A very powerful API that makes no effort to hide the complexity it exposes. Arguably generics themselves in C++ aren't to blame at all. And so it follows that generics in Go need not be complicated and painful either.
"I don't care" is not the same as "nobody cares", and especially not "nobody will ever care".
Why would somebody care? Well, C++ gets used a lot in embedded systems. There, you can have different storage pools for different specialized kinds of memory. But if Go isn't trying to solve those kinds of problems, then Go doesn't need the same machinery that C++ does. It doesn't make C++ stupid, it makes it designed for a different environment.
Also note that C++/STL templates default the allocators and comparators, so you don't need to supply them unless you really need them. That's pretty much what you're asking for... until you try to read a compiler or linker message.
Sorry, I wasn't clear in my overall message. The only point I was trying to make is that "generics error messages are hard to read in C++" is false: it's really that STL-related error messages are hard to read. If someone wanted to build a simple standard library that omits a lot of the complexity provided by the STL, they could do so, and the error messages would likely be readable.
> To quote Yossi Krenin, "You may think it's a StringToStringMap, but only until the tools enlighten you - it's actually more of a... std::map<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::less<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >"
The Kreinin quote is in relation to C++. For historical reasons (reverse compatibility with C) and interoperability with other parts of the language (manual memory management), C++ types are way more complicated than types in a more recent language such as C#, where a Map<string,string> is really not much more complicated than you would think it would be. One might be able to argue that generics make things more complicated, but they certainly don't have to make them as complicated as C++ does.