Everyone knows that functional programming spreads with rapidity of fire in modern programming languages. Recent examples are Java 8 and C ++, both of which now support lambda functions.
So let's start (and let the fun come with us). This text is also available as a slide on
Slideshare . The author was inspired to write this article by JSON creator
Douglas Crockford .
An Identity function that takes an argument and returns the same argument:
')
auto Identity = [](auto x) { return x; }; Identity(3);
Translator's note : New compared to C ++ 11 is the ability to omit type names.
The functions add, sub, and mul, which take two arguments and return their sum, difference, and product, respectively:
auto add = [](auto x, auto y) { return x + y; }; auto sub = [](auto x, auto y) { return x - y; }; auto mul = [](auto x, auto y) { return x * y; };
The identityf function, which takes an argument and returns an instance of the inner class, when called, which will return the original argument:
auto identityf = [](auto x) { class Inner { int x; public: Inner(int i): x(i) {} int operator() () { return x; } }; return Inner(x); }; identityf(5)();
Another identityf implementation that returns not an object, but a function (yes, now you can return functions):
auto identityf = [](auto x) { return [=]() { return x; }; }; identityf(5)();
Note: Lambda function ≠closure:
- Lambda is just an anonymous function.
- A closure is a function that uses objects from the environment in which it was declared. In the second line of the previous example, the equal sign means “capture context”.
- Not all lambdas are closures, and not all closures are lambdas.
- Closures in C ++ are normal objects that can be called.
- Closures do not prolong the life of the objects that they use (for this you need to use shared_ptr).
A function that returns a generator function that returns numbers from a specified interval:
auto fromto = [](auto start, auto finish) { return [=]() mutable { if(start < finish) return start++; else throw std::runtime_error("Complete"); }; }; auto range = fromto(0, 10); range();
A function that takes numbers one by one and adds them together:
auto addf = [](auto x) { return [=](auto y) { return x+y; }; }; addf(5)(4);
A function that swaps the arguments of another function:
auto swap =[](auto binary) { return [=](auto x, auto y) { return binary(y, x); }; }; swap(sub)(3, 2);
The twice function, which takes a binary function and returns a unary function, which passes the argument to a binary function twice:
auto twice =[](auto binary) { return [=](auto x) { return binary(x, x); }; }; twice(add)(11);
A function that takes a binary function and returns a function that takes two arguments in turn:
auto applyf = [](auto binary) { return [=](auto x) { return [=](auto y) { return binary(x, y); }; }; }; applyf(mul)(3)(4);
A currying function that takes a binary function and argument and returns a function that takes a second argument:
auto curry = [](auto binary, auto x) { return [=](auto y) { return binary(x, y); }; }; curry(mul, 3)(4);
Note:
Currying (currying, schönfinkeling) is the conversion of a function that takes several arguments into a chain of functions that take on one argument.
- In the λ-analysis, all functions take only one argument.
- You need to understand how currying works to learn Haskell.
- Currying ≠partial application of the function.
Partial application of the function:
auto addFour = [](auto a, auto b, auto c, auto d) { return a+b+c+d; }; auto partial = [](auto func, auto a, auto b) { return [=](auto c, auto d) { return func(a, b, c, d); }; }; partial(addFour,1,2)(3,4);
Three options for how, without creating a new function, to get a function that adds one to the argument:
auto inc = curry(add, 1); auto inc = addf(1); auto inc = applyf(add)(1);
The implementation of the composition of functions:
auto composeu =[](auto f1, auto f2) { return [=](auto x) { return f2(f1(x)); }; }; composeu(inc1, curry(mul, 5))(3)
A function that takes a binary function and modifies it so that it can only be called once:
auto once = [](auto binary) { bool done = false; return [=](auto x, auto y) mutable { if(!done) { done = true; return binary(x, y); } else throw std::runtime_error("once!"); }; }; once(add)(3,4);
A function that takes a binary function and returns a function that takes two arguments and a callback:
auto binaryc = [](auto binary) { return [=](auto x, auto y, auto callbk) { return callbk(binary(x,y)); }; }; binaryc(mul)(5, 6, inc)
Finally, write the following three functions:
- unit is the same as identityf;
- stringify - turns its argument into a string and applies unit to it;
- bind - takes the result of unit and returns a function that takes a callback and returns the result of its application to the result of unit.
auto unit = [](auto x) { return [=]() { return x; }; }; auto stringify = [](auto x) { std::stringstream ss; ss << x; return unit(ss.str()); }; auto bind = [](auto u) { return [=](auto callback) { return callback(u()); }; };
Now make sure that everything works:
std::cout << "Left Identity " << stringify(15)() << "==" << bind(unit(15))(stringify)() << std::endl; std::cout << "Right Identity " << stringify(5)() << "==" << bind(stringify(5))(unit)() << std::endl;
What is so interesting about the unit and bind functions? The fact is that it is a
monad .
Read the
second post from the series in the author's blog.