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);
}
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 conauto
: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";
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.