Ok, so here is a small comparison between the new and exciting lambda functions brought in C++ 0x and the functional functions defined in Erlang. Remember to compile all c++ code with the new flag for 0x:
/usr/local/bin/i686-pc-linux-gnu-gcc-4.6.0 -Wall -std=gnu++0x -lstdc++ test_lambdas.cpp
Let’s go for a simple function that will double a list in C++.
void test_double()
{
vector v1,v2;
for (long long i = 0; i<100000000 ;i++) {
v1.push_back(i);
}
v2.resize(v1.size());
transform(v1.begin(), v1.end(), v2.begin(),
[](int a){return 2*a;}
);
}
Here is the same version of the code in Erlang.
double()->
Seq = lists:seq(0,100000000),
Seq2 = lists:map(fun(A)->2*A end, Seq).
Still the code in Erlang looks shorter and neat. Let write a function that sums all the elements in an array.
void test_sum()
{
vector<int> v;
int sum = 0;
v.assign (1000,1);
for_each (v.begin(), v.end(),[&](int a){sum += a;});
cout << "sum:" << sum << endl;
}
Note that we have to capture the local variable “sum” by reference and not by value as we are change its value while traversing the array.
Now the Erlang version of the same algorithm.
sum() ->
V = [1 || X<-lists:seq(1,1000)],
lists:foldl(fun(A, Acc)-> A+Acc end,0, V).
Another example with classes, we have an array of pointers that points to objects of the class Student. In this function we just sort the students by the mark they got, so the best student will be at the end of the array.
class Student
{
public:
string name;
int id;
double mark;
};
void test_sort()
{
vector<Student*> v;
for (long long i = 0; i < 1000000; i ++) {
Student * s1 = new Student();
s1->name = "test";
s1->id = i;
s1->mark = (rand() % 100 + 1)+ (double)(rand() % 100 + 1)/100;
v.push_back(s1);
}
sort(v.begin(), v.end(), [](Student * a, Student * b) { return a->mark < b->mark ; });
for (auto & i:v){
cout << "student="<< i->name << " id=" << i->id << " mark=" << i->mark << endl;
}
}
Note the nice loop at the end that prints a student, it uses a new syntax introduced in 0x similar to Perl and avoid the long code needed to declare iterators when traversing a std container.
Now the same in Erlang:
-record(student, {name, id, mark}).
sort()->
{A1,A2,A3} = now(),
random:seed(A1, A2, A3),
V = [#student{id = X, name = "test", mark = random:uniform() * 100}
|| X<-lists:seq(1,1000000)],
lists:sort(fun(A,B)-> A#student.mark < B#student.mark end,V).
With the last example I am doing a performance tests with 10million objects. I am measuring only the sorting bit and ignoring the creation of the objects itself.
It turns out that Erlang took around 50sec to do the job while c++ took 11 secs. Probably this is not a fair test anyway so you might want to ignore the line I just wrote. The reason is that a list in Erlang is implemented as a “linked-list”, so we are comparing sorts of two different containers which doesn’t make sense.
Changing “vector” to “list” gives c++ down to 20 seconds which is still behind Erlang. Is there any Erlanger out there that wants to suggest a better algorithm for the Erlang version?
Just to finish this post, I want to give attention to one of the lambda function behavior that is very prone to errors.
class Test {
public:
void do_something(){
int n = 10;
auto lambda = [=] {return n;};
cout << lambda() << endl;
n = 11;
cout << lambda() << endl;
}
};
class Test2 {
public:
int n;
void do_something(){
n = 10;
auto lambda = [=] {return n;};
cout << lambda() << endl;
n = 11;
cout << lambda() << endl;
}
};
The first function will give us 10 and 10 as expected because we are catching “n” by value so the lambda function is going to have “n” to 10 because thats the value it had when the function was created.
However the last class Test2 will give us 10 and 11 as an output. And this is because “n” is a member of the class, and inside the lambda function you should read “return n” as “return this->n”. So in reality, what we are caching here is the pointer to “this”.