Thursday, December 29, 2005

An observation concerning functional programming in C++

The following code was extracted from a live program.

namespace
{
class count_bytes
{
public:
count_bytes(size_t& total)
: total_(total)
{
}
void operator()(
const Element& element
)
{
total_ += element.bytes();
}
private:
size_t& total_;
};
}
size_t
ElementSet::bytes() const
{
size_t total = 0;
std::for_each(elements_.begin(), elements_.end(), count_bytes(total));
return total;
}

By my count that's 26 lines of code. To be fair I should admit that I tightened it up a bit. The original was well over 30 lines.

Compare that to:

size_t
ElementSet::bytes() const
{
size_t total = 0;
for(size_t i = 0; i < elements_.size; ++i)
{
total += elements_[i].bytes();
}
return total;
}


Ten lines (And yes, you could use an iterator rather than an index.)

Functional Programming Motto: You may have to type a whole lot more, but at least your code will be harder to understand.

Actually functional programming is just fine when you need it. The problem happens when it's the "Wrong Tool For The Job[TM]." There seems to be a lot of that goin' 'round [sigh].

7 comments:

Anonymous said...

Another great way to do it is:

namespace
{
void count_bytes(
size_t& total,
const Element& element
)
{
total += element.bytes();
}
}

size_t ElementSet::bytes(
) const
{
size_t total = 0;
std::for_each(elements_.begin(), elements_.end(), boost::bind(&count_bytes, boost::ref(total), _1));
}

That's much more expressive!

Dale Wilson said...

> That's much more expressive!
Well, it depends on your definition of "expressive" I agree it's more concise, but to me it is more obscure than the original -- you've tossed in a boost::bind and a boost:ref, not to mention a _1 whatever that is.

I sure hope it works, 'cause if it doesn't it'll be a bitch to debug.

(PS: ...and there's a bug in it ;-) )

Anonymous said...

That will teach me to not try to compile first ;) . Who needs return values, anyway?!

What is meant by expressive? Unless you understand the semantics of a std::for_each, it can be pretty daunting. Same with boost::bind or boost::lambda or any other libraries out there.

I would say that what makes my code, and the original, expressive is that if you're just interested in the bytes() method, you can just look at it alone and get the idea without getting mired in the language constructs and details. I feel that this expressiveness is increasingly important as you do more complex things; the loop would be the same but perhaps count_bytes would vary depending on type. Is this more expressive, more generic, or both? Are the two tied?

Anonymous said...

namespace {
struct Widget {
int bytes_;
int bytes() const { return bytes_; }
};
int countem(const std::vector<Widget>& elements) {
struct Local {
static int func(int soFar, const Widget& w) {
return soFar + w.bytes();
}
};
return std::accumulate(elements.begin(), elements.end(), 0,
Local::func);
}
void test() {
Widget one = {1}, two = {2}, three = {3}, four = {4};
std::vector<Widget> vw;
vw.reserve(4);
vw.push_back(one);
vw.push_back(two);
vw.push_back(three);
vw.push_back(four);
int total = countem(vw);
std::cout << total << std::endl;
}
}

PS: I tried, to no avail, to use boost::lambda for this...
std::accumulate(elements.begin(), elements.end(), 0, _1 + bind(&Widget::bytes, _2));

Anonymous said...

With Boost Lambda:

#include <numeric>
#include <vector>

#include <boost/lambda/bind.hpp>
#include <boost/lambda/lambda.hpp>

namespace {
struct Widget {
int bytes_;
int bytes() const { return bytes_; }
};
int countem(const std::vector<Widget>& elements) {
using namespace boost::lambda;
return std::accumulate(elements.begin(), elements.end(), 0,
_1 + bind(&Widget::bytes, _2));
}
void test() {
//same as prior post
}
}

Turns out that the other day I had the correct lambda expression but the wrong include files!

Anonymous said...

I wouldn't blame "functional programming" as such. Rather the clumsy implementation of functional programming concepts in c++. The functional programming motto is not "you have to type a whole lot more..." but rather the opposite.

In a reasonable functional programming language, like Haskell (www.haskell.org), the entire loop would look something like

allbytes = sum (map bytes elements_)

Or, if we don't want to use the already-available sum function:

allbytes = foldr (\s elem -> s + bytes(elem)) elements_

Here, "foldr" takes a function (s,elem)->s+bytes(elem) and applies this function recursively to the entire list elements_

Either way, that's one line of code (by my count =).

/Erik

Anonymous said...

cout << accumulate(v.begin(), v.end(), 0) << "\n";