The examples are clearly achieving the goal of "concise". I would counter with a different style though, because I try to imagine how the code would change over time and how it would fare when given constraints on memory and runtime.
Compare:
for (int x : numbers) {
if (1 == (x % 2)) {
odds.push_back(x);
}
}
auto odds = KeepIf(IsOdd, numbers);
To me, the first case is preferable. The 2nd case uses custom functions that may not be familiar to the maintainer, and semantics are clearly hidden (e.g. does KeepIf() modify the list, return a copy, or return some kind of intelligent generator object that still depends on the lifetime of "numbers"?).
Suppose I discover that my application starts crashing with the odd number 15 and I have to work around that case very quickly. The loop version above is trivial to modify, whereas I have no idea how the 2nd version would be modified to filter out one value! And if I want to do several things with the numbers while they're being found (e.g. print them out), the loop is easy to change but the 2nd version basically has to be replaced entirely (and replacing even a concise expression is risky due to the issues above, e.g. unclear semantics).
Now compare approaches for another case:
if (FindWord("I", team)) ...
if (Contains("I", SplitWords(team))) ...
This shows a general problem with a "functional style" at times: it's placing constraints on the implementation that don't need to be there.
To me, the first style is preferable because FindWord() could easily walk a raw string and stop as soon as it finds the target word, with a chance for better-than-N average complexity. The second case would mandate: copying strings, visiting all characters in the string every time, and revisiting at least some of the characters in the form of words.
Finally, I think that the C++ standard algorithms, lambdas, and compact-"for" do allow a lot of these tricks to be avoided. Consider algorithms like std::all_of() for instance, which can be combined with lambdas to produce the same results as examples like "AllBy(And(..." but without the downsides I mention at the beginning of my comment.
I really like your remark about the performance of
if (Contains("I", SplitWords(team)))
so I added it to the README.
If you don't want the 15 in the odds, just to this:
auto goodOdds = without(15, keep_if(is_odd, numbers));
The fact, that `keep_if` does return a copy can easily obtained by looking at the function signature or the type of `odds`. Both things should not be complicated with a modern IDE.
I agree with you in general. The library is not made for performance-critical stuff.
But the "raw loops etc. are easier to understand by outsiders" seems like an argument agains using any libraries at all.
Streaming fusion exists, and can easily be implemented in C++, that's what authors of this library should have done. C++ ranges library (proposition for C++17) will make this kind of thing possible without any overhead. Realistically, STL interface for sequences is pretty bad and barely composable.
This is not a problem with "functional style", it's a problem with library design.
writing
map (+2) (map (+1) (map (+3) xs))
will be just a single pass over a list in Haskell, with 3 function applications (maybe 1 if that is optimized to (+6)).
Compare:
To me, the first case is preferable. The 2nd case uses custom functions that may not be familiar to the maintainer, and semantics are clearly hidden (e.g. does KeepIf() modify the list, return a copy, or return some kind of intelligent generator object that still depends on the lifetime of "numbers"?).Suppose I discover that my application starts crashing with the odd number 15 and I have to work around that case very quickly. The loop version above is trivial to modify, whereas I have no idea how the 2nd version would be modified to filter out one value! And if I want to do several things with the numbers while they're being found (e.g. print them out), the loop is easy to change but the 2nd version basically has to be replaced entirely (and replacing even a concise expression is risky due to the issues above, e.g. unclear semantics).
Now compare approaches for another case:
This shows a general problem with a "functional style" at times: it's placing constraints on the implementation that don't need to be there.To me, the first style is preferable because FindWord() could easily walk a raw string and stop as soon as it finds the target word, with a chance for better-than-N average complexity. The second case would mandate: copying strings, visiting all characters in the string every time, and revisiting at least some of the characters in the form of words.
Finally, I think that the C++ standard algorithms, lambdas, and compact-"for" do allow a lot of these tricks to be avoided. Consider algorithms like std::all_of() for instance, which can be combined with lambdas to produce the same results as examples like "AllBy(And(..." but without the downsides I mention at the beginning of my comment.