c++ stl functional-programming currying binders

¿Cómo se puede currar en C++?



stl functional-programming (10)

1. ¿Qué es currying?

Currying simplemente significa una transformación de una función de varios argumentos a una función de un solo argumento. Esto se ilustra más fácilmente usando un ejemplo:

Tome una función f que acepte tres argumentos:

int f(int a,std::string b,float c) { // do something with a, b, and c return 0; }

Si queremos llamar a f , tenemos que proporcionar todos sus argumentos f(1,"some string",19.7f) .

Entonces, una versión al curry de f , llamémoslo curried_f=curry(f) solo espera un único argumento, que corresponde al primer argumento de f , es decir, el argumento a . Además, f(1,"some string",19.7f) también se puede escribir usando la versión curried_f(1)("some string")(19.7f) como curried_f(1)("some string")(19.7f) . El valor de retorno de curried_f(1) por el contrario es simplemente otra función, que maneja el siguiente argumento de f . Al final, terminamos con una función o curried_f invocable que cumple la siguiente igualdad:

curried_f(first_arg)(second_arg)...(last_arg) == f(first_arg,second_arg,...,last_arg).

2. ¿Cómo se puede lograr el currying en C ++?

Lo siguiente es un poco más complicado, pero funciona muy bien para mí (usando c ++ 11) ... También permite currying de grado arbitrario como así: auto curried=curry(f)(arg1)(arg2)(arg3) y luego auto result=curried(arg4)(arg5) . Aquí va:

#include <functional> namespace _dtl { template <typename FUNCTION> struct _curry; // specialization for functions with a single argument template <typename R,typename T> struct _curry<std::function<R(T)>> { using type = std::function<R(T)>; const type result; _curry(type fun) : result(fun) {} }; // recursive specialization for functions with more arguments template <typename R,typename T,typename...Ts> struct _curry<std::function<R(T,Ts...)>> { using remaining_type = typename _curry<std::function<R(Ts...)> >::type; using type = std::function<remaining_type(T)>; const type result; _curry(std::function<R(T,Ts...)> fun) : result ( [=](const T& t) { return _curry<std::function<R(Ts...)>>( [=](const Ts&...ts){ return fun(t, ts...); } ).result; } ) {} }; } template <typename R,typename...Ts> auto curry(const std::function<R(Ts...)> fun) -> typename _dtl::_curry<std::function<R(Ts...)>>::type { return _dtl::_curry<std::function<R(Ts...)>>(fun).result; } template <typename R,typename...Ts> auto curry(R(* const fun)(Ts...)) -> typename _dtl::_curry<std::function<R(Ts...)>>::type { return _dtl::_curry<std::function<R(Ts...)>>(fun).result; } #include <iostream> void f(std::string a,std::string b,std::string c) { std::cout << a << b << c; } int main() { curry(f)("Hello ")("functional ")("world!"); return 0; }

Ver salida

De acuerdo, como comentó Samer, debería agregar algunas explicaciones sobre cómo funciona esto. La implementación real se realiza en el _dtl::_curry , mientras que las funciones de la plantilla curry son solo envoltorios de conveniencia. La implementación es recursiva sobre los argumentos del argumento de plantilla std::function FUNCTION .

Para una función con un solo argumento, el resultado es idéntico a la función original.

_curry(std::function<R(T,Ts...)> fun) : result ( [=](const T& t) { return _curry<std::function<R(Ts...)>>( [=](const Ts&...ts){ return fun(t, ts...); } ).result; } ) {}

Aquí lo difícil: para una función con más argumentos, devolvemos un lambda cuyo argumento está ligado al primer argumento de la llamada a la fun . Finalmente, el currying restante para los argumentos N-1 restantes se delega a la implementación de _curry<Ts...> con un argumento de plantilla menos.

Actualización para c ++ 14/17:

Una nueva idea para abordar el problema del currying acaba de llegar a mí ... Con la introducción de if constexpr en c + + + 17 (y con la ayuda de void_t para determinar si una función está completamente al curry), las cosas parecen obtener mucho más fácil:

template< class, class = std::void_t<> > struct needs_unapply : std::true_type { }; template< class T > struct needs_unapply<T, std::void_t<decltype(std::declval<T>()())>> : std::false_type { }; template <typename F> auto curry(F&& f) { /// Check if f() is a valid function call. If not we need /// to curry at least one argument: if constexpr (needs_unapply<decltype(f)>::value) { return [=](auto&& x) { return curry( [=](auto&&...xs) -> decltype(f(x,xs...)) { return f(x,xs...); } ); }; } else { /// If ''f()'' is a valid call, just call it, we are done. return f(); } } int main() { auto f = [](auto a, auto b, auto c, auto d) { return a * b * c * d; }; return curry(f)(1)(2)(3)(4); }

Vea el código en acción here . Con un enfoque similar, here es cómo curry funciones con un número arbitrario de argumentos.

La misma idea parece funcionar también en C ++ 14, si cambiamos el constexpr if con una selección de plantilla dependiendo de la prueba needs_unapply<decltype(f)>::value :

template <typename F> auto curry(F&& f); template <bool> struct curry_on; template <> struct curry_on<false> { template <typename F> static auto apply(F&& f) { return f(); } }; template <> struct curry_on<true> { template <typename F> static auto apply(F&& f) { return [=](auto&& x) { return curry( [=](auto&&...xs) -> decltype(f(x,xs...)) { return f(x,xs...); } ); }; } }; template <typename F> auto curry(F&& f) { return curry_on<needs_unapply<decltype(f)>::value>::template apply(f); }

¿Qué es currying?

¿Cómo se puede currar en C ++?

Por favor explique carpetas en contenedor STL?


Algunas buenas respuestas aquí. Pensé que agregaría la mía porque era divertido jugar con el concepto.

Aplicación de función parcial : el proceso de "vincular" una función con solo algunos de sus parámetros, difiriendo el resto para completarlo más tarde. El resultado es otra función con menos parámetros.

Currying : Es una forma especial de aplicación de funciones parciales donde solo puedes "enlazar" un solo argumento a la vez. El resultado es otra función con exactamente 1 parámetro menos.

El código que estoy a punto de presentar es una aplicación de función parcial a partir de la cual es posible currar, pero no es la única posibilidad. Ofrece algunos beneficios sobre las implementaciones de currying anteriores (principalmente porque es una aplicación de función parcial y no currying, heh).

  • Aplicando sobre una función vacía:

    auto sum0 = [](){return 0;}; std::cout << partial_apply(sum0)() << std::endl;

  • Aplicando múltiples argumentos a la vez:

    auto sum10 = [](int a, int b, int c, int d, int e, int f, int g, int h, int i, int j){return a+b+c+d+e+f+g+h+i+j;}; std::cout << partial_apply(sum10)(1)(1,1)(1,1,1)(1,1,1,1) << std::endl; // 10

  • soporte constexpr que permite static_assert tiempo de static_assert :

    static_assert(partial_apply(sum0)() == 0);

  • Un mensaje de error útil si accidentalmente llega demasiado lejos al proporcionar argumentos:

    auto sum1 = [](int x){ return x;}; partial_apply(sum1)(1)(1);

    error: static_assert failed "Intentando aplicar demasiados argumentos!"

Otras respuestas anteriores devuelven lambdas que unen un argumento y luego devuelven más lambdas. Este enfoque envuelve esa funcionalidad esencial en un objeto invocable. Las definiciones para el operator() permiten llamar a la lambda interna. Las plantillas variables nos permiten verificar que alguien vaya demasiado lejos, y una función de conversión implícita al tipo de resultado de la llamada a función nos permite imprimir el resultado o comparar el objeto con una primitiva.

Código:

namespace detail{ template<class F> using is_zero_callable = decltype(std::declval<F>()()); template<class F> constexpr bool is_zero_callable_v = std::experimental::is_detected_v<is_zero_callable, F>; } template<class F> struct partial_apply_t { template<class... Args> constexpr auto operator()(Args... args) { static_assert(sizeof...(args) == 0 || !is_zero_callable, "Attempting to apply too many arguments!"); auto bind_some = [=](auto... rest) -> decltype(myFun(args..., rest...)) { return myFun(args..., rest...); }; using bind_t = decltype(bind_some); return partial_apply_t<bind_t>{bind_some}; } explicit constexpr partial_apply_t(F fun) : myFun(fun){} constexpr operator auto() { if constexpr (is_zero_callable) return myFun(); else return *this; // a callable } static constexpr bool is_zero_callable = detail::is_zero_callable_v<F>; F myFun; };

Demo en vivo

Algunas notas más:

  • Elegí usar is_detected principalmente para el disfrute y la práctica; sirve lo mismo que un rasgo de tipo normal aquí.
  • Definitivamente podría haber más trabajo para apoyar el reenvío perfecto por razones de rendimiento
  • El código es C ++ 17 porque requiere soporte constexpr lambda en C ++ 17
    • Y parece que GCC 7.0.1 todavía no está allí, así que utilicé Clang 5.0.0

Algunas pruebas:

auto sum0 = [](){return 0;}; auto sum1 = [](int x){ return x;}; auto sum2 = [](int x, int y){ return x + y;}; auto sum3 = [](int x, int y, int z){ return x + y + z; }; auto sum10 = [](int a, int b, int c, int d, int e, int f, int g, int h, int i, int j){return a+b+c+d+e+f+g+h+i+j;}; std::cout << partial_apply(sum0)() << std::endl; //0 static_assert(partial_apply(sum0)() == 0, "sum0 should return 0"); std::cout << partial_apply(sum1)(1) << std::endl; // 1 std::cout << partial_apply(sum2)(1)(1) << std::endl; // 2 std::cout << partial_apply(sum3)(1)(1)(1) << std::endl; // 3 static_assert(partial_apply(sum3)(1)(1)(1) == 3, "sum3 should return 3"); std::cout << partial_apply(sum10)(1)(1,1)(1,1,1)(1,1,1,1) << std::endl; // 10 //partial_apply(sum1)(1)(1); // fails static assert auto partiallyApplied = partial_apply(sum3)(1)(1); std::function<int(int)> finish_applying = partiallyApplied; std::cout << std::boolalpha << (finish_applying(1) == 3) << std::endl; // true auto plus2 = partial_apply(sum3)(1)(1); std::cout << std::boolalpha << (plus2(1) == 3) << std::endl; // true std::cout << std::boolalpha << (plus2(3) == 5) << std::endl; // true


Currying es una forma de reducir una función que toma múltiples argumentos en una secuencia de funciones anidadas con un argumento cada uno:

full = (lambda a, b, c: (a + b + c)) print full (1, 2, 3) # print 6 # Curried style curried = (lambda a: (lambda b: (lambda c: (a + b + c)))) print curried (1)(2)(3) # print 6

Currying es agradable porque puede definir funciones que son simplemente envoltorios alrededor de otras funciones con valores predefinidos, y luego pasar las funciones simplificadas. Los enlazadores C ++ STL proporcionan una implementación de esto en C ++.


Eche un vistazo a Boost.Bind que hace que el proceso mostrado por Greg sea más versátil:

transform(a.begin(), a.end(), back_inserter(b), bind(f, _1, 5));

Esto une 5 a f segundo argumento.

Vale la pena señalar que esto no es currying (en cambio, es una aplicación parcial). Sin embargo, usar el currying de manera general es difícil en C ++ (de hecho, solo recientemente se hizo posible) y en su lugar se usa una aplicación parcial.



Implementé el currying con plantillas variadas también (ver la respuesta de Julian). Sin embargo, no hice uso de recursion o std::function . Nota: Utiliza varias funciones de C ++ 14 .

El ejemplo proporcionado (función main ) se ejecuta en realidad en tiempo de compilación, lo que demuestra que el método de currying no prevalece sobre las optimizaciones esenciales del compilador.

El código se puede encontrar aquí: https://gist.github.com/Garciat/c7e4bef299ee5c607948

con este archivo auxiliar: https://gist.github.com/Garciat/cafe27d04cfdff0e891e

El código todavía necesita (mucho) trabajo, que puedo o no completar pronto. De cualquier manera, estoy publicando esto aquí para referencia futura.

Código de publicación en caso de que los enlaces mueran (aunque no deberían):

#include <type_traits> #include <tuple> #include <functional> #include <iostream> // --- template <typename FType> struct function_traits; template <typename RType, typename... ArgTypes> struct function_traits<RType(ArgTypes...)> { using arity = std::integral_constant<size_t, sizeof...(ArgTypes)>; using result_type = RType; template <size_t Index> using arg_type = typename std::tuple_element<Index, std::tuple<ArgTypes...>>::type; }; // --- namespace details { template <typename T> struct function_type_impl : function_type_impl<decltype(&T::operator())> { }; template <typename RType, typename... ArgTypes> struct function_type_impl<RType(ArgTypes...)> { using type = RType(ArgTypes...); }; template <typename RType, typename... ArgTypes> struct function_type_impl<RType(*)(ArgTypes...)> { using type = RType(ArgTypes...); }; template <typename RType, typename... ArgTypes> struct function_type_impl<std::function<RType(ArgTypes...)>> { using type = RType(ArgTypes...); }; template <typename T, typename RType, typename... ArgTypes> struct function_type_impl<RType(T::*)(ArgTypes...)> { using type = RType(ArgTypes...); }; template <typename T, typename RType, typename... ArgTypes> struct function_type_impl<RType(T::*)(ArgTypes...) const> { using type = RType(ArgTypes...); }; } template <typename T> struct function_type : details::function_type_impl<typename std::remove_cv<typename std::remove_reference<T>::type>::type> { }; // --- template <typename Args, typename Params> struct apply_args; template <typename HeadArgs, typename... Args, typename HeadParams, typename... Params> struct apply_args<std::tuple<HeadArgs, Args...>, std::tuple<HeadParams, Params...>> : std::enable_if< std::is_constructible<HeadParams, HeadArgs>::value, apply_args<std::tuple<Args...>, std::tuple<Params...>> >::type { }; template <typename... Params> struct apply_args<std::tuple<>, std::tuple<Params...>> { using type = std::tuple<Params...>; }; // --- template <typename TupleType> struct is_empty_tuple : std::false_type { }; template <> struct is_empty_tuple<std::tuple<>> : std::true_type { }; // ---- template <typename FType, typename GivenArgs, typename RestArgs> struct currying; template <typename FType, typename... GivenArgs, typename... RestArgs> struct currying<FType, std::tuple<GivenArgs...>, std::tuple<RestArgs...>> { std::tuple<GivenArgs...> given_args; FType func; template <typename Func, typename... GivenArgsReal> constexpr currying(Func&& func, GivenArgsReal&&... args) : given_args(std::forward<GivenArgsReal>(args)...), func(std::move(func)) { } template <typename... Args> constexpr auto operator() (Args&&... args) const& { using ParamsTuple = std::tuple<RestArgs...>; using ArgsTuple = std::tuple<Args...>; using RestArgsPrime = typename apply_args<ArgsTuple, ParamsTuple>::type; using CanExecute = is_empty_tuple<RestArgsPrime>; return apply(CanExecute{}, std::make_index_sequence<sizeof...(GivenArgs)>{}, std::forward<Args>(args)...); } template <typename... Args> constexpr auto operator() (Args&&... args) && { using ParamsTuple = std::tuple<RestArgs...>; using ArgsTuple = std::tuple<Args...>; using RestArgsPrime = typename apply_args<ArgsTuple, ParamsTuple>::type; using CanExecute = is_empty_tuple<RestArgsPrime>; return std::move(*this).apply(CanExecute{}, std::make_index_sequence<sizeof...(GivenArgs)>{}, std::forward<Args>(args)...); } private: template <typename... Args, size_t... Indices> constexpr auto apply(std::false_type, std::index_sequence<Indices...>, Args&&... args) const& { using ParamsTuple = std::tuple<RestArgs...>; using ArgsTuple = std::tuple<Args...>; using RestArgsPrime = typename apply_args<ArgsTuple, ParamsTuple>::type; using CurryType = currying<FType, std::tuple<GivenArgs..., Args...>, RestArgsPrime>; return CurryType{ func, std::get<Indices>(given_args)..., std::forward<Args>(args)... }; } template <typename... Args, size_t... Indices> constexpr auto apply(std::false_type, std::index_sequence<Indices...>, Args&&... args) && { using ParamsTuple = std::tuple<RestArgs...>; using ArgsTuple = std::tuple<Args...>; using RestArgsPrime = typename apply_args<ArgsTuple, ParamsTuple>::type; using CurryType = currying<FType, std::tuple<GivenArgs..., Args...>, RestArgsPrime>; return CurryType{ std::move(func), std::get<Indices>(std::move(given_args))..., std::forward<Args>(args)... }; } template <typename... Args, size_t... Indices> constexpr auto apply(std::true_type, std::index_sequence<Indices...>, Args&&... args) const& { return func(std::get<Indices>(given_args)..., std::forward<Args>(args)...); } template <typename... Args, size_t... Indices> constexpr auto apply(std::true_type, std::index_sequence<Indices...>, Args&&... args) && { return func(std::get<Indices>(std::move(given_args))..., std::forward<Args>(args)...); } }; // --- template <typename FType, size_t... Indices> constexpr auto curry(FType&& func, std::index_sequence<Indices...>) { using RealFType = typename function_type<FType>::type; using FTypeTraits = function_traits<RealFType>; using CurryType = currying<FType, std::tuple<>, std::tuple<typename FTypeTraits::template arg_type<Indices>...>>; return CurryType{ std::move(func) }; } template <typename FType> constexpr auto curry(FType&& func) { using RealFType = typename function_type<FType>::type; using FTypeArity = typename function_traits<RealFType>::arity; return curry(std::move(func), std::make_index_sequence<FTypeArity::value>{}); } // --- int main() { auto add = curry([](int a, int b) { return a + b; }); std::cout << add(5)(10) << std::endl; }


Otras respuestas explican muy bien las carpetas, así que no repetiré esa parte aquí. Solo demostraré cómo currying y la aplicación parcial se puede hacer con lambdas en C ++ 0x.

Ejemplo de código: (Explicación en comentarios)

#include <iostream> #include <functional> using namespace std; const function<int(int, int)> & simple_add = [](int a, int b) -> int { return a + b; }; const function<function<int(int)>(int)> & curried_add = [](int a) -> function<int(int)> { return [a](int b) -> int { return a + b; }; }; int main() { // Demonstrating simple_add cout << simple_add(4, 5) << endl; // prints 9 // Demonstrating curried_add cout << curried_add(4)(5) << endl; // prints 9 // Create a partially applied function from curried_add const auto & add_4 = curried_add(4); cout << add_4(5) << endl; // prints 9 }


Si usas C ++ 14 es muy fácil:

template<typename Function, typename... Arguments> auto curry(Function function, Arguments... args) { return [=](auto... rest) { return function(args..., rest...); } }

Puede usarlo así:

auto add = [](auto x, auto y) { return x + y; } // curry 4 into add auto add4 = curry(add, 4); add4(6); // 10


Simplificando el ejemplo de Gregg, usando tr1:

#include <functional> using namespace std; using namespace std::tr1; using namespace std::tr1::placeholders; int f(int, int); .. int main(){ function<int(int)> g = bind(f, _1, 5); // g(x) == f(x, 5) function<int(int)> h = bind(f, 2, _1); // h(x) == f(2, x) function<int(int,int)> j = bind(g, _2); // j(x,y) == g(y) }

Los componentes funcionales Tr1 le permiten escribir un código de estilo funcional en C ++. Además, C ++ 0x permitirá que las funciones lambda en línea hagan esto también:

int f(int, int); .. int main(){ auto g = [](int x){ return f(x,5); }; // g(x) == f(x, 5) auto h = [](int x){ return f(2,x); }; // h(x) == f(2, x) auto j = [](int x, int y){ return g(y); }; // j(x,y) == g(y) }

Y aunque C ++ no proporciona el rico análisis de efectos secundarios que realizan algunos lenguajes de programación orientados a funciones, el análisis de const y la sintaxis lambda de C ++ 0x pueden ayudar:

struct foo{ int x; int operator()(int y) const { x = 42; // error! const function can''t modify members } }; .. int main(){ int x; auto f = [](int y){ x = 42; }; // error! lambdas don''t capture by default. }

Espero que ayude.


En resumen, currying toma una función f(x, y) y dado un Y fijo, da una nueva función g(x) donde

g(x) == f(x, Y)

Esta nueva función se puede invocar en situaciones en las que solo se proporciona un argumento, y pasa la llamada a la función f original con el argumento Y fijo.

Los enlaces en el STL le permiten hacer esto para las funciones de C ++. Por ejemplo:

#include <functional> #include <iostream> #include <vector> using namespace std; // declare a binary function object class adder: public binary_function<int, int, int> { public: int operator()(int x, int y) const { return x + y; } }; int main() { // initialise some sample data vector<int> a, b; a.push_back(1); a.push_back(2); a.push_back(3); // here we declare a function object f and try it out adder f; cout << "f(2, 3) = " << f(2, 3) << endl; // transform() expects a function with one argument, so we use // bind2nd to make a new function based on f, that takes one // argument and adds 5 to it transform(a.begin(), a.end(), back_inserter(b), bind2nd(f, 5)); // output b to see what we got cout << "b = [" << endl; for (vector<int>::iterator i = b.begin(); i != b.end(); ++i) { cout << " " << *i << endl; } cout << "]" << endl; return 0; }