Skip to content

soheilhy/fn

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

43 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

fn

fn is a collection of functional programming helpers for C++11, inspired by Scala. It's header only and has no dependecies. It is efficient, lightweight and convenient (compared to the STL's <algorithm>).

fn provides a lazy evaluated view (similar to Scala's view) on which you can apply the usual functional programming tasks, including but not limited to filter, map, reduce, zip, fold_left, flat_map and for_all. Views are iteratble.

Some Examples

MAX: Find the maximum of elements in a container (e.g., vector, list, ...).

std::vector<int> v{1, 2, 3, 4, 5};
auto max = _(v).reduce([](int m, int i) { return std::max(m, i); });
// or simply by calling `max`:
auto max = _(v).max();
// or even prevent an extra copy by passing a pointer to the vector:
auto max = _(&v).max();

EVENS: Find all even elements in a vector.

std::vector<int> evens = _(&v).filter([](int i) { return i % 2 == 0; });
// or, append them to an existing container:
_(&v).filter([](int i) { return i % 2 == 0; }) >> &evens;
// or, print them:
_(&v).filter([](int i) { return i % 2 == 0; }) >> [](int i) {
  printf("%d\n", i);
};
// or, use a fancy filter operator:
_(&v) % [](int i) { return i % 2 == 0; } >> [](int i) {
  printf("%d\n", i);
};
// or, iterate over them:
for (auto i : _(&v) % [](int i) { return i % 2 == 0; }) {
  printf("%d\n", i);
}

XOR: Find the xor of all even elements in a vector.

std::vector<int> v{1, 2, 3, 4, 5};
auto r = _(&v).filter([](int i) { return i % 2 == 0; })
              .reduce([](int m, int i) { return m ^ i; });

WORDS OF SAME LENGTH: Count the number of words of each distinct length.

vector<string> words {"map", "fold", "filter", "reduce", "any"};
auto len_count =
    _(&words).map([](const string& w) { return w.size(); })
             .fold_left(map<int, int>(), [](map<int, int>&& cnt,
                                            size_t len) {
                                              cnt[len]++;
                                              return cnt;
                                          });

// And if your compiler is fully functional for C++14, you can
// write a lot less using _$ and _$$ macros. These macros rely on
// C++14's automatic type deduction for lambda parameters.
auto len_count =
    _(&words).map(_$ { return _1.size(); })
             .fold_left(map<int, int>(), _$$ {
                _1[_2]++;
                return _1;
              });

Euler #1: Find the sum of all the multiples of 3 or 5 below 1000.

// Here we use fn::range which is convenient, efficient, and iteratable.
int s = _(range(1, 1000))
            .filter([](int i) { return i % 3 == 0 || i % 5 == 0; })
            .sum();

Euler #4: Find the largest palindrome made from the product of two 3-digit numbers.

int max = _(range(999, 99, -1))
              .flat_map([](int i) {
                 return _(range(999, i, -1))
                     .map([i](int j) { return i * j; });
               })
              .filter([](int i) {
                 auto s = std::to_string(i);
                 return std::equal(s.begin(), s.end(), s.rbegin());
               })
              .max();

Views and Ranges

C++11 come with functional programming concepts but the syntax is not as pleasant as what you get in Scala or Haskell. The goal of fn is to provide a lightweight library with a simple and sweet syntax for common functional programming tasks.

fn provides two main concepts View (inspired by Scala's view) and Range (inspired by Python's xrange).

View

View simply embeds a container, or anything iteratble. To create a view, you need to call fn::_:

#include "fn/fn.h"

using fn::_;
using fn::range;

int main() {
  auto view = _({1, 2, 3, 4});
  for (auto i : view) {
    ...
  }
}

You can transform a view to another view using functions map, filter, flat_map:

#include "fn/fn.h"

using fn::_;
using fn::range;

int main() {
  auto view = _({-1, 2, -3, 4});
  auto f = view.filter([](int i) { return i > 0; });
  auto m = view.map([](int i) { return i * 2; });
  auto mm = view.flat_map([](int i) { return _(range(0, i)); });
  ...
}

Note that these functions are lazily evaluated, meaning that they won't be called unless you evaluate the view by calling the evaluate method or converting the view to a container:

#include <deque>
#include <list>
#include <vector>

#include "fn/fn.h"

using std::vector;

using fn::_;
using fn::range;

int main() {
  auto view = _({-1, 2, -3, 4});
  auto f = view.filter([](int i) { return i > 0; });
  vector<int> v1 = f.evaluate();
  vector<int> v2 = f;
  auto l = f.as_list();
  auto d = f.as_deque();
  ...
}

Also, if you call methods that result in a value (e.g., reduce, max, sum, first, and for_each), the view will be evaluated:

#include "fn/fn.h"

using fn::_;
using fn::range;

int main() {
  auto view = _({-1, 2, -3, 4});
  auto m = view.max();
  auto f = view.first();
  ...
}

Views are immutable and by default copy the container to pass to them. You can save for that copy by passing a pointer to fn::_ if you're sure the pointer will remain valid:

#include "fn/fn.h"

using fn::_;
using fn::range;

int main() {
  std::vector<int> vec {-1, 2, -3, 4};
  auto view = _(&vec);
  ...
}

Range

Range is pretty similar to python's xrange. To create a range, just call fn::range():

#include "fn/range.h"

using fn::range;

int main() {
  for (auto i : range(1, 10)) {
    ...
  }
}

You can also pass the range to _ create a view:

#include "fn/fn.h"
#include "fn/range.h"

using fn::range;
using fn::_;

int main() {
  auto sum = _(range(1, 11)).sum();
  ...
}

Helper macros for C++14

If your compiler supports C++14, you can exploit automatic type deduction for lambda parameters. Actually, fn has two macros to help you with that _$ and _$$ which respectively create lambdas of one and two parameters. You can access the paramters as _1 and _2.

auto a = _$ { return _1 * 2; }
auto b = _$$ { return std::max(_1, _2); }

How to use

Just add fn's include directory to your C++ include directories, and you are ready to go:

CXX -std=c++11 -I ${FN_HOME}/include/ ...

That's all. It's a header only library with no dependecies, no and no configuration.

Roadmap

  1. Parallelization.

About

Scala-like functional programming helpers for C++11.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published