But Python's major strength is readability, even more than coinciseness, or better, to provide both at the same time.
C was born as a terse language, sacrificing readability for coinciseness (the original examples in K&R are incredibly succint, almost elegant, but far from readable). I can't see many improvements in C++ (a language that arguably has worse coinciseness than C, without gaining in readability in the process)in that regards, even with the new version.
I work with C/C++ and I could easily understand a Python script even when I wasn't that familiar with the language, the same cannot be really said for that code in C++.
BTW, I'm not bashing C++, that has a lot going for it,just not readability and coinciseness.
Broadly: reading code is just hard. It's much harder than writing code. There are no non-trivial codebases that can be considered "easy to read" (if you think there are, then you're fooling yourself), and the choice of language makes only mild difference.
(edit: I'm amused at the multiple people who had to jump in to explain "decorators aren't hard!" instead of responding to my core point. First, I know what a decorator is. Second, no one who isn't a reasonably proficient python programmer is going to have any clue what @classmethod does or why all the old code doesn't break without using it. It's non-standard, language-specific syntax in exactly the same way that an STL iterator is, and the only reason you think one is easy and not the other is because you know Python and not C++.)
See for yourself: https://gist.github.com/3988350
Even now, I still like Haskell's
[fn(x) | x <- myList, x < 5]
over Python's equivalent [fn(x) for x in myList if x < 5]
Of course, I'm a mathematician, so that probably explains everything about my convoluted path of understanding these things, and my preferences to this day. :-)But a complex program written in C++ (or using advanced C++ constructs) will scale much worse than a Python one, in my opinion.
Decorators are pretty opaque; idiomatic python makes minimal use of them, and uses them only for things that don't change the meaning of the method inside. I've never used @classmethod and hope to never have to. I don't think it's fair to compare to an STL iterator, which is something you're encouraged to use as much as possible.
I don't know what you're talking about for iterables; aren't they obvious?
It is sufficient if the C++ code I write is reasonably readable to a fellow C++ programmer. I really don't care if it is not readable to a casual observer.
It is nice that Python code is readable to even to non python people. But it is not a requirement for every language.
string s = "string";
reverse(s.begin(), s.end());
And here's how to reverse a string in Python:
s = "string"
print s[::-1]
In my opinion, the C++ version is far easier to read and just makes more sense. Edit - This is just one small example, however, I find it holds true for the entire languages in general. Also, I do a lot of Python and C++ systems programming so I use both frequently and while I prefer C++, I think Python is the best scripting language available today.
s = "string"
reversed(s)
It also has the plus of creating a generator, saving memory from a new array allocation.It's odd how Python has list.reverse() but not str.reverse(). Probably a conscious design decision sometime ago.
In this case, we are accessing the position "two colons minus one". I refuse to believe this makes any sort of sense for someone that has no deep knowledge of Python.
using namespace boost::adaptors;
s | reversed
or reverse(s) ''.join(reversed(s))
It's still not the same as your example, because in C++ you reverse the string in-place. If you have a mutable array-like, you can do arrayish[:] = reversed(arrayish)
But to me the syntax with `s[::-1]` is already perfectly clear. There is the difference that `reversed` returns an iterator, while `s[::-1]` constructs a new string.In terms of readability, they both beg the question - is the reversed string ever actually held in memory at some point or can it occur lazily via latent reverse iteration?
I mean is the alleged readability of either language really answering any important questions?
const map<const string, const tuple<int, int, StrToStr>> TagDataMap {
{"title" , make_tuple( 3, 30, stripnulls)},
{"artist" , make_tuple( 33, 30, stripnulls)},
...
it will be succinct when the whole type can get reduced to "auto". Python would just do: TagDataMap = {
'title': (3, 30, stripnulls),
'artist': (33, 30, stripnulls),
...
Ord implementation is also crazy: int i = static_cast<unsigned char>(s[0]);
return (boost::format("%1%") % i).str();
considering it doesn't even handle different string encodings.I don't want to complain about C++. It's a great language and has its uses. But it's not succinct and it's far away from what scripting languages provide. Claiming otherwise is close to fanboyism. Not being succinct is not a bad thing. There are many other things that C++ does for you that Python doesn't.
The difference between make_tuple(...) and (...) is not significant compared to what it would have been before without list initializers at all and having to manually populate the map. That's something people who knew C++ of the past but not C++11 would be happy to see. The rest of the code is a great demonstration of these new features as well. That's the point: to demonstrate C++11, not undermine Python.
The article ends with some cogent criticism of C++. If this sets of your fanboy detector, you should turn it down a little.
That's not what he wrote though. The title is "C++11 and Boost - Succinct Like Python", the contents say "almost as painless as in a modern dynamic language like Python". That's what I can't agree with. There's still lots of supporting syntax that doesn't actually do anything for the end result.
string ord(string const &s) {
return to_string(unsigned(s[0]));
}
He skips all the const-refs...The C-style cast is shorter, but less safe, so you should not use it: http://stackoverflow.com/questions/1609163/what-is-the-diffe...
Yeah, I couldn't quite decide if I should include const refs. On the one hand, it is idiomatic, but on the other hand I didn't want to clutter the code with something that is just an optimization on paper.
I recently rewrote one of my C++ projects(which uses C++11 features and Boost.asio) in Go. It took me half the time, less than 2/3 lines of code compared to the C++ version. This is expected. But most surprisingly, despite that I tried my best thinking about how to make it as efficient as possible in every detail when writing C++ version, and that I'm quite new to Go, the Go version still achieves nearly 100% higher throughput than C++ version.
Maybe my C++ skill sucks. But I guess I'll just let it suck.
Check this out: http://en.munknex.net/2011/12/golang-goroutines-performance....
To me, this post would have been 10X more interesting if there was some elementary benchmarking included. If it's about the same speed as the corresponding Python code, which I'd bet is easier to write for more programmers, then I don't quite see the point being as clearly proven.
If it's 100 times faster (or whatever), then it gives more credibility to the idea of writing code like this in C++ to begin with, and to learning all the new language features that makes it safe while being that much faster than Python.
The program is I/O-bound, so the only speed improvement comes from not having to start up the Python interpreter.
If the task was CPU bound, you would get a great performance boost (sometimes 100x over CPython), but that is well known.
There can be other reasons than performance for writing in C++. Used well, the strong static type system can catch many bugs. I suspect (but I cannot prove) that it could be almost as good as Haskell.
I use Python for most things, though, so I don't really advocating switching to C++ for every little scripting task.
For other reasons to write in C++ (or any static typed lang), I'd add type dispatching: calling different methods based on type. I also hate having a typo in a method name throwing at runtime.
I also often convert python implementations to C++, often using boost, usually for performance in one part of the code. You can quite often get the same implementation in a similar number of lines, especially since C++11 with boost.
#include <iostream>
#include <vector>
int main() {
std::vector<std::string> v;
v.push_back(std::string("Hello"));
v.push_back(std::string("there"));
for (auto ii = v.begin(), ie = v.end(); ii != ie; ++ii) {
v.clear();
std::cout << *ii << std::endl;
}
return 0;
}
Preventing this sort of thing requires strong guarantees about aliasing (to ensure that "v" can't alias the vector being iterated over), which the C++ type system can't help you with. std::string type = "foo";
auto it = std::find_if(
channel_widgets.begin(),
channel_widgets.end(),
[type](const std::shared_ptr<Widget> &w){
return (w->getType() == type);
}
);
if (it == channel_widgets.end()) {
std::cerr << "widget not found" << std::endl;
} else {
std::shared_ptr<Widget> target_widget = *it;
std::cout << "widget found." << std::endl;
}
versus (for example): type = "foo"
matches = [x for x in widgets if x.get_type() == type];
if matches:
target_widget = matches[0]
print "widget found:",target_widget
else:
print "widget not found"
I may be missing out on some C++11 feature for doing this better. (Or even some Python feature for doing this better!)First write a function that searches an entire range with a predicate, so you don't have to write the .begin() and .end() anymore. That's just applying DRY. Then you could write a helper struct that wraps an iterator and the end iterator of the range searched, so you can give it an operator bool (). Return this struct from your find function and you get code like this:
auto result = my::find_if( channel_widgets, predicate );
if( result )
do_stuf_with_result( result.it );
else
error(); type = "foo"
try:
target_widget = (x for x in widgets if x.get_type() == type).next()
print "widget found:",target_widget
except StopIteration:
print "widget not found" auto it = channel_widgets.begin();
while (it != channel_widgets.end())
if (it->getType() == type) goto found;
// error
found: // whatever
I kinda hate myself every time I write such for-loop, but it's still more succint than any variant of find_if. Generally, I use "trivial" STL algorithms (find, find_if, for_each, ...) only if I can reuse the functor several times. Otherwise it's not worth the hassle.PS: you can do it also without goto... use "break" after if, and after while you write
if (it == channel_widgets.end())
whatever; // not_found std::string type("foo");
auto range = channel_widgets | filter([type](const std::shared_ptr<Widget> &w) {
return w->getType() == type;
});
if (range) {
std::string target_widget = *range.begin();
std::cout << "widget found." << std::endl;
} else {
std::cerr << "widget not found" << std::endl;
}Could you expand on this a bit? I'm not a C++ guy, but I was thinking of picking it up. By what metric do you mean it has "lost credibility?"
cout << join(pm | transformed([](propMap::value_type pv){...
I'm sorry, but as "succinct" as it is, this is basically an unreadable bullshit. Reminds me strongly of a macro abuse in C.http://play.golang.org/p/53jSv32wSF
(You can't access the filesystem on the playground, so it doesn't run.)
* Look at that error handling. Mmmmhmmm, clear and explicit. If something goes wrong, I'll know about it.
* Apart from, of course, errors that I ignore, such as when converting the year from 4 chars to an int (line 54). I don't care if I can't parse that.
* Note the difference (lines 54, 56) between parsing the track (which is a byte), and the year, written as 4 digits. The byte can just be cast to an int, the string needs to be parsed by the strconv package.
* I have a little bit of defensive programming in the form of a panic(), which would tell me if something that I thought was impossible has happened.
* defer on line 19 makes sure the file closes if we managed to open it, regardless of when we return.
* type casts are explicit, even from int to int64 (line 20)
* I don't specify which interfaces a type implements, the compiler handles that all for me.
* Parse() returns map[string]interface{}, which allows me to store anything (in this case just ints and strings) in a key-value store.
* A type-safe printf! "%v" (line 67) uses reflection to look at the type of the argument and deduce the natural format. So I can pass it an int or a string and it works :)
* On line 86 I pass this weird new type to a printf function and it goes ahead and uses the String() method that we defined. If we hadn't defined that we'd get a printout of the type, which in this case would be the 128 bytes we read.
if err != nil {
return nil, err
}Line 24 makes a byte slice, line 25 populates it with data read from the file.
There's also a ton of redundant libraries and language features, resulting in a lot of decisions being arbitrary choices between equivalents. Having the same decision made throughout the code makes everyone who has to read the code's life easier.
C++ can be made to do lots of things, and it doesn't achieve that by being elegant. It achieves that by trying to do everything possible under the sun. Because of that, C++ is and always will be much more complex than the other languages.
If your environment is solely your own and you don't care about having your users build something, fine.
Tuples in python are a reasonable tradeoff between not wanting to declare anything and the hassle of anonymous structure. This doesn't apply C++ where the equivalent is the POD struct:
//In the olden days we could not initialize a map inline like this
//Key -> metadata mapping. Unfortunately strutcts cannot be declared
//inside a template declaration.
struct TagData { int start; int length; StrToStr mapfun; };
const map<const string, const TagData> TagDataMap {
{"title" , { 3, 30, stripnulls}},
{"artist" , { 33, 30, stripnulls}},
{"album" , { 63, 30, stripnulls}},
{"year" , { 93, 4, stripnulls}},
{"comment" , { 97, 29, stripnulls}},
{"genre" , {127, 1, ord}}};
Creating a named struct pays off when it's time to use the Map, no extra locals or tie() needed to write clear code: //for loops over collections are finally convenient to use.
for(auto td : TagDataMap){
//C++ created a horrible precedent by making the data type of
//a map pair<K,V> instead of struct ValueType { K key; V value; };
auto tdd = td.second;
ret[td.first] = tdd.mapfun(sbuf.substr(tdd.start, tdd.length));
}
http://liveworkspace.org/code/bcd52515fb7161858e974b7ff3c0aa...Perhaps I should have mentioned that, though.
You will still need C++ for dealing with all the legacy C++ out there, but if you are starting a new project, you ought to be able to get the small, fast, efficient runtime advantages of a legacy monster like C++ from a much smaller, simpler language with a modern standard library.
Maybe it will be Go, but even if not, we need a simple, safe, productive language with modern features pre-installed (unicode strings, safe arrays, lists, maps) and a modern standard library that statically compiles to small, fast, native executables.
C++ with its "if you use the most recent 10% and pretend the old 90% doesn't exist, it's a great language" ethos is not what we need for new code.
That the language may have some Python-esque tendencies (given very particular language, compiler and library versions, and a depth of knowledge not required in the Python equivalent) doesn't seem that interesting. Especially when you consider that 9/10 C++ programmers you meet don't actually code in that style and don't intend to.
That said, I guess we have to live with C++03 for many more years. E.g. in one application that I co-developed, we use a library that can currently only be compiled without a substantial amount of effort on Visual Studio 2005 or 2008. The DLL compiled with older versions is not compatible with 2010 or 2012. So, we are basically stuck in 2005 or 2008 land for now.
I think only a hardcore C++ developer would claim that the author's sample is "succinct". Honestly, C++11 is still far behind the easiness of Python (or Scheme), even with Boost.
Funny, a decade ago Ada 95 (the "military" language for high-critical applications) looked like a monstrous over-designed beast when compared to C++. Today Ada 2012 looks elegant and even "small" when compared to C++11. How times have changed :-)
* Horrible error messages
* Even simple programs take ages to compile due to massive header files.
LLVM/Clang help on both fronts but it's still quite difficult.
D2 seems much more promising if you can do without the libraries.