Skip to main content

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.
auto Identity = [](auto x) {
  return x;
};
Identity(3); // 3
Write 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)(); // 5

Write a function, identityf, that takes an argument and returns a function that returns that argument.
auto identityf = [](auto x) {
  return [=]() { return x; };
};
identityf(5)(); // 5

Lambda != 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)
Write a function that produces a function that returns values in a range.
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
Write a function that adds from two invocations.
auto addf = [](auto x) {
  return [=](auto y) { 
    return x+y; 
  };
};
addf(5)(4); // 9
Write 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); // -1
Write 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); // 22
Write 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); // 12
Write 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); // 12
Currying (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
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); //10
Without 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) // 20
Write 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); // 7
Write 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.

Comments

HiroshimaCC said…
Is the Right Identity supposed to be true?
Sumant said…
@HiroshimaCC: Good point. There was a oversight because "5" and 5 look the same when printed to stdout. The code now updated.
Jagan said…
I am unable to figure out how to get the values in range. I mean how to execute it to get the values in range ?

fromto(0, 10); would return the lambda and from(0, 10)(); will only print 0.
Isn't that the case ?
Jagan said…
This comment has been removed by the author.
Igor Mikushkin said…
Please correct identityf. It should use templated class according to description.
Sumant said…
@Jagan. fromto returns a range lambda that returns numbers from 0 to 9 one at a time. I've updated the code to reflect that.

@Igor: Are you referring to identityf::Inner? For simplicity, I'm using just an int member.
Stuart said…
@Sumant, I enjoyed this post, as I enjoy most of your posts. Thanks for sharing!
Michael said…
Interesting, thank you!

The Lambda != Closure section is confusing though.

"A lambda is just an anonymous function." -- I understand the desire to make the explanation as simple as possible, but here it happens at the cost of correctness. "Just functions" don't carry state. Perhaps "A lambda is just a syntax sugar to define anonymous functions and function objects"?

"Not all closures are lambdas and not all lambdas are closures." -- sounds weird. A lambda is a syntactic construct (expression), and a closure is a run-time object, an instance of a closure type. Neither can "be" the other.
Sumant said…
@Michael: I agree. The text was confusing and somewhat incorrect. I've taken the liberty to use your words in the updated post.
joaof said…
you can make the first identityf more like the identityf lamba:

auto identityf = [](auto x)
{
class inner
{
public:
using X = decltype(x);
X mx;
inner(X x): mx(x) {}
X operator()() { return mx; }
};
return inner{x};
};

(you version of inner uses int)
mjohn5 said…
Why are add and sub auto, but mult explicitly uses int?
Sumant said…
@mjohn5 No good reason! Just a reminder that good old functions are equally good candidates.
Unknown said…
Great read; thanks for posting!

Jumping on the back of @joaof's comment: Using decltype to ascertain the type of auto x allows for using an interesting pattern of, what the D Language community calls, Voldemort types. Walter Bright wrote an interesting article about it which you can read here.

Interesting to note that, unlike in D, we can later retrieve the type of Inner outside of the lambda:

auto foo = identifyf(5);
foo(); //5
using Foo = decltype(foo);
Foo bar(10);
bar(); //10
Sumant said…
@dave Aliasing the type of lambda is fine but are you sure about the rest? I get compiler error for "Foo bar(10)" and is_constructible returns false. Besides, there are many limitations like missing copy-assignment operator for lambdas. class Inner, however it copy-assignable.

Live code: http://melpon.org/wandbox/permlink/mfteteOhqXrZGxVS



Unknown said…
@Sumant

I am quite sure about the rest: http://ideone.com/h2ZRs3 :) I think, maybe, that I was confusing something in your example contra mine.

/Dave
Sumant said…
@Dave Oh yeah, when you have a named inner class inside a lambda, then what you are saying makes sense. However, if the lambda returns a closure then there are few limitations.
Unknown said…
Yup, I missed that, admittedly, not-so-subtle difference :)
black_13 said…
you stated: "C++ closures do not extend the lifetime of their context. (If you need this use shared_ptr)" could you give
and example?
mjohn5 said…
The point is that c++ lambdas are *not* closures because they just honor the external lifetime of the object without making sure the object survives as long as the lambda does. In c++ one way to lengthen an object's lifetime is to make it a shared pointer, which is reference counted.
Anonymous said…
you stated: "C++ closures do not extend the lifetime of their context. (If you need this use shared_ptr)" could you give
and example?
gclub

Popular Content

Unit Testing C++ Templates and Mock Injection Using Traits

Unit testing your template code comes up from time to time. (You test your templates, right?) Some templates are easy to test. No others. Sometimes it's not clear how to about injecting mock code into the template code that's under test. I've seen several reasons why code injection becomes challenging. Here I've outlined some examples below with roughly increasing code injection difficulty. Template accepts a type argument and an object of the same type by reference in constructor Template accepts a type argument. Makes a copy of the constructor argument or simply does not take one Template accepts a type argument and instantiates multiple interrelated templates without virtual functions Lets start with the easy ones. Template accepts a type argument and an object of the same type by reference in constructor This one appears straight-forward because the unit test simply instantiates the template under test with a mock type. Some assertion might be tested in

Multi-dimensional arrays in C++11

What new can be said about multi-dimensional arrays in C++? As it turns out, quite a bit! With the advent of C++11, we get new standard library class std::array. We also get new language features, such as template aliases and variadic templates. So I'll talk about interesting ways in which they come together. It all started with a simple question of how to define a multi-dimensional std::array. It is a great example of deceptively simple things. Are the following the two arrays identical except that one is native and the other one is std::array? int native[3][4]; std::array<std::array<int, 3>, 4> arr; No! They are not. In fact, arr is more like an int[4][3]. Note the difference in the array subscripts. The native array is an array of 3 elements where every element is itself an array of 4 integers. 3 rows and 4 columns. If you want a std::array with the same layout, what you really need is: std::array<std::array<int, 4>, 3> arr; That's quite annoying for

Covariance and Contravariance in C++ Standard Library

Covariance and Contravariance are concepts that come up often as you go deeper into generic programming. While designing a language that supports parametric polymorphism (e.g., templates in C++, generics in Java, C#), the language designer has a choice between Invariance, Covariance, and Contravariance when dealing with generic types. C++'s choice is "invariance". Let's look at an example. struct Vehicle {}; struct Car : Vehicle {}; std::vector<Vehicle *> vehicles; std::vector<Car *> cars; vehicles = cars; // Does not compile The above program does not compile because C++ templates are invariant. Of course, each time a C++ template is instantiated, the compiler creates a brand new type that uniquely represents that instantiation. Any other type to the same template creates another unique type that has nothing to do with the earlier one. Any two unrelated user-defined types in C++ can't be assigned to each-other by default. You have to provide a