3
\$\begingroup\$

Much like this question I'm attempting to write in C++ something which can resemble Haskell's (.) function as much as possible, which is basically Chapter 1's second exercise from Bartosz Milewski's Category Theory for Programmers.

This similar question about writing the identity function served as a reference too.

This answer on StackOverflow reassured me that decltype used on lambdas' auto parameters is equivalent to decltype used on template parameters.

And this is what I came up with:

static constexpr struct compose_t {
    template <typename F, typename G>
    constexpr decltype(auto) operator()(F&& f, G&& g) const noexcept {
        return [&f,&g](auto&& x){
            return f(g(std::forward<decltype(x)>(x)));
        };
    }
} compose;

Some thoughts:

  • operator() takes f and g by reference, and the returned lambda captures them by reference; and I made this choice thinking it should make passing non-stateless callables cheaper, but I'm not sure I made the right choice. Another reference is this.
  • Since I'm passing them by reference, I had to choose between const& and &&, and I've basically chosen randomly.
  • I've not concerned myself with variadics, as I wanted a binary function composition "operator", in line with Haskell's (.) :: (b -> c) -> (a -> b) -> a -> c.
  • I cannot compose compose with something else, because it is binary, whereas the implementation implicitly assumes that F and G be unary. I guess making something like compose(compose,compose) (which is (.).(.) in Haskell) possible would be up to a partial application "facility".

This is a code where I tried to test it:

#include <cassert>
#include <utility>

// ... the code from above

static constexpr struct changesign_t {
    template<typename T>
    constexpr decltype(auto) operator()(T&& t) const noexcept { return -t; };
} changesign;

int square(int x) { return x*x; }
int twice(int x) { return 2*x; }

int main() {
    assert(compose(square,twice)(3) == 36);
    assert(compose([](int x){ return x*x; },[](int x){ return 2*x; })(3) == 36);
    assert(compose(changesign, compose([](auto x){ return x*x; }, [](auto x){ return 2*x; }))(3) == -36);
}
\$\endgroup\$
3
\$\begingroup\$

There's no need to use a struct compose_t to wrap operator(), you can just move operator() out of it and rename it compose():

template <typename F, typename G>
constexpr decltype(auto) compose(F&& f, G&& g) noexcept {
    return [&f,&g](auto&& x){
        return f(g(std::forward<decltype(x)>(x)));
    };
}

Now it is also much closer to the code written in this post you mentioned, except of course you are limited to composing only two unary functions.

Other than that, if you can live with the restrictions you have, I see nothing wrong with your implementation.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.