Fun with Lambdas: C++14 Style (part 1)
It's common knowledge that Functional Programming is spreading like a wildfire in mainstream languages. Latest promoted languages: Java 8 and C++, both of which now support lambdas. So, let the lambdas begin! and may the fun be ever on your side. The same text is available in slides form on Slideshare. This blog post and the talk/slides are inspired by JSON inventor Douglas Crockford.
Write an Identity function that takes an argument and returns the same argument.
Write an Identity function that takes an argument and returns the same argument.
auto Identity = [](auto x) { return x; }; Identity(3); // 3Write 3 functions add, sub, and mul that take 2 parameters each 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; }; int mul (int x, int y) { return x * y; };Write a function, identityf, that takes an argument and returns an inner class object that returns that argument.
auto identityf = [](auto x) { class Inner { int x; public: Inner(int i): x(i) {} int operator() () { return x; } }; return Inner(x); }; identityf(5)(); // 5Write a function, identityf, that takes an argument and returns a function that returns that argument.
auto identityf = [](auto x) { return [=]() { return x; }; }; identityf(5)(); // 5Lambda != Closure
- A lambda is just a syntax sugar to define anonymous functions and function objects.
- A closure in C++ is a function object which closes over the environment in which it was created. The line #2 above defines a closure that closes over x.
- A lambda is a syntactic construct (expression), and a closure is a run-time object, an instance of a closure type.
- C++ closures do not extend the lifetime of their context. (If you need this use shared_ptr)
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(); // 0 range(); // 1Write a function that adds from two invocations.
auto addf = [](auto x) { return [=](auto y) { return x+y; }; }; addf(5)(4); // 9Write a function swap that swaps the arguments of a binary function.
auto swap =[](auto binary) { return [=](auto x, auto y) { return binary(y, x); }; }; swap(sub)(3, 2); // -1Write a function twice that takes a binary function and returns a unary function that passes its argument to the binary function twice.
auto twice =[](auto binary) { return [=](auto x) { return binary(x, x); }; }; twice(add)(11); // 22Write a function that takes a binary function and makes it callable with two invocations.
auto applyf = [](auto binary) { return [=](auto x) { return [=](auto y) { return binary(x, y); }; }; }; applyf(mul)(3)(4); // 12Write a function that takes a function and an argument and returns a function that takes the second argument and applies the function.
auto curry = [](auto binary, auto x) { return [=](auto y) { return binary(x, y); }; }; curry(mul, 3)(4); // 12Currying (schönfinkeling)
- Currying is the technique of transforming a function that takes multiple arguments in such a way that it can be called as a chain of functions, each with a single argument.
- In lambda calculus functions take a single argument only.
- Must know Currying to understand Haskell.
- Currying != Partial function application
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); //10Without creating a new function show 3 ways to create the inc function.
auto inc = curry(add, 1); auto inc = addf(1); auto inc = applyf(add)(1);Write a function composeu that takes two unary functions and returns a unary function that calls them both.
auto composeu =[](auto f1, auto f2) { return [=](auto x) { return f2(f1(x)); }; }; composeu(inc1, curry(mul, 5))(3) // 20Write a function that returns a function that allows a binary function to be called exactly 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); // 7Write 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) // 31 binaryc(mul)(5,6,[](int a) { return a+1; });Write 3 functions:
- unit – same as Identityf
- stringify – that stringifies its argument and applies unit to it
- bind – that takes a result of unit and returns a function that takes a callback and returns the result of callback applied 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()); }; };Then verify.
std::cout << "Left Identity " << stringify(15)() << "==" << bind(unit(15))(stringify)() << std::endl; std::cout << "Right Identity " << stringify(5)() << "==" << bind(stringify(5))(unit)() << std::endl;Why are unit and bind special? Read more about them here.