Ivan Čukić

Curry all over the C++11

Currying is all-present in functional programming. And we are not really using it.

What is currying? It is a 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.

For example, lets look at a simpler version of fprintf function that looks something like this:

  print: (stream, message) -> do something

  // Invoked like this:
  print(cout, "Hello")

If we use the method often with specific streams (like cout, cerr and a log file), we can define it in a different manner, so that we could easily make additional versions specific to those streams. And, we don’t need to impose our opinions of what a user wants by providing a set of convenience functions ourselves (ok, qDebug() is something that everybody wants, but you get the point).

  print: stream -> function called print_to
  print_to: message -> do something

  // It would be invoked like this:
  // (note that the print_to function doesn't
  // really need to be named)
  print(cout)("Hello")

  // or
  log = print(logging_stream)
  err = print(cerr)

  log("Hello")
  err("Error world")

So, this seems totally unneeded, right? It can be quite useful when working with generic code (for example STL algorithms). The following example demonstrates vector partitioning for different predicates.

  #include <string>
  #include <iostream>
  #include <algorithm>
  #include <iterator>

  auto less_than = [] (int i) {
      return [i] (int j) {
          return j < i;
      };
  };

  template <typename B, typename M, typename E>
  inline void write_partitions(B b, M m, E e)
  {
      std::copy(b, m, std::ostream_iterator<int>(std::cout, " "));
      std::cout << "  |  ";

      std::copy(m, e, std::ostream_iterator<int>(std::cout, " "));
  }

  int main(int argc, const char *argv[])
  {
      std::vector<int> ns { 1, -2, 3, -4, 5, -6, 7, -8, 9, -10 };

      // Original vector
      std::copy(ns.begin(), ns.end(),
              std::ostream_iterator<int>(std::cout, " "));
      std::cout << " - Original vector" << std::endl;

      // Partitions for numbers ranging from 0 to 9
      for (int i = 0; i < 10; i++) {
          auto p = std::partition(ns.begin(), ns.end(), less_than(i));

          write_partitions(
              ns.begin(), 
              p,
              ns.end());
          std::cout << " - Predicate: _ < " << i << std::endl;
      }

      return 0;
  }

This is the output it generates:

  -10 -2 -8 -4 -6   |  5 7 3 9 1  - Predicate: _ < 0
  -10 -2 -8 -4 -6   |  5 7 3 9 1  - Predicate: _ < 1
  -10 -2 -8 -4 -6 1   |  7 3 9 5  - Predicate: _ < 2
  -10 -2 -8 -4 -6 1   |  7 3 9 5  - Predicate: _ < 3
  -10 -2 -8 -4 -6 1 3   |  7 9 5  - Predicate: _ < 4
  -10 -2 -8 -4 -6 1 3   |  7 9 5  - Predicate: _ < 5
  -10 -2 -8 -4 -6 1 3 5   |  9 7  - Predicate: _ < 6
  -10 -2 -8 -4 -6 1 3 5   |  9 7  - Predicate: _ < 7
  -10 -2 -8 -4 -6 1 3 5 7   |  9  - Predicate: _ < 8
  -10 -2 -8 -4 -6 1 3 5 7   |  9  - Predicate: _ < 9
Loading comments...