2008-07-06

STL, redux

Funny to see that claiming that the following code is readable stirred up quite a few comments.
std::transform( list.begin(), list.end(), std::back_inserter( res ), std::bind2nd( std::plus<int>(), 42 ) );

Now, there are basically three points that you guys like to point out:

Try another language - the short answer, no. The longer answer, I like C/C++, much because I've grown to know them, but also because I can see the actual machine code that the code I write will produce. This, to me as an electronics guy, is really neat. It gives me a sense of control.

Also, I've tried Haskell and Lisp (and Excel, if you want another functional language), and I like the concept, but creating something large and actually useful using these tools... I don't know. I'm sure that you can point out a million examples, but... nah. Not for me. At least not for proper work.

C++0x can do this better - but why change a winning concept. Lambda expressions does not belong in C++. The C-family of languages contains imperative languages, not functional ones. Call me old, call me granddad, but I like it the way it is. If I could go back in time, I'd rather spend my time pushing MS to improve their implementation than changing the specs.

You call that readability? - yes I do, and seeing examples from other languages, I do so even more. The trick is to read code from the right direction. For example, compare reading an ordinary mathematical expression to reading it in RPN. Neither is fun, but as soon as you know how to approach it, both are simple.

I try to read C/C++ from the inside out. Take, the troublesome expression that started this. I would start with the arguments. The first two are simple - just limiting the input data. The third is a bit more tricky: std::back_inserter( res ). Try starting from the inside, what is res? A list of integers. What could a back_inserter do to a list? Perhaps insert stuff at the back of it.

Continuing with the operation: std::bind2nd( std::plus<int>(), 42 ). Again, starting from the inside, plus probably adds two arguments together, integers judging from the template specialization. So, bind2nd? Coming from an engineering background (and having done this a couple of times) I thing that it is quite clear that it binds (locks) the second argument to a value, in this case 42. I can admit that this is not 100% clear if you're not familiar with the topic.

So, taking one last step out I find myself looking at the word transform. So, a list of items is transformed into another list. Again, not too hard to grasp, but you might want to look up the details in an STL reference. Speaking of such, SGI hosts are really good STL reference. So, for the curios readers, given these links I think that you also can call the transform expression readable:

7 Comments:

At 12:19 PM, Blogger Andre said...

I completely agree with you. The biggest advantage of STL isn't that every child knows how to read or write it, but that everything is done in a consistent way (imho). If you grasp the whole concept once, it appears really elaborate.
In the other comments I especially liked the statement that
list = list /map/ (int i) { return i+42; };
was more readable.

 
At 12:34 PM, Anonymous Anonymous said...

It's funny that you think C++ is an imperative language and functional programming doesn't fit it while you're enjoying the functional aspects of STL programming. What you're doing is an example of functional style of programming and lambda expressions fit this kind of programming very well. Also I should add that C++0x lambdas are not closures as far as I know and because of this they are not "functional enough" and won't make C++ a functional language.

 
At 12:46 PM, Blogger Thomas said...

Since you posted this on planet KDE (which is Qt centric ;) I'm wondering if the above would be more readable using Qt... I'm still not quite sure what it is you are tying to do so I can't suggest anything.

The thing is, STL is powerful, but the user experience is lacking. Long compiler error messages and API that is not very C++ like (really, underscores and abbreviations in the method names?), methods with 5 arguments?

Anyway, I can see both arguments, and I largely agree with them. I just wanted to point out that this is not something about languages, this is discussion about API.

Cheer!

 
At 1:16 PM, Anonymous Tim said...

I've never been happy with C++'s attempts at functional programming. It strikes me as a barely acceptable imitation.

It's arguably readable, but certainly not beautiful in C++ syntax.

And without the lambda expressions you object to it's really nothing more than a curiosity. It's rarely worth creating a named function to do the task, and never worth surprising people with the (to C++) odd style.

 
At 1:24 PM, Blogger Vladimir Prus said...

What are you trying to claim? That this way of building functional object can be understood by humans? No doubt it can be. RPN can also be understood by humans, but we still prefer more obvious notations. And for the task of adding 42 to each element of collection, plain old for loop is perfectly adequate.

I was in love with this functional-objects-everywhere approach some 5 years ago, but as soon as you stop doing trivial things, the code becomes totally unreadable.

So, I don't think this programming style will see widespread use until lambda is widely available.

 
At 5:38 PM, Blogger Titus Brown said...

It's not readable; you're doing too many things on one line of code.

 
At 7:26 PM, Blogger Diederik said...

> Since you posted this on planet KDE (which is Qt centric ;)
> I'm wondering if the above would be more readable using Qt.

How about:

QList< int > list;
foreach( int &item, list ) {
item += 42;
}

I guess it's a matter of personal style. Coding in STL was the subject I least enjoyed at school. It takes a while before you find out this kind of magic:

-----

template< class T > struct printName : public unary_function< T, void >
{
// Constructor
printName(ostream& out) : os_(out) {

}

void operator () (T object) {
os_ << " " << object->getName() << endl;
}

ostream &os_;
};

for_each(students.begin(), students.end(), printName< Student * >(cerr) )

----

I'd rather type:

foreach( Student *student, students ) { cerr << student->getName() << endl;
}

:-p

 

Post a Comment

<< Home