Composing functions together with the pipe operator
The Unix operating system's command line shell allows the standard output of one function to be piped into the another to form a filter chain. Later, this feature became part of every command line shell offered as part of most operating systems. While writing functional style code, when we combine methods through functional composition, the code becomes hard to read because of deep nesting. Now, with Modern C++ we can overload the pipe (|) operator to allow chaining several functions together, like we do commands in a Unix shell or Windows PowerShell console. That is why someone re-christened the LISP language as Lots of Irritating and Silly Parentheses. The RxCpp library uses the | operator extensively to compose functions together. The following code helps us understand how one can create pipeable functions. We will take a look at how this can be implemented in principle. The code given here is only good for expository purposes:
//---- PipeFunc2.cpp
//-------- g++ -std=c++1z PipeFunc2.cpp
#include <iostream>
using namespace std;
struct AddOne {
template<class T>
auto operator()(T x) const { return x + 1; }
};
struct SumFunction {
template<class T>
auto operator()(T x,T y) const { return x + y;} // Binary Operator
};
The preceding code creates a set of Callable classes and it will be used as part of a compositional chain of functions. Now, we need to create a mechanism to convert an arbitrary function to a closure:
//-------------- Create a Pipable Closure Function (Unary)
//-------------- Uses Variadic Templates Paramter pack
template<class F>
struct PipableClosure : F{
template<class... Xs>
PipableClosure(Xs&&... xs) : // Xs is a universal reference
F(std::forward<Xs>(xs)...) // perfect forwarding
{}
};
//---------- A helper function which converts a Function to a Closure
template<class F>
auto MakePipeClosure(F f)
{ return PipableClosure<F>(std::move(f)); }
// ------------ Declare a Closure for Binary
//------------- Functions
//
template<class F>
struct PipableClosureBinary {
template<class... Ts>
auto operator()(Ts... xs) const {
return MakePipeClosure([=](auto x) -> decltype(auto)
{ return F()(x, xs...);}); }
};
//------- Declare a pipe operator
//------- uses perfect forwarding to invoke the function
template<class T, class F> //---- Declare a pipe operator
decltype(auto) operator|(T&& x, const PipableClosure<F>& pfn)
{ return pfn(std::forward<T>(x)); }
int main() {
//-------- Declare a Unary Function Closure
const PipableClosure<AddOne> fnclosure = {};
int value = 1 | fnclosure| fnclosure;
std::cout << value << std::endl;
//--------- Decalre a Binary function closure
const PipableClosureBinary<SumFunction> sumfunction = {};
int value1 = 1 | sumfunction(2) | sumfunction(5) | fnclosure;
std::cout << value1 << std::endl;
}
Now, we can create an instance of PipableClosure with a unary function as a parameter and chain (or compose) together a series of invocations to the closure. The preceding code snippet should print three on the console. We have also created a PipableBinaryClosure instance to string together both unary and binary functions.