Giter VIP home page Giter VIP logo

dobiasd / functionalplus Goto Github PK

View Code? Open in Web Editor NEW
2.0K 72.0 167.0 2.8 MB

Functional Programming Library for C++. Write concise and readable C++ code.

Home Page: http://www.editgym.com/fplus-api-search/

License: Boost Software License 1.0

C++ 95.73% Shell 0.16% Elm 2.47% JavaScript 0.02% HTML 0.13% CSS 0.26% CMake 0.74% Python 0.49%
range composition stl functional-programming algorithms header-only c-plus-plus c-plus-plus-14 c-plus-plus-17 library cpp cpp14

functionalplus's Introduction

logo

CI (License Boost 1.0)

FunctionalPlus

helps you write concise and readable C++ code.

Table of contents

Introduction

Great code should mostly be self-documenting, but while using C++ in reality you can find yourself dealing with low-level stuff like iterators or hand-written loops that distract from the actual essence of your code.

FunctionalPlus is a small header-only library supporting you in reducing code noise and in dealing with only one single level of abstraction at a time. By increasing brevity and maintainability of your code it can improve productivity (and fun!) in the long run. It pursues these goals by providing pure and easy-to-use functions that free you from implementing commonly used flows of control over and over again.

Say you have a list of numbers and are interested in the odd ones only.

bool is_odd_int(int x) { return x % 2 != 0; }

int main()
{
    typedef vector<int> Ints;
    Ints values = {24, 11, 65, 44, 80, 18, 73, 90, 69, 18};
    // todo: get odd numbers from values ...
}

There are different possibilities to attain your goal. Some of them are:

  1. write a (range based) for loop
    Ints odds;
    for (int x : values)
    {
        if (is_odd_int(x))
        {
            odds.push_back(x);
        }
    }
  1. use std::copy_if from the STL
    Ints odds;
    std::copy_if(std::begin(values), std::end(values),
            std::back_inserter(odds), is_odd_int);
  1. use keep_if from FunctionalPlus
    auto odds = fplus::keep_if(is_odd_int, values);

If you think version 3 could be the one most pleasant to work with, you might like FunctionalPlus. And if you still think the hand-written for loop is easier to understand, also consider what would happen if the loop body (i.e. a corresponding lambda function in the call to fplus::keep_if) would be much longer. When reading keep_if you would still immediately know that odds can only contain elements that came from values and were selected by some, possibly complicated, predicate. In the for loop case you have no idea what is happening until you read the whole loop body. The loop version probably would need a comment at the top stating what the use of keep_if would tell at first glance.

Usage examples

Below are some short examples showing nice things you can do with functions and containers using FunctionalPlus.

The same old song

You can test the content of a container for various properties, e.g.

#include <fplus/fplus.hpp>
#include <iostream>

int main()
{
    std::list<std::string> things = {"same old", "same old"};
    if (fplus::all_the_same(things))
        std::cout << "All things being equal." << std::endl;
}

The I in our team

There also are some convenience functions for retrieving properties of containers. For example you can count the occurrences of a character in a string.

#include <fplus/fplus.hpp>
#include <iostream>

int main()
{
    std::string team = "Our team is great. I love everybody I work with.";
    std::cout << "There actually are this many 'I's in team: " <<
        fplus::count("I", fplus::split_words(false, team)) << std::endl;
}

Output:

There actually are this many 'I's in team: 2

The cutest kitty

Finding the highest rated element in a container is very simple compared to a hand-written version(1, 2).

#include <fplus/fplus.hpp>
#include <iostream>

struct cat
{
    double cuteness() const
    {
        return softness_ * temperature_ * roundness_ * fur_amount_ - size_;
    }
    std::string name_;
    double softness_;
    double temperature_;
    double size_;
    double roundness_;
    double fur_amount_;
};

void main()
{
    std::vector<cat> cats = {
        {"Tigger",   5, 5, 5, 5, 5},
        {"Simba",    2, 9, 9, 2, 7},
        {"Muffin",   9, 4, 2, 8, 6},
        {"Garfield", 6, 5, 7, 9, 5}};

    auto cutest_cat = fplus::maximum_on(std::mem_fn(&cat::cuteness), cats);

    std::cout << cutest_cat.name_ <<
        " is happy and sleepy. *purr* *purr* *purr*" << std::endl;
}

Output:

Muffin is happy and sleepy. *purr* *purr* *purr*

Function composition, binding, and map creation

Let's say you have the following function given.

std::list<int> collatz_seq(int x);

And you want to create an std::map<std::uint64_t, std::string> containing string representations of the Collatz sequences for all numbers below 30. You can implement this nicely in a functional way too.

#include <fplus/fplus.hpp>
#include <iostream>

// std::list<std::uint64_t> collatz_seq(std::uint64_t x) { ... }

int main()
{
    typedef std::list<int> Ints;

    // [1, 2, 3 ... 29]
    auto xs = fplus::numbers<Ints>(1, 30);

    // A function that does [1, 2, 3, 4, 5] -> "[1 => 2 => 3 => 4 => 5]"
    auto show_ints = fplus::bind_1st_of_2(fplus::show_cont_with<Ints>, " => ");

    // A composed function that calculates a Collatz sequence and shows it.
    auto show_collats_seq = fplus::compose(collatz_seq, show_ints);

    // Associate the numbers with the string representation of their sequences.
    auto collatz_dict = fplus::create_map_with(show_collats_seq, xs);

    // Print some of the sequences.
    std::cout << collatz_dict[13] << std::endl;
    std::cout << collatz_dict[17] << std::endl;
}

Output:

[13 => 40 => 20 => 10 => 5 => 16 => 8 => 4 => 2 => 1]
[17 => 52 => 26 => 13 => 40 => 20 => 10 => 5 => 16 => 8 => 4 => 2 => 1]

The functions shown not only work with default STL containers like std::vector, std::list, std::deque, std::string etc. but also with custom containers providing a similar interface.

Type deduction and useful error messages

FunctionalPlus deduces types for you where possible. Let's take one line of code from the Collatz example:

    auto show_collats_seq = fplus::compose(collatz_seq, show_ints);

collatz_seq is a function taking an uint64_t and returning a list<uint64_t>. show_ints takes a list<uint64_t> and returns a string. By making use of function_traits, written by kennyim, it is possible to automatically deduce the expression fplus::compose(collatz_seq, show_ints) as being a function taking an uint64_t and returning a string, so you do not have to manually provide type hints to the compiler.

If two functions whose "connecting types" do not match are passed in, an unambiguous error message describing the issue will be generated. FunctionalPlus uses compile time assertions to avoid the confusingly long error messages compilers generate when faced with type errors in function templates.

Changing the way you program from "writing your own loops and nested ifs" to "composing and using small functions" will result in more errors at compile time but will pay out by having fewer errors at runtime. Also, more precise compile-time errors will reduce the time spent debugging.

Tutorial

The article "Functional programming in C++ with the FunctionalPlus library; today: HackerRank challenge Gemstones" provides a smooth introduction into the library by showing how one could develop an elegant solution to a problem using the FunctionalPlus approach.

Also on Udemy there is a course "Functional Programming using C++" that makes heavy use of FunctionalPlus to explain general functional concepts.

Forward application and composition

The "Gemstones" tutorial above explains how one can apply functional thinking to arrive at the solution below for the following problem:

Find the number of characters present in every line of an input text.

std::string gemstone_count(const std::string& input)
{
    using namespace fplus;

    typedef std::set<std::string::value_type> characters;

    const auto lines = split_lines(false, input); // false = no empty lines

    const auto sets = transform(
        convert_container<characters, std::string>,
        lines);

    // Build the intersection of all given character sets (one per line).
    const auto gem_elements = fold_left_1(
        set_intersection<characters>, sets);

    return show(size_of_cont(gem_elements));
}

By using the functionality from namespace fwd, you can get along without temporary variables, and make it clear that the whole process is simply pushing the input through a chain of functions, similar to the pipe concept in the Unix command line.

std::string gemstone_count_fwd_apply(const std::string& input)
{
    using namespace fplus;
    typedef std::set<std::string::value_type> characters;
    return fwd::apply(
        input
        , fwd::split_lines(false)
        , fwd::transform(convert_container<characters, std::string>)
        , fwd::fold_left_1(set_intersection<characters>)
        , fwd::size_of_cont()
        , fwd::show()
    );
}

In fplus::fwd:: you find many fplus:: functions again, but in a partially curried version, i.e. fplus::foo : (a, b, c) -> d has its counterpart with fplus::foo : (a, b) -> (c -> d). This makes the style above possible.

Alternatively to the forward application version, you can also write point-free and define your function by composition:

using namespace fplus;
typedef std::set<std::string::value_type> characters;

const auto gemstone_count_fwd_compose = fwd::compose(
    fwd::split_lines(false),
    fwd::transform(convert_container<characters, std::string>),
    fwd::fold_left_1(set_intersection<characters>),
    fwd::size_of_cont(),
    fwd::show()
);

By the way, in case you need the parameters of a binary function in reverse order, namespace fplus::fwd::flip also exists. fplus::bar : (a, b) -> c does not only have its analog in fplus::fwd::bar : a -> b -> c but also in fplus::fwd::flip::bar : b -> a -> c.

Finding the functions you need

If you are looking for a specific FunctionalPlus function you do not know the name of yet, you can of course use the auto-complete feature of your IDE to browse the content of the namespace fplus. But the recommended way is to use the FunctionalPlus API search website. You can quickly search by keywords or function type signatures with it. If you prefer, you can also browse the source code using Sourcegraph.

Performance

The basic functions are fast, thanks to C++'s concept of abstraction without overhead. Here are some measurements from the first example, taken on a standard desktop PC, compiled with GCC and the O3 flag.

5000 random numbers, keep odd ones, 20000 consecutive runs accumulated
----------------------------------------------------------------------

| Hand-written for loop | std::copy_if | fplus::keep_if |
|-----------------------|--------------|----------------|
|               0.632 s |      0.641 s |        0.627 s |

So the compiler seems to do a very good job optimizing and inlining everything to basically equal machine code performance-wise.

The more complex functions though sometimes could be written in a more optimized way. If you use FunctionalPlus in a performance-critical scenario and profiling shows you need a faster version of a function please let me know or even help improving FunctionalPlus.

FunctionalPlus internally often can operate in-place if a given container is an r-value (e.g. in chained calls) and thus avoids many unnecessary allocations and copies. But this is not the case in all situations. However, thanks to working with a multi-paradigm language one easily can combine manually optimized imperative code with fplus functions. Luckily experience (aka. profiling) shows that in most cases the vast majority of code in an application is not relevant for overall performance and memory consumption. So initially focusing on developer productivity and readability of code is a good idea.

Comparison with range-v3

FunctionalPlus and range-v3 (basis for ranges in C++-20) do have things in common, as the following code snippet shows.

const auto times_3 = [](int i){return 3 * i;};
const auto is_odd_int = [](int i){return i % 2 != 0;};
const auto as_string_length = [](int i){return std::to_string(i).size();};

// FunctionalPlus
using namespace fplus;
const auto result_fplus = fwd::apply(
    numbers(0, 15000000)
    , fwd::transform(times_3)
    , fwd::drop_if(is_odd_int)
    , fwd::transform(as_string_length)
    , fwd::sum());

// range-v3
const auto result_range_v3 =
    accumulate(
        views::ints(0, ranges::unreachable)
        | views::take(15000000)
        | views::transform(times_3)
        | views::remove_if(is_odd_int)
        | views::transform(as_string_length), 0);

There are some differences though. Range-v3 ranges are lazy, which means no intermediate memory is allocated during the single steps of a processing chain like the above. When using FunctionalPlus on the other hand you work with normal STL containers. Also implementing a new function is simpler compared to writing a new range adaptor. Additionally FunctionalPlus provides much more functions out of the box and has the API search website. So the choice between the two libraries depends on your preferences and the project's needs.

Requirements and Installation

A C++14-compatible compiler is needed. Compilers from these versions on are fine:

  • GCC ( >= 4.9 )
  • Clang ( >= 3.7 with libc++ >= 3.7 )
  • Visual Studio ( >= 2015 )
  • XCode ( >= 9 )

Guides for different ways to install FunctionalPlus can be found in INSTALL.md.

Disclaimer

The functionality in this library initially grew due to my personal need for it while using C++ regularly. I try my best to make it error-free and as comfortable to use as I can. The API still might change in the future. If you have any suggestions, find errors, miss some functions, or want to give general feedback/criticism, I'd love to hear from you. Of course, contributions are also very welcome.

License

Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)

functionalplus's People

Contributors

adela0814 avatar am11 avatar artalus avatar danimtb avatar dflamand avatar dobiasd avatar ferdymercury avatar friendlyanon avatar headupinclouds avatar henryiii avatar jchia avatar kkweon avatar kvaz1r avatar luso58 avatar manwithabike avatar mattpaletta avatar offa avatar paivagustavo avatar pcres91 avatar pfultz2 avatar pthom avatar ruanformigoni avatar scinart avatar seekingmeaning avatar theodelrieu avatar thiagobbt avatar tom91136 avatar unixnme avatar xtofl avatar ztdwu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

functionalplus's Issues

The function name 'unique' is a little misleading

I'm glad to just find this great library! After some exploration, I found that the function name unique is a little misleading. Here unique([1,2,2,3,2]) == [1,2,3,2], though I would expect unique([1,2,2,3,2]) == [1,2,3] as the behavior of numpy.unique in python.

Despite this name is followed from std::unique, which remove consecutive duplicates in range, I personally think using a slight different name (e.g. unique_consecutime) here would be beneficial for avoiding misuse. However it's just personal thought. Please feel free to take it or leave it :)

Clean-up: static_asserts

There are a lot of duplicate function_traits_asserts specialization.

We need to group them.

Also, since C++14 constexpr functions can return void, which would remove the cast to void on each call to detail::trigger_static_asserts

Infinite ranges - generators

Very nice library!
I find some similarities with ranges v3 from Eric Niebler.

Strong points toward yours are being more portable and faster to compile.

Anyway i'd like some more features that are missing, such as infinite ranges and generators.
For example i would really like to do:

std::vector<std::string> vec = {"first", "second", "third"};
for(auto & el : fplus::zip(vec, fplus::generate_range(1))
    std::cout << el.first << " " << el. second << '\n';

// outputs:
// first 1
// second 2
// third 3

using std::array

The following code does not compile.

#include <fplus/fplus.hpp>
int main()
{
    std::array<int, 3> xs = {0,1,2};
    fplus::transform(fplus::square<int>, xs);
}

fplus::transform (and some other functions) could deduce the length of the returned sequence at compile time and thus support std::array in theory.

I'm thinking about adding this functionality, but I'm still in the process of gathering ideas around this topic. Any thoughts? :)

std

you use many symbols like "std ::" , adding one line that using namespace std will be ok!

unknown type name 'uint'; did you mean 'int'? using clang++

Problem

Thanks for the awesome library first of all.
During compilation, I get the following error messages.

Please let me know if you need more information.

Thank you.

First Error

/usr/local/include/fplus/composition.hpp:385:29: error: unknown type name 'uint'; did you mean 'int'?

Second Error

/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/iterator:668:78: error: implicit instantiation of
      undefined template 'std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >'
    _LIBCPP_INLINE_VISIBILITY back_insert_iterator& operator=(const typename _Container::value_type& __value_)

Third Error (same as second)

/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/iterator:671:72: error: implicit instantiation of
      undefined template 'std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >'
    _LIBCPP_INLINE_VISIBILITY back_insert_iterator& operator=(typename _Container::value_type&& __value_)

What I did

  • Using cmake
git clone https://github.com/Dobiasd/FunctionalPlus
cd FunctionalPlus
mkdir build
cd build
cmake ..
sudo make install
  • Save the tutorial code as main.cpp
#include <fplus/fplus.hpp>
#include <iostream>

int main() {
  std::list<std::string> things = {"same old", "same old"};
  if (fplus::all_the_same(things))
    std::cout << "All things being equal." << std::endl;
}
  • Compile
clang++ -std=c++14 main.cpp
  • Results:
In file included from main.cpp:1:
In file included from /usr/local/include/fplus/fplus.hpp:10:
/usr/local/include/fplus/composition.hpp:385:29: error: unknown type name 'uint'; did you mean 'int'?
        return [f, storage](uint x)
                            ^
In file included from main.cpp:1:
In file included from /usr/local/include/fplus/fplus.hpp:9:
In file included from /usr/local/include/fplus/compare.hpp:9:
In file included from /usr/local/include/fplus/function_traits.hpp:25:
In file included from /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/functional:477:
In file included from /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:606:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/iterator:668:78: error: implicit instantiation of undefined template 'std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >'
    _LIBCPP_INLINE_VISIBILITY back_insert_iterator& operator=(const typename _Container::value_type& __value_)
                                                                             ^
/usr/local/include/fplus/container_common.hpp:111:16: note: in instantiation of template class 'std::__1::back_insert_iterator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >' requested here
        return std::back_inserter(ys);
               ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/iosfwd:193:33: note: template is declared here
    class _LIBCPP_TYPE_VIS_ONLY basic_string;
                                ^
In file included from main.cpp:1:
In file included from /usr/local/include/fplus/fplus.hpp:9:
In file included from /usr/local/include/fplus/compare.hpp:9:
In file included from /usr/local/include/fplus/function_traits.hpp:25:
In file included from /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/functional:477:
In file included from /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:606:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/iterator:671:72: error: implicit instantiation of undefined template 'std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >'
    _LIBCPP_INLINE_VISIBILITY back_insert_iterator& operator=(typename _Container::value_type&& __value_)
                                                                       ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/iosfwd:193:33: note: template is declared here
    class _LIBCPP_TYPE_VIS_ONLY basic_string;
                                ^
3 errors generated.

My setup

  • OSX
  • clang(++)
clang++ --version
Apple LLVM version 8.1.0 (clang-802.0.42)
Target: x86_64-apple-darwin16.7.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

error: 'fwd' has not been declared (gcc version 4.9.2)

When I compile followinf code from usage-examples:

#include <fplus/fplus.hpp>
#include <iostream>

int main()
{
using namespace fplus;
const auto result_fplus = fwd::apply(
    numbers(0, 15000000)
    , fwd::transform(times_3)
    , fwd::drop_if(is_odd)
    , fwd::transform(as_string_length)
    , fwd::sum());
}

get error:

error: 'fwd' has not been declared
const auto result_fplus = fwd::apply(
...

is it possible rewrite it without updated of the compiler?

Adding variadic math functions like min, max etc.

I have seen your math functions like min_2, min_3 etc. and I wondered why not use variadic templates to remove duplicated code?

Inspired by http://articles.emptycrate.com/2016/05/14/folds_in_cpp11_ish.html I have made some benchmarks and it came out that variadic versions are faster on -g and on pair with optimizations with your versions.

#include <vector>
#include <functional>
#include <utility>
#include <type_traits>
#include <iostream>
#include <chrono>

template <typename TimeT = std::chrono::milliseconds>
struct measure
{
  template <typename F, typename... Args>
  static typename TimeT::rep execution(F func, Args&&... args)
  {
    auto start = std::chrono::system_clock::now();

    // Now call the function with all the parameters you need.
    func(std::forward<Args>(args)...);

    auto duration = std::chrono::duration_cast<TimeT>(std::chrono::system_clock::now() - start);

    return duration.count();
  }
};

template <typename U, typename... V>
auto min(const U& u, const V&... v)
{
  using rettype = typename std::common_type_t<U, V...>;
  rettype result = static_cast<rettype>(u);
  (void)std::initializer_list<bool>{((v < result) ? (result = static_cast<rettype>(v), false) : false)...};
  return result;
}

// API search type: min_2 : (a, a) -> a
// Minimum of two values.
template <typename X>
const X& min_2(const X& a, const X& b)
{
  return std::min(a, b);
}

// API search type: min_3 : (a, a, a) -> a
// Minimum of three values.
template <typename X>
const X& min_3(const X& a, const X& b, const X& c)
{
  return min_2(min_2(a, b), c);
}

// API search type: min_4 : (a, a, a, a) -> a
// Minimum of four values.
template <typename X>
const X& min_4(const X& a, const X& b, const X& c, const X& d)
{
  return min_2(min_3(a, b, c), d);
}

// API search type: min_5 : (a, a, a, a, a) -> a
// Minimum of five values.
template <typename X>
const X& min_5(const X& a, const X& b, const X& c, const X& d, const X& e)
{
  return min_3(min_3(a, b, c), d, e);
}

int main()
{
  constexpr int COUNT = 50000000;
  std::vector<int> v, v1;
  v.reserve(COUNT);
  v1.reserve(COUNT);

  std::cout << "variadic version: " << '\n';
  std::cout << measure<std::chrono::milliseconds>::execution([&v]()
                                                             {
                                                               for (int i = -COUNT; i < COUNT; ++i)
                                                                 v.emplace_back(min(1, -14, 3, -1, i));

                                                             }) << "ms\n";

  std::cout << "functional plus version: " << '\n';
  std::cout << measure<std::chrono::milliseconds>::execution([&v1]()
                                                             {
                                                               for (int i = -COUNT; i < COUNT; ++i)
                                                                 v1.emplace_back(min_5(1, -14, 3, -1, i));

                                                             }) << "ms\n";
}

It both depends on data and optimizations but I have received the following results (not the measure of all times but hey... :) ):

g++ -g -Wall -Wpedantic -Wextra --std=c++14 main.cpp && ./a.out                                                                                                       
variadic version: 
2240ms
functional plus version: 
4335ms

g++ -O1 -Wall -Wpedantic -Wextra --std=c++14 main.cpp && ./a.out
variadic version: 
333ms
functional plus version: 
307ms

g++ -O2 -Wall -Wpedantic -Wextra --std=c++14 main.cpp && ./a.out
variadic version: 
326ms
functional plus version: 
347ms

g++ -O3 -Wall -Wpedantic -Wextra --std=c++14 main.cpp && ./a.out
311ms
functional plus version: 
321ms

The variadic version is only worse on O1 no idea why...

If anyone is happy with this type of approach I will be more than happy to submit a pull request for some of the functions in numeric which can be easily handled with variadic templates/initializer_list.

using libc++ on travis

To consolidate the problem I open this new issue.

Situation: We would like to use recent versions of libc++ on Travis (Ubuntu Trusty).
The default version provided with sudo apt install libc++-dev is svn199600 (_LIBCPP_VERSION is defined as 1101).

My attempts to learn about how to do it on SO (1, 2) were not successful yet.

enable clang in travis CI

Currently our travis ci configuration uses 4 different g++ versions for the tests. I think it would be cool if it could use at least one version of clang additionally.
@offa: Is this something you could have a look at?

could not deduce template argument for 'ContainerOut'

Hi,

I have just started playing with FunctionalPlus using Microsoft Visual Studio 2015 and have this stupid building error that I've been fighting with all day:

1> fplus_tutorial.cpp 1>.......\include\fplus\container_common.hpp(1166): error C2783: 'ContainerOut fplus::transform(F,ContainerIn &&)': could not deduce template argument for 'ContainerOut'

is that something known? I've been googling a lot and checking everything on my side, but it just look like that code can't be really handled by VS15.

Many thanks in advance for help.

BR,
Konrad

Compile error when using transparent function objects

Hello, awesome lib! I was playing a bit with it and I have the following example code that compiles and runs as expected:

#include <iostream>
#include <vector>
#include <string>

#include <fplus/fplus.hpp>

using namespace std::string_literals;

int main()
{
  auto const l1 = {"Hello"s, "World"s};
  auto const l2 = {","s, "!"s};
  auto const v = fplus::zip_with(std::plus<std::string>{}, l1, l2);
  std::cout << fplus::show_cont_with(" ", v) << std::endl;
}

However, when I use std::plus<>{}, I get a compiler error (VS2017 on Win10 x64):

include\fplus/function_traits.hpp(79): error C3556: 'std::plus::operator ()': incorrect argument to 'decltype'
include\fplus/pairs.hpp(42): note: see reference to class template instantiation 'utils::function_traits' being compiled
C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.10.25017\include\chrono(227): note: see reference to class template instantiation 'std::chrono::duration<__int64,std::nano>' being compiled
C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.10.25017\include\chrono(1002): note: see reference to class template instantiation 'std::chrono::time_pointstd::chrono::steady_clock,std::chrono::steady_clock::duration' being compiled
include\fplus/function_traits.hpp(80): error C2955: 'utils::function_traits': use of class template requires template argument list
include\fplus/function_traits.hpp(79): note: see declaration of 'utils::function_traits'
include\fplus/pairs.hpp(42): error C2039: 'arity': is not a member of 'utils::function_traits'
include\fplus/pairs.hpp(42): note: see declaration of 'utils::function_traits'
include\fplus/pairs.hpp(42): error C2065: 'arity': undeclared identifier
include\fplus/pairs.hpp(45): error C2903: 'arg': symbol is neither a class template nor a function template
include\fplus/pairs.hpp(45): error C2039: 'type': is not a member of 'utils::function_traits'
include\fplus/pairs.hpp(42): note: see declaration of 'utils::function_traits'
include\fplus/pairs.hpp(45): warning C4091: 'typedef ': ignored on left of 'int' when no variable is declared
include\fplus/pairs.hpp(45): error C2144: syntax error: 'unknown-type' should be preceded by ';'
include\fplus/pairs.hpp(46): error C2903: 'arg': symbol is neither a class template nor a function template
include\fplus/pairs.hpp(46): error C2039: 'type': is not a member of 'utils::function_traits'
include\fplus/pairs.hpp(42): note: see declaration of 'utils::function_traits'
include\fplus/pairs.hpp(46): warning C4091: 'typedef ': ignored on left of 'int' when no variable is declared
include\fplus/pairs.hpp(46): error C2144: syntax error: 'unknown-type' should be preceded by ';'
include\fplus/pairs.hpp(49): error C2923: 'std::is_convertible': 'FIn0' is not a valid template type argument for parameter '_To'
include\fplus/pairs.hpp(45): note: see declaration of 'FIn0'
include\fplus/pairs.hpp(49): error C2955: 'std::is_convertible': use of class template requires template argument list
C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.10.25017\include\type_traits(416): note: see declaration of 'std::is_convertible'
include\fplus/pairs.hpp(50): error C2057: expected constant expression
include\fplus/pairs.hpp(51): error C2923: 'std::is_convertible': 'FIn1' is not a valid template type argument for parameter '_To'
include\fplus/pairs.hpp(46): note: see declaration of 'FIn1'
include\fplus/pairs.hpp(51): error C2955: 'std::is_convertible': use of class template requires template argument list
C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.10.25017\include\type_traits(416): note: see declaration of 'std::is_convertible'
include\fplus/pairs.hpp(52): error C2057: expected constant expression

I never really used transparent function objects, so it might very well be a silly mistake.

Changing the header files does not trigger a rebuild of the unittests

Since #112 I encounter a new effect when changing the library and running the unittests.

My usual workflow looks like this:

initially:

rm -r build
mkdir build
cd build
cmake -DUNITTEST=ON ..
make unittest

later:

# change something in one of the header files
make unittest

With the old versions of CMakeLists.txt and test/CMakeLists.txt it still works, but with the new ones the second make unittest does not rebuild the tests. It just ignores the changes in the header files. I have to run sudo make install to rebuild the tests.

@offa and @theodelrieu Is this intended and my workflow is just strange? Or do you think it would be possible and helpful to change the cmake configuration to support this again?

transform_pair not working with std::function

The following code does not compile any more. I guess it has something to do with PR #87.

const std::function<int(int)> double_int = [](int x) -> int
{
    return 2 * x;
};
typedef std::pair<int, int> IntPair;
fplus::transform_pair(double_int, double_int, IntPair({2,3}));

@theodelrieu, could you please have a look at it?

Adding Unit Tests?

What about adding unit tests to the project?

I have written some example tests, see here: https://github.com/offa/FunctionalPlus/tree/ut_gtest (test directory)

The tests are off per default at the moment – so no dependencies are needed. Only Building / running the unit tests requires gtest. To do so:

cd build
cmake -DUNITTEST=ON ..
make unittest

You can check the CI log's for the branch to see how it works.

Btw. I stumbled over #20 by writing an UT for it 👍

util::function_traits fails with generic lambda on MSVC

This works out of the box - great!

auto cpp11_odd = [](int i) { return i % 2 != 0; };
fplus::keep_if(cpp11_odd,
    generate_range<std::vector<int>>(10, 1000));

This fails with C3556 on VS2015:

auto cpp14_odd = [](auto i) { return i % 2 != 0; };
fplus::keep_if(cpp14_odd,
    generate_range<std::vector<int>>(10, 1000));

Output-Build.txt

If that would work out of the box... great!

drop_if_with_idx() – what's wrong here?

Here's an example where keep_if_with_idx() is Ok, but drop_if_with_idx() fails to compile:

Predicate

static bool accept_with_index(size_t index, int value)
{
    return ( index % 2 == 0 ) && ( value >= 10 );
}

keep_if_with_idx() – Works

const std::vector<int> v = { 1, 20, 30, 4, 50, 60, 7 };
auto result = fplus::keep_if_with_idx(accept_with_index, v);
// Result: { 30, 50 }

drop_if_with_idx() – Fails to compile

const std::vector<int> v = { 1, 20, 30, 4, 50, 60, 7 };
auto result = fplus::drop_if_with_idx(accept_with_index, v);
// Expected Result: { 1, 20, 4, 60, 7 }  --- Compilation fails

Compilation fails due a failed static assertions ("Wrong arity").


Is there a mistake in the example / usage of drop_if_with_idx()?

Function adjacent_pairs crashes in MSVC2017 Debug mode

Hi =) adjacent_pairs advances iterator it2 beyond xs.end(), which crashes in debug mode on MSVC 2017 (V 15.2).

#include "fplus/fplus.hpp"
int main() {
	std::vector<int> vec = {1, 2, 4, 5 };
	auto x = fplus::adjacent_pairs(vec);
}

Proposal for a fix: PR

fplus::transform: taking advantage of move-semantics

With closing issue #73 we introduced the possibility of container re-usage for many functions like keep_if, get_segment and sort.

In the general case the output container of fplus::transform does not have the same type as the input container, but in case the given function is of type a -> a this can happen. For these cases I would like the function to reuse the input container if it is an r-value.

But currently I am banging my head against the wall trying to implement it, i.e. the unit tests won't compile. Would anybody of you guys like to help me out here?

Clean-up: default template arguments

There's a lot of defaulted template arguments in the library, they are primarily used to deduce function return types.

The problem is that one can accidentally replace them, and they make the search API less clear.

Most can be removed thanks to C++14's return type deduction.
However, it seems that a few are designed to be replaced? If so, making a list would greatly help to remove the unneeded ones.

std::initializer_list

It seems that at least some functions don't work with initializer_list. Is it possible to make make them work? For example, this doesn't compile:

void foo(initializer_list<int> const& x) {
    fplus::transform([](int i){ return i + 1;}, x);
}

[feature request] Better Description & Expose the API search link, "Foogle"

Overview

The Awesome repo needs an awesome description.
So, I have two feature requests:

  1. The current description is too implicative.
  2. Add the Foogle to the description.

After all, it's just a suggestion, so feel free to close it if you think it's not necessary.

1. Too implicative description

By Description, I mean
image

Why?

  • Sure it does help to write in concise and readable C++ code, but it does not truly show what FunctionlPlus is trying to offer. Functional Programming Library for C++
  • When I search "Functional Programming C++," it doesn't show the FunctionalPlus.
    In order to search FunctionalPlus, you have to search "Concise C++ Code".
    image

Possible suggestion

  • Change to Functional Programming Library for C++ or something similar

2. Expose Foogle

By Foogle I mean the API search website http://www.editgym.com/fplus-api-search/
It is truly awesome and super helpful, but it's currently hidden deeply in the README.

Why?

In order to go to the Foogle from this GitHub,

you have to go through 3 steps.

  1. Scroll down until "Finding the functions you need" in the toc
  2. Click "Finding the functions you need"
  3. Click "FunctionalPlus API search website."

Possible suggestions

  • if you provide the link in the description, it's one click to the Foogle.
    image

  • If you don't like it's in the description, I still think it should be located somewhere in the top of README.md, so it's easily accessible.

Room for performance improvement in `split_by`

The performance of split_by can be improved by using a more efficient tokenizing algorithm as written here (not by me) http://tcbrindle.github.io/posts/a-quicker-study-on-tokenising. I've included an inlined version of the algorithm in the test program below. Here are the execution times of splitting a short string 5,000,000 times, compiled with -O3 on clang 3.7 on x86_64 Linux 4.3:

Time taken: 19.8554 (fplus::split_words)
Time taken: 2.02921 (improved version)
#include <algorithm>
#include <chrono>
#include <iostream>
#include <vector>
#include "fplus.h"

// Slightly modified code based on http://tcbrindle.github.io/posts/a-quicker-study-on-tokenising/
template <typename Str, typename Container = std::vector<std::string>>
Container tokenize(const Str &str, const char delim = ' ') {
    Container result;
    auto inserter = std::inserter(result, std::begin(result));

    auto token_start = std::begin(str);
    while (token_start != std::end(str) && *token_start) {
        const auto token_end = std::find(token_start, std::end(str), delim);
        if (token_start != token_end) {
            *inserter = { token_start, token_end };
        }
        if (token_end == std::end(str)) break;
        token_start = std::next(token_end);
    }
    return result;
}


int main() {
    using namespace std::chrono;

    auto words = std::string{ "a bb ccc dddd eeeee ffffff ggggggg hhhhhhhhh" };

    const auto start_time = high_resolution_clock::now();
    for (auto _ = 0; _ < 5000000; _++) {
        // auto tokens = fplus::split_words(words);
        auto tokens = tokenize(words);
    }
    const auto elapsed = high_resolution_clock::now() - start_time;
    std::cout << "Time taken: " << duration_cast<duration<double>>(elapsed).count() << std::endl;
}

constructor_as_function and std::initializer_list

Hi, I think there is a problem with the implementation of constructor_as_function.

return T{args...};

IIRC, make_unique uses parenthesis instead of curly braces, I believe we should do the same.

Here is the current behavior:

struct Test {
  Test(int, int){} // 1
  Test(std::initializer_list<int>){} // 2
};

fplus::constructor_as_function<Test>(4, 2); // will call constructor 2

[question] Generated files when building

Hi,

I have seen that in order to use this library you have to generate some files with CMake.
In my case they were two files: curry_instances.autogenerated_defines and fwd_instances.autogenerated_defines.

I would like to know if those generated files may be different depending on the operating system, compiler used or not.

Thank you

Leveraging C++ 14 features for forward function application (unix pipe style). What is your opinion on this?

Let's say we have the following three functions

const auto times_3 = [](int i){return 3 * i;};
const auto is_odd = [](int i){return i % 2 == 0;};
const auto as_string_length = [](int i){return std::to_string(i).size();};

and want to transform/filter a sequence of numbers with them and sum up the result.

Up to now, we would have to write the following:

const auto result_old_style =
    sum(
    transform(as_string_length,
    drop_if(is_odd,
    transform(times_3,
    numbers(0, 15000000)))));

I do not like the fact that we have to read backwards to perceive the flow of data. I think something in the style of unix pipes would be nicer.

With some additional code in the library we could do the following thing:

const auto result_new_style = fwd::apply(
    numbers(0, 15000000)
    , fwd::transform(times_3)
    , fwd::drop_if(is_odd)
    , fwd::transform(as_string_length)
    , fwd::sum());

Furthermore it would solve the problem of the almost unusable-ugly function composition, going from this:

const auto function_chain_old_style = compose(
    bind_1st_of_2(transform<decltype(times_3), std::vector<int>>, times_3),
    bind_1st_of_2(drop_if<decltype(is_odd), std::vector<int>>, is_odd),
    bind_1st_of_2(transform<decltype(as_string_length), std::vector<int>>, as_string_length),
    sum<std::vector<std::size_t>>);

to that:

const auto function_chain_new_style = fwd::compose(
    fwd::transform(times_3),
    fwd::drop_if(is_odd),
    fwd::transform(as_string_length),
    fwd::sum());

Transforming over the inner containers in nested ones

typedef std::vector<int> ints;
std::vector<ints> nested_ints;

would thus also become simpler, going from:

const auto nested_transformed_old_style = transform(
    bind_1st_of_2(transform<decltype(times_3), ints>, times_3),
    nested_ints);

to

const auto nested_transformed_new_style = fwd::apply(
    nested_ints
    , fwd::transform(fwd::transform(times_3)));

It would increase the compiler requirements from C++11 to C++14. But this functionality could be provided in a separate header, so one could still use the old style when only C++11 is available.
Here is the full code of the example above. (The code up to line 104 would live in a file perhaps called fwd.hpp.)

What do you think about this idea?

Clean-up: remove result_of occurences

result_of is deprecated in C++17, and has a lot of caveats.

We should use invoke_result_t instead. Note that invoke_result_t returns a T&& (as std::declval).

It is important to be careful when storing it into a template type:

using Result = detail::invoke_result_t<F>;
// std::vector<T&&> will fail to compile
// using ContainerOut = std::vector<Result>;
using ContainerOut = std::vector<std::decay_t<Result>>;

'recursive' #includes should not require includes/fplus on the include path

E.g. composition.h contains an include

#include "function_traits.h"

Which means that, when included from anywhere, the client project needs both fplus_installation/includes and fplus_installation/includes/fplus on it's include path.

This makes adoption harder, and makes collisions with other libraries more probable.

Public headers should assume as little as possible from client code: they should probably include their neighbour headers as if specified by client code:

#include "fplus/function_traits.h"

fplus::map_union fails to compile for std::unordered_map

Example code

#include "fplus/fplus.hpp"
#include <unordered_map>

int main(void) {
   std::unordered_map<int, int> foo = fplus::map_union(std::unordered_map<int, int>(), std::unordered_map<int, int>());
   return 0;
}

Observed behavior

Compile error:

error: could not convert ‘fplus::map_union_with(F, const MapIn&, const MapIn&) [with F = fplus::map_union(const MapType&, const MapType&) [with MapType = std::unordered_map<int, int>]::<lambda(const Value&, const Value&)>; MapIn = std::unordered_map<int, int>]((* & dict1), (* & dict2))’ from ‘std::map<int, int, std::less<int>, std::allocator<std::pair<const int, int> > >’ to ‘std::unordered_map<int, int>’
     return map_union_with(get_first, dict1, dict2);

Expected behavior

Code compiles.

Cause?

I'm not sure why this happens, but it seems to have to do with the SameMapTypeNewTypes template

template<class Cont, class NewKey, class NewVal> struct SameMapTypeNewTypes : public std::false_type {};
as used in
using MapOut = typename internal::SameMapTypeNewTypes<MapIn, Key, OutVal>::type;
.

System

I'm using Arch Linux with GCC 8.1.0 and fplus 0.2.1 (the latest release from conan).

I'm happy to help debug further! If I figure out what causes this, I'll submit a PR, but I'm not sure what it is yet...

improve documentation

Some functions (like fold_left) do already have a short textual description of what they do.
For some functions this perhaps is not really needed, since the type signature or the example are sufficient.
But some functions could need a description.
enumerate, generate and is_sorted_on are just some that come to my mind right now.

Is anybody interested in browsing trough parts of the code and adding documentation where sensible? I think this would be very helpful, especially for helping users new to the library.

errors when using conan

i wanted to try frugally-deep through the conan package manager,

however i'm not able to compile, getting a lot of errors like:

~.conan/data/functionalplus/v0.2-p1/dobiasd/stable/package/5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9/include/fplus/internal/meta.hpp:28:41: error: no template named 'remove_reference_t' in namespace 'std'; did you mean 'remove_reference'?
using uncvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
                                   ~~~~~^~~~~~~~~~~~~~~~~~
                                        remove_reference

not sure if this is a bug in FunctionalPlus or i'm doing something wrong.

conanfile.txt:

[requires]
frugally-deep/v0.3.3-p0@dobiasd/stable


[generators]
cmake

Clean-up: use invoke everywhere

In order to support every Callable object, we should use invoke instead of:

// will not work with pointers/references to member functions/data members
return f(x, y);

Clean-up: merge namespaces detail && internal

Renaming detail to internal is the first step. There are a few internal methods that are superseded by those in detail.

There's also some static_assert that do not use the detail::trigger_static_assert facility.

scan_right_1 API doc

Thank you for creating FunctionalPlus!
I was interested in it, and reading code and made memo/summary for my use, then found small error in API doc for scan_right_1
Probably, result [1,3,6] is remain of copy&paste from scan_left_1, isn't it?
I get == [6,5,3] as expected.

quote container_common.hpp line:522

// API search type: scan_right_1 : (((a, a) -> a), [a]) -> [a]
// scan_right_1((+), [1, 2, 3]) == [1, 3, 6]
// Takes the last two items of the list and applies the function,
// then it takes the third item from the end and the result, and so on.
// It returns the list of inntermediate and final results.
template <typename F, typename ContainerIn,
typename Acc = typename ContainerIn::value_type,
typename ContainerOut = typename same_cont_new_t<ContainerIn, Acc>::type>
ContainerOut scan_right_1(F f, const ContainerIn& xs)
{
return reverse(scan_left_1(flip(f), reverse(xs)));

}

Is it compilable for avr?

Have you tried to compile this code for Arduino like hardware?
I'm failing on my first attempt . Looks like avr stl library is missing something that you need.
I'm a scala developer, and every time I write for(int i=0..... something small is dying in me.

Use doxygen

The API-search is neat, but not very useful, because a new user is in a situation "i don't know what I'm looking for". Instead, the user must have a facility to explore the library. I'd recommend annotating public API with doxy-comments and generate documentation from that.

Taking advantage of move-semantics.

e.g. let's look at keep_if

template <typename Pred, typename Container>
Container keep_if(Pred pred, const Container& xs)
{
    internal::check_unary_predicate_for_container<Pred, Container>();
    Container result;
    auto it = internal::get_back_inserter<Container>(result);
    std::copy_if(std::begin(xs), std::end(xs), it, pred);
    return result;
}

It looks to me it would be better implemented as

template <typename Pred, typename Container>
Container keep_if(Pred pred, Container xs)
{
    // erase-remove elements from xs, per predicate
    return xs;
}

This way if the container is passed as rvalue, as would most likely be the case, then we have no copy-overhead: xs is passed by-move, edited in-place, and returned by-move.

This is not just applicable to keep_if, but the family of functions where input and output types are the same, and the input is likely to be passed as rvalue, and the type is potentially heavy to copy.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.