Función de composición de función similar a Haskell
Al igual que esta pregunta , estoy intentando escribir en C ++ algo que pueda parecerse a la (.)función de Haskell tanto como sea posible, que es básicamente el segundo ejercicio del Capítulo 1 de la Teoría de categorías para programadores de Bartosz Milewski .
Esta pregunta similar sobre la escritura de la función de identidad también sirvió como referencia.
Esta respuesta en StackOverflow me aseguró que se decltypeusa en los autoparámetros de lambdas es equivalente a decltypeusar en los parámetros de la plantilla.
Y esto es lo que se me ocurrió:
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;
Algunos pensamientos:
operator()tomafygpor referencia, y la lambda devuelta los captura por referencia; y tomé esta decisión pensando que debería hacer que el paso de las personas que llaman no apátridas sea más barato, pero no estoy seguro de haber tomado la decisión correcta . Otra referencia es esta .- Como los estoy pasando por referencia, tuve que elegir entre
const&y&&, y básicamente elegí al azar. - No me he preocupado por las variantes, ya que quería un "operador" de composición de función binaria , en línea con Haskell
(.) :: (b -> c) -> (a -> b) -> a -> c. - No puedo componer
composecon otra cosa, porque es binaria, mientras que la implementación implícitamente asume esoFyGes unaria. Supongo que hacer posible algo comocompose(compose,compose)(que está(.).(.)en Haskell) dependería de una "instalación" de aplicación parcial.
Este es un código donde intenté probarlo:
#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);
}
Respuestas
No es necesario usar un struct compose_tpara envolver operator(), simplemente puede operator()salir y cambiarle el nombre 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)));
};
}
Ahora también está mucho más cerca del código escrito en esta publicación que mencionaste , excepto que, por supuesto, estás limitado a componer solo dos funciones unarias.
Aparte de eso, si puede vivir con las restricciones que tiene, no veo nada malo en su implementación.