Wednesday 19 March 2014

Why I don't like Lambda's (C++)

Why I don't like Lambda's (C++)
I think I'm going to be in the minority of programmers when I say that I don't like Lambda's, in case  you don't know, a Lambda is a piece of code you an run inline, so you can create it and run it where you're working... like this:

#include <iostream>

int main ()
{
int sum10 = []
{
int i = 0;
for (int k = 0; k < 10; ++k)
{
i += k;
}
return i;
}();
}

When the code here is compiled & run, the value entered into "sum10" is the result of the function which follows, traditionally we'd write this code like this:

#include <iostream>

int Sum10Function ();

int main ()
{
int sum10 = Sum10Function();
}

int Sum10Function ()
{
int i = 0; 
for (int l = 0; k < 10; ++k)
{
i += k;
}
return i;
}

You can see this "Sum10Function" is actually a function, it has a declaration and a definition.  And this is how I'd always prefer to do work with functions, I do not like Lambda's because they ignore the greatest power of functions like "Sum10Function".

And that power is having a known location, a location you can find and a location which one can easily find all references to, you know if you have a problem in the function that you have one body to find and you can find it easily.  Finding a Lambda, written inline, in a mass of source is not a boon, it is in fact a major headache.

Other authors on the topic, such as "Alex Allain" (http://www.cprogramming.com/c++11/c++11-lambda-closures.html) actually have the balls to intimate that the fact you can write the function inline like the lambda, without having to stop and go create a prototype and a body declaration is a strength, but he; like many others; totally ignores the elephant in the room and that is the maintainability of code.

Alex's other examples on that page also start to use function pointer or functor examples, the first "AddressBook" class example he gives is taking a type, and yes we can assign a lambda to a type "auto"... but we can also assign a function pointer to an "auto", so there's no difference in using a function pointer or using a lambda in that example, except the Lambda falls foul of my point, it's harder to find and manage the source for the function.

The article linked there is also one of the major references if one googles for Lambda's in C++.  And it has code examples, which work, but which he describes as "pretty good looking", but which I see as horrible... here's the examples, cleaned up in the same program:

#include <iostream>
#include <vector>
#include <algorithm>

int main()
{
using namespace std;

vector<int> v;
v.push_back(1);
v.push_back(2);
//...
for (auto itr = v.begin(), end = v.end(); itr != end; itr++)
{
cout << *itr << endl;
}
vector<int> vw;
vw.push_back(1);
vw.push_back(2);
//...
for_each(vw.begin(), vw.end(), [](int val)
{
cout << val << endl;
});

return 1;
}

They both do the same thing, they output the vector, but I don't think the second code looks better, I in fact think both examples need cleaning up, because to my eye they're miss leading, but my argument about the maintainability of Lambda's aside the second example does hint at the only use for them.  It hints that they can take the loop variable "val" as an input from the left, i.e. from the iteration over the vector in the "for_each" loop, rather than need to declare that iterator as in the first loop "auto itr = v.begin()"...

But I would still argue that this ease of throwing the code in there, leads to hard to maintain code, I think the first example cleaned up would leave a much more wholesome and maintainable code base...

#include <iostream>
#include <vector>

int main()
{
using namespace std;
vector<int> v = { 1, 2 };
for (auto itr = v.begin(); itr != v.end; ++itr)
{
cout << (*itr) << endl;
}
}

And there is my preferred implementation of that example, I find it simpler and easier to understand, and as you can see I'm using the C++11 declaration of the vector contents to ease understanding, so I'm not throwing an anti C++11 rant here, far from it, I've looked at Lambda's in Python and C# as well as here in C++ and I really don't like the syntax maintenance questions they bring up.

Sometimes they look and can behave very powerfully, but even with the for_each example above, I'd still much prefer this:

#include <iostream>
#include <vector>
#include <algorithm>

void Output (int p_Value)
{
std::cout << p_Value << std::endl;
}

int main ()
{
std::vector<int> v = { 1, 2 };
for_each (v.begin(), v.end(), Output);
}

I can see the "for_each" I can see it calls Output with each iteration, that code clearly has a semi-colon, and I can go find "Output" very easily.  I find this code is much cleaner and simpler to follow.

No comments:

Post a Comment