5

I'm a mathematician used to doing "old style" C++ programming for a long time now. I feel that some new syntactic constructions offerred by C++11 could help me achieve some better code regarding my professionnal projects. Yet as I'm no CS professionnal I must confess that I lack the knowledge to understand some examples I encounter in my self-learning process, altough I've been pretty lucky/succesful so far.

My impression is that variadic templates can be used to implement type-safe functions composition, as in this question. My concern is slightly more general since I'd like to compose functions with heterogeneous (but compatible) argument/return types. I've googled a lot and found another reference, but it seems utter "black magic" to me ;) and I won't pretend I can adapt the code in my context, although I feel I should find what I need in there.

I think the (most incomplete) code below is relatively self-explanatory as to what I'd like to achieve. In particular I believe the proper implementation will throw a compile-time error when one's trying to compose incompatible functions (Arrow here), and will need a piece of recursive template code.

template <typename Source , typename Target> class Arrow
{
  Target eval (const Source &);
};

template <typename ...Arrows> class Compositor
{
  template <typename ...Arrows>
  Compositor (Arrows... arrows)
  {
     // do/call what needs be here
  };

  auto arrow(); // gives a function performing the functionnal composition of arrows

};

// define some classes A, B and C

int main(int argc, char **argv)
{
  Arrow < A , B >  arrow1;
  Arrow < B , C >  arrow2;

  Compositor< Arrow < A , B > , Arrow < B , C > > compositor(arrow1 , arrow2);

  Arrow < A , C >  expected_result = compositor.arrow();
}

Ideally I'd like
    Compositor
to directly subclass
    Arrow < source_of_first_arrow , target_of_last_arrow>
and the method
   arrow()
be replaced by the corresponding
    eval()

but I felt the above code was more explanatory.

Any help will be greatly appreciated, even if it consists in a rough rebuke with a pointer to an existing (relatively basic) piece of example which will surely have escaped my search. Thanks!

Community
  • 1
  • 1

2 Answers2

5

If I got it correctly, you need no fancy template magic to do this composition. Here is the almost self-explanatory code:

#include <functional>
#include <string>
#include <iostream>

// it is just an std::function taking A and returning B
template <typename A, typename B>
using Arrow = std::function<B(const A&)>;

// composition operator: just create a new composed arrow
template <typename A, typename B, typename C>
Arrow<A, C> operator*(const Arrow<A, B>& arr1, const Arrow<B, C>& arr2)
{
    // arr1 and arr2 are copied into the lambda, so we won't lose track of them
    return [arr1, arr2](const A& a) { return arr2(arr1(a)); };
}

int main()
{
    Arrow<int, float> plusHalf([](int i){return i + 0.5;});
    Arrow<float, std::string> toString([](float f){return std::to_string(f);});

    auto composed = plusHalf * toString; // composed is Arrow<int, std::string>
    std::cout << composed(6) << std::endl; // 6.5

    //auto badComposed = toString * plusHalf; // compile time error
}

I mostly played with lambda functions here.

Using a single function call instead of a operator chain proved to be a more tricky problem. This time you got some templates:

#include <functional>
#include <string>
#include <iostream>

// it is just an std::function taking A and returning B
template <typename A, typename B>
using Arrow = std::function<B(const A&)>;

// A helper struct as template function can't get partial specialization
template <typename... Funcs>
struct ComposerHelper;

// Base case: a single arrow
template <typename A, typename B>
struct ComposerHelper<Arrow<A, B>>
{
    static Arrow<A, B> compose(const Arrow<A, B>& arr)
    {
        return arr;
    }
};

// Tail case: more arrows
template <typename A, typename B, typename... Tail>
struct ComposerHelper<Arrow<A, B>, Tail...>
{
    // hard to know the exact return type here. Let the compiler figure out
    static auto compose(const Arrow<A, B>& arr, const Tail&... tail)
    -> decltype(arr * ComposerHelper<Tail...>::compose(tail...))
    {
        return arr * ComposerHelper<Tail...>::compose(tail...);
    }
};

// A simple function to call our helper struct
// again, hard to write the return type
template <typename... Funcs>
auto compose(const Funcs&... funcs)
-> decltype(ComposerHelper<Funcs...>::compose(funcs...))
{
    return ComposerHelper<Funcs...>::compose(funcs...);
}

using namespace std;

int main()
{
    Arrow<int, float> plusHalf([](int i){return i + 0.5;});
    Arrow<float, string> toString([](float f){return to_string(f);});
    Arrow<string, int> firstDigit([](const string& s){return s[0]-'0';});

    auto composed = compose(plusHalf, toString, firstDigit);
    // composed is Arrow<int, int>

    std::cout << composed(61) << std::endl; // "6"

    //auto badComposed = compose(toString, plusHalf); // compile time error
}
Guilherme Bernal
  • 8,183
  • 25
  • 43
  • "I mostly played with lambda functions here" : yes, that's what scares me ;) Thank you for your (very clear) input, I'll try to adapt it to the more complex case of an arbitrary number of functions to compose (hence the variadic template tag). Shouldn't be too difficult being given the basic construction brick you provide. –  Dec 29 '13 at 21:00
  • You can compose more than two using this code. Just do `auto f = f1 * f2 * f3 * f4;`. Or are you looking to do it more like a function call? (`auto f = compose(f1, f2, f3, f4);`) – Guilherme Bernal Dec 29 '13 at 21:16
  • Yes, I meant the latter. I'd like something that can be written as compose(f1,...) [ and I can't seem to make work the inline code character style...?] –  Dec 29 '13 at 22:05
  • @LoïcTeyssier, It wasn't as trivial as I thought initially. I appended it to the answer. If the process is unclear, please ask. – Guilherme Bernal Dec 29 '13 at 22:48
  • Yes, for me the main difficulty was the recursion on the template parameters, which I wanted to get straight. Since both your solutions are of different nature, they are both instructive for me. Thanks for your answer, I'll try this out tomorrow and keep in touch. –  Dec 29 '13 at 23:08
  • I was able to adapt your code to fit my purposes. Thank you very much for your help. –  Dec 30 '13 at 18:21
  • @ Guilherme Bernal. I've posted a solution so that the return types are known without having the compiler deduce them. – prestokeys Mar 24 '15 at 22:22
2

Though the output is exactly the same, here I feel Arrow should be its own class so that its domain and range can be stored as types. Now return types are known without having the compiler deduce them.

#include <functional>
#include <iostream>
#include <string>
#include <sstream>

// Revised as a class with types domain = A and range = B.  It still acts as a std::function<B(const A&)>, through its operator()(const A&) and its data member 'arrow'.
template <typename A, typename B>
class Arrow {
    const std::function<B(const A&)> arrow;
public:
    Arrow (const std::function<B(const A&)>& a) : arrow(a) {}
    B operator()(const A& a) const {return arrow(a);}
    using domain = A;
    using range = B;
};

// The overload * for the composition of two Arrows in Arrow's new form:
template <typename A, typename B, typename C>
Arrow<A,C> operator * (const Arrow<A,B>& arrow_ab, const Arrow<B,C>& arrow_bc) {
    return Arrow<A,C>([arrow_ab, arrow_bc](const A& a)->C {return arrow_bc(arrow_ab(a));});
}

template <typename First, typename... Rest> struct LastType : LastType<Rest...> {};
template <typename T> struct Identity { using type = T; };
template <typename Last> struct LastType<Last> : Identity<Last> {};

template <typename...> struct ComposeArrows;

template <typename First, typename... Rest>
struct ComposeArrows<First, Rest...> : ComposeArrows<Rest...> {
    using last_arrow = typename LastType<Rest...>::type;
    using composed_arrow = Arrow<typename First::domain, typename last_arrow::range>;
    composed_arrow operator()(const First& first, const Rest&... rest) {
        return first * ComposeArrows<Rest...>::operator()(rest...);
    }
};

template <typename Last>
struct ComposeArrows<Last> {
    Last operator()(const Last& arrow) {return arrow;}
};

template <typename... Arrows>
typename ComposeArrows<Arrows...>::composed_arrow composeArrows (const Arrows&... arrows) {
    return ComposeArrows<Arrows...>()(arrows...);
}

// ------------ Testing ------------
template <typename T>
std::string toString (const T& t) {
    std::ostringstream os;
    os << t;
    return os.str();
}

int main() {
    Arrow<int, double> plusHalf ([](int i){return i + 0.5;});
    Arrow<double, std::string> doubleToString ([](float f){return toString(f) + " is the result";});
    Arrow<std::string, char> lastCharacter ([](const std::string& str)->int {show(str)  return str.back();});

    const Arrow<int, char> composed = composeArrows (plusHalf, doubleToString, lastCharacter);
    std::cout << composed(2) << std::endl; // "t"
    std::cout << composeArrows (plusHalf, doubleToString, lastCharacter)(2) << std::endl; // "t"
}

Here is another solution with a slightly different syntax:

#include <iostream>
#include <functional>
#include <tuple>
#include <string>

template <typename A, typename B>
struct Arrow {
    using domain = const A&;
    using range = B;
    using Function = std::function<range(domain)>;
    const Function& function;
    Arrow (const Function& f) : function(f) {}
    range operator()(domain x) const {return function(x);}
};

template <typename... Ts>
struct LastType {
    using Tuple = std::tuple<Ts...>;
    using type = typename std::tuple_element<std::tuple_size<Tuple>::value - 1, Tuple>::type;
};

template <typename Arrow1, typename Arrow2>
typename Arrow2::range compose (const typename Arrow1::domain& x, const Arrow2& arrow2, const Arrow1& arrow1) {
    return arrow2(arrow1(x));
}

template <typename Arrow, typename... Rest>
auto compose (const typename LastType<Rest...>::type::domain& x, const Arrow& arrow, const Rest&... rest) {
    return arrow(compose(x, rest...));
}

int main() {
    Arrow<std::string, int> f([](const std::string& s) {return s.length();});
    Arrow<int, double> g([](int x) {return x + 0.5;});
    Arrow<double, int> h([](double d) {return int(d+1);});
    std::cout << compose("hello", g, f) << '\n';  // 5.5
    std::cout << compose("hello", h, g, f) << '\n';  // 6
}
prestokeys
  • 4,817
  • 3
  • 20
  • 43
  • Ok, thanks for the answer. I arrived at this design some time ago already. –  Mar 25 '15 at 08:05