Generic programming, among other things.
The C languages are actually missing a lot of the important concepts, like lambdas,
Lambdas are functions without names. They're not important concepts, they're really just syntactical sugar.
and discourage other important things like recursion.
Just because the language doesn't require TCO doesn't mean it discourages recursion; it just discourages teaching recursion with trivial examples like factorial. Quicksort, for instance, would be an excellent place in a C or C++-based course for teaching recursion.
The proper CS name is "parametric polymorphism". This is not as special as "Generic programming". (What is special, is the abysmal C++ implementation of parametric polymorphism.)
> Lambdas are functions without names. They're not important concepts, they're really just syntactical sugar.
The transformation required to go from a higher order program to a first order one is called "lambda lifting". This goes way beyond syntactic sugar. So, a language which lack lambdas don't fully implement functions… which look like an important CS concept.
No, it isn't. You would know this if you actually bothered to understand the terms you used rather than bandying them about in an attempt to win Internet points. Generic programming is a far broader topic than parametric polymorphism: that's why languages which already have parametric polymorphism, like Haskell, have variants which support true generic programming, like Generic Haskell and Scrap Your Boilerplate.
> (What is special, is the abysmal C++ implementation of parametric polymorphism.)
Given that you obviously don't understand the difference between run-of-the-mill parametric polymorphism and the generic programming enabled by C++ templates (and no other typed implementation except perhaps GADTs, and I remain unconvinced even of that) Id say you're fairly unqualified to be assessing the quality of C++'s implementation of parametric polymorphism.
> The transformation required to go from a higher order program to a first order one is called "lambda lifting". This goes way beyond syntactic sugar.
Wow, you really seem to enjoy using terms you clearly don't understand.
No, lambda lifting doesn't "go way beyond syntactic sugar." It's actually a fairly simple syntactical transformation: the free variables in the function body are converted to arguments to a static function, and closures, when generated, are a simple data structure consisting of a pointer to this static function and the arguments to "fill in" when called.
> So, a language which lack lambdas don't fully implement functions…
It's hard to believe that you'd even say that, let alone get modded up for doing so. I find it hard to argue against, much like I would have difficulty arguing against someone who said that a country which lacked cars couldn't fully implement wheels.
You see, what lambda lifting does is convert closures (which seem synonymous, at least in your writing, with lambdas) into ordinary functions with no free variables. Functions without free variables are more basic than functions with free variables.
The fact that template are Turing complete doesn't hide the restrictions they come with (like how they interact with inheritance). I know they have others goals than mere parametric polymorphism. I even admit that I don't understand these goals. I just say that one goal wasn't met: simple parametric polymorphism.
About lambda lifting being a (simple) syntactical transformation, please stop. You're right, but in a trivial sense: every compilation process is a (less simple) syntactical transformation. That includes lambda lifting, ghc's crazy optimizer, and every static compiler on this planet.
Now, by "syntactic sugar", I meant something that could be dealt with with local macros. You know, I have done the transformation you speak of manually in C++. More than once. I know this is fairly simple. But it is not local. I still have to write something outside the definition of the function I am writing the closure in. So, to do lambda lifting, I need a more global preprocessor.
The locality of the transformation may not be important to you, but to me, it is crucial. I don't mind some noise (semicolons, curly brackets, keywords…), because when used to it, I can see the actual code beneath it. However, the code has to be there. If I have to look up elsewhere, it quickly blows up my short term memory. Also, as long as the transformation is strictly local, you could deal with it with simple macros, like this:
-- I assuume "closure" is a Lisp-like macro
let x =
let y = ... in
closure arg1 arg2 (... body ...)
So, a language without lambdas doesn't treat functions as first class citizens. That's what I meant by "doesn't fully implement functions". This is a strong wording, but I don't think it is inaccurate. Put the same restrictions on integers, and see if you can still say that they are "fully implemented".Did I overlooked something?
The STL embodies (and accomplishes) far more than parametric polymorphism. As far as I know there is no other language with as comprehensive a collection of container-independent algorithms in the standard library.
> The fact that template are Turing complete doesn't hide the restrictions they come with (like how they interact with inheritance). I know they have others goals than mere parametric polymorphism. I even admit that I don't understand these goals. I just say that one goal wasn't met: simple parametric polymorphism.
And you're still wrong. Templates achieve parametric polymorphism and more. I can write a simple, templated, singly-linked list class in C++ which can do everything the same `'a list` in ML can do.
> About lambda lifting being a (simple) syntactical transformation, please stop. You're right, but in a trivial sense: every compilation process is a (less simple) syntactical transformation. That includes lambda lifting, ghc's crazy optimizer, and every static compiler on this planet.
I was making a greater claim that you seem to understand. Lambda lifting is a simple syntactical transformation. I can explain the entire transformation in a paragraph or less. I can offer example code of the transformation off the top of my head with ease. All lambda lifting does is make explicit what closures allow to remain implicit.
Your following two paragraphs are a fairly obvious attempt to change your original, wrong statement into something defensible.
> So, a language without lambdas doesn't treat functions as first class citizens.
Ridiculous. A language without lambdas is just that: a language without lambdas. Remove lambda from Python, and you remove only the ability of a Python programmer to define unnamed functions. Functions remain first class; their binding rules remain exactly the same. The only difference is that now programmers must give a name to all their functions.
Whoops. I forgot that, sorry. People may have meant true generic programming, after all. Now, actually using STL's <algorithm> and still end up with readable code isn't easy to me. For instance, each time `for_each` or `accumulate` would be appropriate, I end up writing a `for` loop instead, because the end result is more concise and more local. So in the end, container independent algorithms rarely matter to me.
> I can write a simple, templated, singly-linked list class in C++ which can do everything the same `'a list` in ML can do.
I think we don't use "simple" in the same way. Here, such an implementation of lists takes only 12 tokens and 2 lines in ML. How many do you need to do the same in C++? My first guess is more five times that, probably ten. Still simple, but much more complicated nonetheless.
Though in that particular case, I'd rather blame the absence of inductive types. Templates really are simple here. The real problem shows when you combine them with inheritance: you have to jump through some hoops (like having to re-declare virtual methods in the derived classes).
> Your following two paragraphs are a fairly obvious attempt to change your original, wrong statement into something defensible.
It was an attempt to clarify my meaning. Yes, my statement was wrong. Lambda lifting doesn't go way beyond syntactic sugar. But, to me, it still crosses the line. To you, it doesn't. I think that's only because your definition of "syntactic sugar" is more liberal than mine.
But even then, we're not done. Before you do the actual lambda lifting, you have to parse the source code. Trivial in Lisp, but quite a feat in C++.
> […] The only difference is that now programmers must give a name to all their functions.
To me, such a restriction is sufficient to make function second-class citizen. If you can have anonymous integers, and you can't have anonymous functions at the same time, then functions are not first class. Again, we disagree on a mere definition. Beyond that, I agree that the other "first-classiness" properties of functions are far more important.
> Now, actually using STL's <algorithm> and still end up with readable code isn't easy to me. For instance, each time `for_each` or `accumulate` would be appropriate, I end up writing a `for` loop instead, because the end result is more concise and more local.
This seems to be a new complaint, again unrelated to your previous complaint. No one, least of all me, is claiming that C++ is concise. If that's a debate you want to have, you'll need to find some other patsy to have it with.
> So in the end, container independent algorithms rarely matter to me.
They matter a lot to any implementer of new containers and algorithms, because they mean that any container implementer can provide iterators and automatically support a huge library of algorithms, and any algorithm implementer can automatically support a wide array of containers without needing any specific knowledge of how those containers are implemented.
> I think we don't use "simple" in the same way. Here, such an implementation of lists takes only 12 tokens and 2 lines in ML. How many do you need to do the same in C++?
When did this debate become about conciseness? Your claim was not that parametric polymorphism was wordy in C++, it was that the implementation was "abysmal". In reality, the implementation is as optimal as it really can be in an imperative, C-derived language: a non-polymorphic data structure on some type "X" can be made polymorphic by prepending "template <typename T>" before the data structure and replacing all references to "X" with "T". That's pretty simple.
The difference you note in line counts for implementing a singly linked list is not because of any real difference in capability of the language to support parametric polymorphism, but in ML's support for algebraic data types as opposed to C++'s support for machine-based data types: now you're introducing yet another complaint which is not your original complaint.
> The real problem shows when you combine them with inheritance: you have to jump through some hoops (like having to re-declare virtual methods in the derived classes).
Perhaps, but since virtual methods and inheritance-based subtyping is something ML doesn't support at all you cannot fault C++ for its less-than-optimal support for those features in comparison to ML.
> But even then, we're not done. Before you do the actual lambda lifting, you have to parse the source code. Trivial in Lisp, but quite a feat in C++.
Introducing yet another complaint unrelated to anyone's original claims. Ignoring it, since it's not relevant to the reason why this thread originally began.
> To me, such a restriction is sufficient to make function second-class citizen.
"First class" is a well defined term in programming language design and implementation, and it's not the same definition you're trying to use here. I see no point in arguing with you about your non-standard use of the term.
Well, yes. Sorry.
> The difference you note in line counts for implementing a singly linked list is not because of any real difference in capability of the language to support parametric polymorphism[…]
Hey, I said that much!
> since virtual methods and inheritance-based subtyping is something ML doesn't support at all you cannot fault C++ for its less-than-optimal support for those features in comparison to ML
Fair enough. I'll revise my stance.
> "First class" is a well defined term in programming language design and implementation.
Not in Wikipedia: http://en.wikipedia.org/wiki/Talk:First-class_function Could you point me to an authoritative source which would clarify the issue?
I happen to think that's the right decision.
Or switch not from C/C++ to Java, but from Scheme to Python?
MIT's EECS department panicked when enrollment crashed after the dot.com bubble burst (for the previous quarter century or so they had 2/5ths of the undergraduates (!!!)) and it was decided from on high to change the very nature of its CS foundation and to purge Scheme from the base curriculum.
You can find Sussman's comments on this here and there; here's one paraphrase: http://muckandbrass.com/web/display/~cemerick/2009/03/24/Why...
Although I can't at the moment find the sharpest direct quote of him, very roughly, it's a change from understanding what's really going on to patching together libraries.
Now, this is in the cause of building really interesting things, or that is my trying to put a positive gloss on it all and my impression of what it's actually accomplishing. If you're a pure CS type this is an unmitigated disaster ("So the reason, basically, is that software today is a train wreck, and you might as well embrace helplessness and random tinkering from the start? Horrifying." Andy Hefner, http://www.wisdomandwonder.com/link/2110/why-mit-switched-fr...) and you should go to one of the other top schools like Berkeley, where they still teach SICP. If you want to go out into the world and make cool stuff, it's probably still pretty good to great.