6 ms·
The extra syntax is not there for nothing. It's adding type information. Thus allowing error checking or dispatching by type or optimisations at compile time.
by alpatters 14y ago
The extra syntax is not there for nothing. It's adding type information. Thus allowing error checking or dispatching by type or optimisations at compile time.
It is a cost in terms of syntax and readability but it's not for nothing.
So for correct programs the end result might be the same in terms of values. For buggy code and runtime speed that's not necessarily the case.
- jlarocco 14y agoThe extra code is there for a reason, but it's still there and it's not there in a language like Python. Nobody would argue that C++11 code can be much cleaner than old C++ code. But to say it's almost as clean as Python goes to far, and isn't fooling anybody. So why not be satisfied beating old C++?
- hnriot 14y agothat's not entirely true, take make_tuple for example, this doesn't add anything except verbosity (by comparison to Python)
- drivebyacct2 14y agoYeah, I agree with the claim that this is not in the same realm of readability (seriously, just consider for 10 seconds what an at-scale C++11 codebase looks like versus a scaled up python code base. But I understand why they're different and I can't get enough of the statically typed kool-aid lately.
- Evbn 14y agoI am picturing Python drowning in isinstance checks to avoid constant crashing every time a new function is added.
- drivebyacct2 14y agoI'm just imagining how many nights in a row I've committed more lines of code than exist in my project in a snapshot because it's so damn confusing, interconnected, and concurrent. The thought of doing it in a dynamic language is downright nauseating.
- danking00 14y agoAnd this is where I decide the language is too far developed on the wrong foundation. I cannot put up with type systems that don't have complete or near-complete type inference. I don't know why one would start a new project in a language that didn't support Hindley-Milner.
- Locke1689 14y agoJust to elaborate on what danking00 is saying, the extra syntax in this case is not adding any extra information (to the compiler). The left hand type of the expression can be completely inferred at compile time, in this case. What that required syntax is adding is pain, but no gain (except for imperceptibly faster compile time). In Haskell this would look like: TagDataMap = [ ("title", ((3, 30, stripnulls)), ("artist", ((33, 30, stripnulls)), ... Haskell will correctly infer that TagDataMap :: [(String, (Integer, Integer, String -> String)]. Ok, technically this is an associative list, not a Python dictionary, but it is a map and can be accessed like one. Hell, most people use dictionaries with less than 10 items, which are much slower than arrays most of the time
- fusiongyro 14y ago"the extra syntax in this case is not adding any extra information (to the compiler)." This actually isn't true in C++, because there could be many classes in scope that could take a list initializer like this. In Haskell, no literal with [] can "turn into" something besides a list, but that can happen in C++: vector<int> items = {1,2,3,4}; int[] items = {1,2,3,4}; These have different types, so how would C++ know which one you meant if you instead wrote: auto items = {1,2,3,4}; Again, in Haskell this isn't a problem because literals have essentially a single type. (Edge cases around integers and strings notwithstanding). Edit: Just to clarify, in a Hindley-Milner system you could maybe get away with something like that, but everything you name in an HM system you must use, and that isn't the case in C++: void foo() { auto items = {1,2,3,4}; return; } I can then make two classes with list constructors: struct FooClass { FooClass(std::initializer_list<int> list) { cout << "Made a Foo!" << endl; } }; struct BarClass { BarClass(std::initializer_list<int> list) { format_your_hard_disk(); } }; Obviously there are consequences to choosing the right type, but the type of that value never leaks out of the function. Nevertheless, because side-effects can happen anywhere, even in a constructor, C++ cannot optimize that out. This might be a convoluted example, and it may be flawed, but conjuring up others is not hard and demonstrates that C++ simply cannot ever have true HM type inferencing. Since the "real deal" is not possible, the language is complex and the standard is large, I would not expect to be able to live without annotations in C++-land. (Again, the FQA makes the horror of multiple non-orthogonal solutions to the same problems quite clear).
- derleth 14y ago> The extra syntax is not there for nothing. You could say the same thing about everything old C++ compelled you to type. Or old COBOL, for that matter. I'm more interested in what the C++ memory allocation style does to verbosity. Memory-handling styles are often more important than the traditional paradigms (functional, OO, etc.) in determining what's reasonable/pleasant to do in a given language. https://sites.google.com/site/steveyegge2/allocation-styles https://sites.google.com/site/steveyegge2/allocation-styles
- eru 14y agoBut aren't all allocation styles basically the same once you get GC? (I guess unless you broaden the scope of the word, and let it describe the lengths you go in Haskell to avoid allocation at all, aka triggering stream fusion and automatic deforrestation in general.)
- Evbn 14y agoThere are other issues: Go has stuff like slice for efficiently allocating array data more finely than a Java array, perl has autovivication.
- Myrmornis 14y agoThis is what caught my eye too as being really ugly and hard to read. const map<const string, const tuple<int, int, StrToStr>> TagDataMap { {"title" , make_tuple( 3, 30, stripnulls)}, Why can't the compiler figure out the types involved in this map structure itself? The user-defined functions are declared above, make_tuple will be declared in some library, and the others are string/int literals.
- fusiongyro 14y agoOne thing you cannot do is infer `const`ness. This isn't a problem in Hindley-Milner systems because variables are not assignable--they're all `const`, all the time. Literals are inherently immutable but when you assign a C++ variable a literal value, sometimes you want a const variable and sometimes you don't. I don't think C++ can figure out the map<..> part simply because lots of things could have list initializers that accept lists of 2 item lists. C++'s overloading and implicit conversion conflicts with perfect type inferencing. This is an example of the kind of thing the FQA talks about, where several of C++'s issues collide to produce counter-intuitive behavior. I do wonder if you could get away with this: const map<const auto, const auto> TagDataMap { ... I don't have access to a C++11 compiler from where I am to find out though. I am particularly unclear on the interaction between `auto` and other aspects of a type declaration--I don't know if you can nest `auto` like this deep inside some other type declaration. I don't see why you couldn't, but wouldn't be shocked either.
- bcoates 14y agoYou can't do it with that syntax. 'auto' never infers constructor calls, just takes the static type of an LHS to capture a temporary into. You'd have to use a non-constructor template function that inspects the type of its argument to guess what kind of map you want. const auto TagDataMap = map_initializer( { "foo", { 1, 20, func }, ... } ); where map_initializer would be a template function that inspects the typedefs of its std::initializer_list<T> argument and generates an appropriate map. In real C++ code, this would almost never be what you actually want, though, as the types in an initializer list in C++ do not imply the types being initialized, they are parameters to a constructor to be determined elsewhere.