New features of lambdas in C ++ 14

Original author: Sumant Tambe
  • Transfer
  • Tutorial
Everyone knows that functional programming is spreading fast in modern programming languages. Recent examples are Java 8 and C ++, both of which now support lambda functions.

So, let's begin (and may the fun come with us). This text is also available as slides on Slideshare . The author of this article was inspired 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); // 3

Translator's Note : New compared to C ++ 11 is the ability to not specify type names.

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, the original argument will be returned:

auto identityf = [](auto x) {
  class Inner {
    int x;
    public: Inner(int i): x(i) {}
    int operator() () { return x; }
  };
  return Inner(x);
};
identityf(5)(); // 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)(); // 5

Note: lambda functions ≠ 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 denotes "capture of context."
  • Not all lambdas are closures, and not all closures are lambdas.
  • Closures in C ++ are common objects that you can call.
  • Closures do not extend the life of the objects they use (for this, use shared_ptr).

A function that returns a generator function that returns numbers from a given 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(); // 0
range(); // 1

A function that takes numbers one at a time and adds them:

auto addf = [](auto x) {
  return [=](auto y) {
    return x+y;
  };
};
addf(5)(4); // 9

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); // -1

A twice function that takes a binary function and returns a unary function that passes an argument to the binary twice:

auto twice =[](auto binary) {
  return [=](auto x) {
    return binary(x, x);
  };
};
twice(add)(11); // 22

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); // 12

A curry function that takes a binary function and an 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); // 12

Note: Currying (schrying, schönfinkeling) is the transformation of a function that takes several arguments into a chain of functions that take one argument.
  • In a λ-analysis, all functions take only one argument.
  • You need to understand how currying works in order to learn Haskell.
  • Currying is a partial application of a function.

Partial use 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); //10

Three options, how to get a function that adds one to the argument without creating a new function:

auto inc = curry(add, 1);
auto inc = addf(1);
auto inc = applyf(add)(1);

Implementation of composition of functions:

auto composeu =[](auto f1, auto f2) {
  return [=](auto x) {
    return f2(f1(x));
  };
};
composeu(inc1, curry(mul, 5))(3) // (3 + 1) * 5 = 20

A function that takes a binary function and modifies it so that it can be called only 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); // 7

A function that takes a binary function and returns a function that takes two arguments and 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; }); // то же самое

Finally, we 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 applying it 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 this is a monad .

Read the second post from the author’s blog series .

Also popular now: