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()
takesf
andg
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 thatF
andG
be unary. I guess making something likecompose(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);
}
1 Answer 1
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.
Explore related questions
See similar questions with these tags.