c++ c++11 functional-programming currying std-function

Programación funcional en C++. Implementando f(a)(b)(c)



c++11 functional-programming (9)

Aquí hay un enfoque inspirado en un patrón de estado único que usa operator() para cambiar el estado.

Editar: se intercambió la asignación innecesaria para una inicialización.

#include<iostream> class adder{ private: adder(double a)val(a){} double val = 0.0; static adder* mInstance; public: adder operator()(double a){ val += a; return *this;} static adder add(double a){ if(mInstance) delete mInstance; mInstance = new adder(a); return *mInstance;} double get(){return val;} }; adder* adder::mInstance = 0; int main(){ adder a = adder::add(1.0)(2.0)(1.0); std::cout<<a.get()<<std::endl; std::cout<<adder::add(1.0)(2.0)(3.0).get()<<std::endl; return 0; }

He estado entrando en los conceptos básicos de la programación funcional con C ++. Estoy tratando de hacer una función f(a)(b)(c) que devolverá a + b + c . Implementé con éxito la función f(a)(b) que devuelve a + b. Aquí está el código para ello:

std::function<double(double)> plus2(double a){ return[a](double b){return a + b; }; }

Simplemente no puedo entender cómo implementar la función f(a)(b)(c) que, como dije anteriormente, debería devolver a + b + c .


Aquí hay un enfoque ligeramente diferente, que devuelve una referencia a *this del operator() , por lo que no tiene ninguna copia flotando. Es una implementación muy simple de un functor que almacena estado y pliegues izquierdos recursivamente sobre sí mismo:

#include <iostream> template<typename T> class Sum { T x_{}; public: Sum& operator()(T x) { x_ += x; return *this; } operator T() const { return x_; } }; int main() { Sum<int> s; std::cout << s(1)(2)(3); }

Vive en Coliru


Esto no es f(a)(b)(c) sino curry(f)(a)(b)(c) . Ajustamos f manera que cada argumento adicional devuelva otro curry o invoque la función con entusiasmo. Esto es C ++ 17, pero se puede implementar en C ++ 11 con un montón de trabajo extra.

Tenga en cuenta que esta es una solución para cursar una función, que es la impresión que obtuve de la pregunta, y no una solución para doblar una función binaria.

template <class F> auto curry(F f) { return [f](auto... args) -> decltype(auto) { if constexpr(std::is_invocable<F&, decltype(args)...>{}) { return std::invoke(f, args...); } else { return curry([=](auto... new_args) -> decltype(std::invoke(f, args..., new_args...)) { return std::invoke(f, args..., new_args...); }); } }; }

Me he saltado el envío de referencias por brevedad. El uso de ejemplo sería:

int add(int a, int b, int c) { return a+b+c; } curry(add)(1,2,2); // 5 curry(add)(1)(2)(2); // also 5 curry(add)(1, 2)(2); // still the 5th curry(add)()()(1,2,2); // FIVE auto f = curry(add)(1,2); f(2); // i plead the 5th


La forma más simple que se me ocurre para hacer esto es definir plus3() en términos de plus2() .

std::function<double(double)> plus2(double a){ return[a](double b){return a + b; }; } auto plus3(double a) { return [a](double b){ return plus2(a + b); }; }

Esto combina las dos primeras listas de argumentos en un único arglista, que se usa para llamar a plus2() . Hacerlo nos permite reutilizar nuestro código preexistente con una mínima repetición y puede ampliarse fácilmente en el futuro; plusN() solo necesita devolver una lambda que llame a plusN-1() , que pasará la llamada a la función anterior, hasta que llegue a plus2() . Se puede usar así:

int main() { std::cout << plus2(1)(2) << '' '' << plus3(1)(2)(3) << ''/n''; } // Output: 3 6

Teniendo en cuenta que solo estamos llamando en línea, podemos convertir esto fácilmente en una plantilla de función, lo que elimina la necesidad de crear versiones para argumentos adicionales.

template<int N> auto plus(double a); template<int N> auto plus(double a) { return [a](double b){ return plus<N - 1>(a + b); }; } template<> auto plus<1>(double a) { return a; } int main() { std::cout << plus<2>(1)(2) << '' '' << plus<3>(1)(2)(3) << '' '' << plus<4>(1)(2)(3)(4) << '' '' << plus<5>(1)(2)(3)(4)(5) << ''/n''; } // Output: 3 6 10 15

Vea ambos en acción here .


Puede hacerlo haciendo que su función f devuelva un functor , es decir, un objeto que implemente operator() . Aquí hay una forma de hacerlo:

struct sum { double val; sum(double a) : val(a) {} sum operator()(double a) { return val + a; } operator double() const { return val; } }; sum f(double a) { return a; }

Ejemplo

Link

int main() { std::cout << f(1)(2)(3)(4) << std::endl; }

Versión de la plantilla

Incluso puede escribir una versión con plantilla que permita que el compilador deduzca el tipo. Pruébalo here .

template <class T> struct sum { T val; sum(T a) : val(a) {} template <class T2> auto operator()(T2 a) -> sum<decltype(val + a)> { return val + a; } operator T() const { return val; } }; template <class T> sum<T> f(T a) { return a; }

Ejemplo

En este ejemplo, T finalmente resolverá double :

std::cout << f(1)(2.5)(3.1f)(4) << std::endl;


Si está abierto a usar bibliotecas, esto es realmente fácil en Boost''s Hana :

double plus4_impl(double a, double b, double c, double d) { return a + b + c + d; } constexpr auto plus4 = boost::hana::curry<4>(plus4_impl);

Y luego usarlo es exactamente lo que deseas:

int main() { std::cout << plus4(1)(1.0)(3)(4.3f) << ''/n''; std::cout << plus4(1, 1.0)(3)(4.3f) << ''/n''; // you can also do up to 4 args at a time }


Simplemente tome su solución de 2 elementos y amplíela envolviéndola con otra lambda.

Como desea devolver una lambda que obtenga un double y devuelva una lambda de suma double , todo lo que necesita hacer es envolver su tipo de retorno actual con otra función y agregar una lambda anidada a su actual (una lambda que devuelve una lambda):

std::function<std::function<double(double)>(double)> plus3 (double a){ return [a] (double b) { return [a, b] (double c) { return a + b + c; }; }; }

  • Como @ notedаn señaló, puede omitir std::function<std::function<double(double)>(double)> y llevarse bien con auto :

    auto plus3 (double a){ return [a] (double b) { return [a, b] (double c) { return a + b + c; }; }; }

  • Puede expandir esta estructura para cada número de elementos, utilizando lambdas anidadas más profundas. Demostración de 4 elementos:

    auto plus4 (double a){ return [a] (double b) { return [a, b] (double c) { return [a, b, c] (double d) { return a + b + c + d; }; }; }; }


Todas estas respuestas parecen terriblemente complicadas.

auto f = [] (double a) { return [=] (double b) { return [=] (double c) { return a + b + c; }; }; };

hace exactamente lo que desea, y funciona en C ++ 11, a diferencia de muchas o quizás la mayoría de las otras respuestas aquí.

Tenga en cuenta que no utiliza la std::function que incurre en una penalización de rendimiento y, de hecho, es probable que pueda incorporarse en muchos casos.


Voy a jugar.

Desea hacer un pliegue al curry sobre la suma. Podríamos resolver este problema, o podríamos resolver una clase de problemas que incluyen esto.

Entonces, primero, además:

auto add = [](auto lhs, auto rhs){ return std::move(lhs)+std::move(rhs); };

Eso expresa el concepto de suma bastante bien.

Ahora, plegando:

template<class F, class T> struct folder_t { F f; T t; folder_t( F fin, T tin ): f(std::move(fin)), t(std::move(tin)) {} template<class Lhs, class Rhs> folder_t( F fin, Lhs&&lhs, Rhs&&rhs): f(std::move(fin)), t( f(std::forward<Lhs>(lhs), std::forward<Rhs>(rhs)) ) {} template<class U> folder_t<F, std::result_of_t<F&(T, U)>> operator()( U&& u )&&{ return {std::move(f), std::move(t), std::forward<U>(u)}; } template<class U> folder_t<F, std::result_of_t<F&(T const&, U)>> operator()( U&& u )const&{ return {f, t, std::forward<U>(u)}; } operator T()&&{ return std::move(t); } operator T() const&{ return t; } };

Toma un valor de semilla y una T, luego permite encadenar.

template<class F, class T> folder_t<F, T> folder( F fin, T tin ) { return {std::move(fin), std::move(tin)}; }

Ahora los conectamos.

auto adder = folder(add, 0); std::cout << adder(2)(3)(4) << "/n";

También podemos usar la folder para otras operaciones:

auto append = [](auto vec, auto element){ vec.push_back(std::move(element)); return vec; };

Utilizar:

auto appender = folder(append, std::vector<int>{}); for (int x : appender(1)(2)(3).get()) std::cout << x << "/n";

Ejemplo en vivo .

Tenemos que llamar a .get() aquí porque for(:) loops no entiende el operator T() nuestra carpeta operator T() . Podemos arreglar eso con un poco de trabajo, pero .get() es más fácil.