4 ms·
"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 s
by 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).
- Locke1689 14y agoThis is definitely true and demonstrates how competing design decisions can add huge complexity to a language. C++ made earlier design goals to allow heavily context-dependent overriding, which has side effects on what features it can add later.
- Peaker 14y agoIntegers and Strings aren't edge cases. Their literals are polymorphic and typed. C++ could also type its list initializers with some polymorphic type (similar to Haskell's Num) but didn't do so. This is not inherent.
- fusiongyro 14y agoThey're not worth discussing because they're a counterintuitive mess rather than a case study of the glory of HM. Maybe edge case isn't the right word, but they're definitely not something I would hail as a perfect resounding success. There are no polymorphic literals in ML, just polymorphic math operators, which is enough of a blight on the standard that OCaml discarded it and forces you to use different operators for real and integer arithmetic. And there's only one kind of string in both MLs. Haskell's Num hierarchy is troublesome. They traded usability for + with complexity for /. It's extremely unlikely that you could write a program in Haskell that does much arithmetic and have it build correctly on the first try without any manifest typing. This is one reason students of Haskell find things so confusing: type declarations are necessary at the top level simply because the extensions and complexity of modern Haskell break HM if you try using it everywhere. Also, the class system in there is not especially mathematically correct, which leads to the numerous replacement Preludes that try to do a better job but haven't caught on. Strings are edge cases because they are not polymorphic unless you enable OverloadedStrings. Once you do, you will either replace the built-in string with something else (ByteString or Text) or find yourself in the same kind of trouble you'd be in with Num. Let me be clear: I'm not saying that these problems are showstoppers. They're really minor annoyances once you're experienced, though they contribute to confusion for beginners. The point I'm trying to make is that you can't just drop HM into any old language and expect it to work. A greater point would perhaps be that all languages have warts simply because they're large, complex beasts (Haskell and C++ especially) and it's unproductive to point to a missing feature in one and demand some sort of perfected version of the other's.
- Peaker 14y ago> They're not worth discussing because they're a counterintuitive mess Do you really believe Num overloading is a counterintuitive mess? I disagree completely. > OCaml discarded it and forces you to use different operators for real and integer arithmetic Which is pretty terrible. > Haskell's Num hierarchy is troublesome. Yes, but that's an orthogonal issue. > This is one reason students of Haskell find things so confusing: type declarations are necessary at the top level simply because the extensions and complexity of modern Haskell break HM if you try using it everywhere That sounds like FUD to me, a heavy Haskell user. Type declarations at the top-level are generally necessary to avoid the dreaded MR and for documentation purposes. Modern Haskell doesn't heavily use extensions that require type annotations on the top-level. > Strings are edge cases because they are not polymorphic unless you enable OverloadedStrings. Once you do, you will either replace the built-in string with something else (ByteString or Text) or find yourself in the same kind of trouble you'd be in with Num. It lets me use literals for Lazy Text, Strict Text, and String with the same syntax, which is nice. My point is merely that giving a type to a polymorphic initializer is possible, and C++ chose not to.
- danking00 14y agoYour example, void foo() { auto items = {1,2,3,4}; return; } is the case of poorly designed syntax, in a truly Hindley-Milner system there is no expression which doesn't have a type. Now, we could add some syntax that screws that up, say: let v = I-HAVE-AN-AMBIGUOUS-TYPE in 0 But that's rather silly, isn't it? If a value isn't used then I would, personally, like my language to optimize it away. So why not ditch such silly syntax? This is part of why I said that the foundations of C++ are too far-gone. And this syntax isn't necessary to save on typing. In a language that supported syntactic macros (such as Scheme or Racket) we could write something like: auto items = my-vector-syntax(1,2,3,4); which expands to something like: auto items; items.push_back(1); items.push_back(2); items.push_back(3); items.push_back(4); If you're interested in true syntactic macros for non-sexp languages (though I do suggest getting over the parentheses, my color settings make them nearly indistinguishable from the background) look at Rust [1]. Actually, Rust also has type inference [2]. Hell, stop programming in C++ and start using Rust! [3] [1] http://dl.rust-lang.org/doc/tutorial-macros.html http://dl.rust-lang.org/doc/tutorial-macros.html [2] http://dl.rust-lang.org/doc/0.4/rust.html#type-system http://dl.rust-lang.org/doc/0.4/rust.html#type-system [3] http://www.rust-lang.org/ http://www.rust-lang.org/
- fusiongyro 14y agoHaving programmed Haskell for the last seven years (and C++ for zero) I have a fairly decent handle on what HM is all about. If all you had said is that the foundations of C++ are too far gone, there wouldn't be anything to discuss. But the foundations of C++ being what they are, there's no point being offended when unfixable things go unfixed. I love HM, but you can't just throw it in any old language simply because it's cool. The language's semantics need to allow for it, and they just don't with C++.
- danking00 14y agoCorrect, I do not suggest adding HM to C++. I suggest using a HM language when you start a new project.
- daivd 14y ago