example c++ lambda c++11 currying

example - ¿Aplicación parcial con un lambda de C++?



c++ lambda example (4)

EDITAR: uso curry a continuación, pero me han informado que esto es una aplicación parcial.

¡He estado tratando de averiguar cómo se escribiría una función de curry en C ++, y en realidad lo descubrí!

#include <stdio.h> #include <functional> template< class Ret, class Arg1, class ...Args > auto curry( Ret f(Arg1,Args...), Arg1 arg ) -> std::function< Ret(Args...) > { return [=]( Args ...args ) { return f( arg, args... ); }; }

Y escribí una versión para lambdas, también.

template< class Ret, class Arg1, class ...Args > auto curry( const std::function<Ret(Arg1,Args...)>& f, Arg1 arg ) -> std::function< Ret(Args...) > { return [=]( Args ...args ) { return f( arg, args... ); }; }

Los exámenes:

int f( int x, int y ) { return x + y; } int main() { auto f5 = curry( f, 5 ); auto g2 = curry( std::function<int(int,int)>([](int x, int y){ return x*y; }), 2 ); printf("%d/n",f5(3)); printf("%d/n",g2(3)); }

Que asco La línea que inicializa g2 es tan grande que podría haberla aplicado manualmente.

auto g2 = [](int y){ return 2*y; };

Mucho más corto. Pero como la intención es tener una función de curry realmente genérica y conveniente, ¿podría (1) escribir una mejor función o (2) de alguna manera mi lambda para construir implícitamente una función std ::? Me temo que la versión actual viola la regla de la menor sorpresa cuando f no es una función gratuita. Especialmente molesto es que no existe ninguna función de make_function o similar que yo sepa que existe. Realmente, mi solución ideal sería simplemente una llamada a std :: bind, pero no estoy seguro de cómo usarla con las plantillas variadas.

PS: No hay impulso, por favor, pero me conformaré con nada más.

EDIT: ya sé acerca de std :: bind. No estaría escribiendo esta función si std :: bind hiciera exactamente lo que quería con la mejor sintaxis. Esto debería ser más un caso especial en el que solo vincula el primer elemento.

Como dije, mi solución ideal debería usar bind, pero si quisiera usar eso, usaría eso.


Con algunas características de C ++ 14, la aplicación parcial que funciona en lambda puede implementarse de una manera bastante concisa.

template<typename _function, typename _val> auto partial( _function foo, _val v ) { return [foo, v](auto... rest) { return foo(v, rest...); }; } template< typename _function, typename _val1, typename... _valrest > auto partial( _function foo, _val1 val, _valrest... valr ) { return [foo,val,valr...](auto... frest) { return partial(partial(foo, val), valr...)(frest...); }; } // partial application on lambda int p1 = partial([](int i, int j){ return i-j; }, 6)(2); int p2 = partial([](int i, int j){ return i-j; }, 6, 2)();


Esta es una forma de hacer curry en C ++ y puede o no ser relevante después de las ediciones recientes al OP.

Debido a la sobrecarga, es muy problemático inspeccionar un functor y detectar su aridad. Sin embargo, lo que es posible es que dado un funtor f y un argumento a , podemos verificar si f(a) es una expresión válida. Si no es así, podemos almacenar a y, dado el siguiente argumento b , podemos comprobar si f(a, b) es una expresión válida, y así sucesivamente. Esto es:

#include <utility> #include <tuple> /* Two SFINAE utilities */ template<typename> struct void_ { using type = void; }; template<typename T> using Void = typename void_<T>::type; // std::result_of doesn''t play well with SFINAE so we deliberately avoid it // and roll our own // For the sake of simplicity this result_of does not compute the same type // as std::result_of (e.g. pointer to members) template<typename Sig, typename Sfinae = void> struct result_of {}; template<typename Functor, typename... Args> struct result_of< Functor(Args...) , Void<decltype( std::declval<Functor>()(std::declval<Args>()...) )> > { using type = decltype( std::declval<Functor>()(std::declval<Args>()...) ); }; template<typename Functor, typename... Args> using ResultOf = typename result_of<Sig>::type; template<typename Functor, typename... Args> class curry_type { using tuple_type = std::tuple<Args...>; public: curry_type(Functor functor, tuple_type args) : functor(std::forward<Functor>(functor)) , args(std::move(args)) {} // Same policy as the wrappers from std::bind & others: // the functor inherits the cv-qualifiers from the wrapper // you might want to improve on that and inherit ref-qualifiers, too template<typename Arg> ResultOf<Functor&(Args..., Arg)> operator()(Arg&& arg) { return invoke(functor, std::tuple_cat(std::move(args), std::forward_as_tuple(std::forward<Arg>(arg)))); } // Implementation omitted for brevity -- same as above in any case template<typename Arg> ResultOf<Functor const&(Args..., Arg)> operator()(Arg&& arg) const; // Additional cv-qualified overloads omitted for brevity // Fallback: keep calm and curry on // the last ellipsis (...) means that this is a C-style vararg function // this is a trick to make this overload (and others like it) least // preferred when it comes to overload resolution // the Rest pack is here to make for better diagnostics if a user erroenously // attempts e.g. curry(f)(2, 3) instead of perhaps curry(f)(2)(3) // note that it is possible to provide the same functionality without this hack // (which I have no idea is actually permitted, all things considered) // but requires further facilities (e.g. an is_callable trait) template<typename Arg, typename... Rest> curry_type<Functor, Args..., Arg> operator()(Arg&& arg, Rest const&..., ...) { static_assert( sizeof...(Rest) == 0 , "Wrong usage: only pass up to one argument to a curried functor" ); return { std::forward<Functor>(functor), std::tuple_cat(std::move(args), std::forward_as_tuple(std::forward<Arg>(arg))) }; } // Again, additional overloads omitted // This is actually not part of the currying functionality // but is here so that curry(f)() is equivalent of f() iff // f has a nullary overload template<typename F = Functor> ResultOf<F&(Args...)> operator()() { // This check if for sanity -- if I got it right no user can trigger it // It *is* possible to emit a nice warning if a user attempts // e.g. curry(f)(4)() but requires further overloads and SFINAE -- // left as an exercise to the reader static_assert( sizeof...(Args) == 0, "How did you do that?" ); return invoke(functor, std::move(args)); } // Additional cv-qualified overloads for the nullary case omitted for brevity private: Functor functor; mutable tuple_type args; template<typename F, typename Tuple, int... Indices> ResultOf<F(typename std::tuple_element<Indices, Tuple>::type...)> static invoke(F&& f, Tuple&& tuple, indices<Indices...>) { using std::get; return std::forward<F>(f)(get<Indices>(std::forward<Tuple>(tuple))...); } template<typename F, typename Tuple> static auto invoke(F&& f, Tuple&& tuple) -> decltype( invoke(std::declval<F>(), std::declval<Tuple>(), indices_for<Tuple>()) ) { return invoke(std::forward<F>(f), std::forward<Tuple>(tuple), indices_for<Tuple>()); } }; template<typename Functor> curry_type<Functor> curry(Functor&& functor) { return { std::forward<Functor>(functor), {} }; }

El código anterior se compila utilizando una instantánea de GCC 4.8 (excepto los errores de copiar y pegar), siempre que haya un tipo de indices y una utilidad indices_for . Esta pregunta y su respuesta demuestran la necesidad y la implementación de tales cosas, donde seq desempeña el papel de indices y gens se puede utilizar para implementar indices (más convenientes) indices_for .

Se tiene mucho cuidado con lo anterior cuando se trata de valorar la categoría y el tiempo de vida de (posibles) temporarios. curry (y el tipo que lo acompaña, que es un detalle de implementación) está diseñado para ser lo más ligero posible y, al mismo tiempo, lo hace muy, muy seguro de usar. En particular, el uso como:

foo a; bar b; auto f = [](foo a, bar b, baz c, int) { return quux(a, b, c); }; auto curried = curry(f); auto pass = curried(a); auto some = pass(b); auto parameters = some(baz {}); auto result = parameters(0);

no copia f , b ; ni da lugar a referencias colgantes a los temporales. Todo esto sigue siendo válido incluso si se sustituye auto&& por auto&& (asumiendo que quux es sano, pero eso está fuera del control de curry ). Todavía es posible llegar a diferentes políticas en ese sentido (por ejemplo, en descomposición sistemática).

Tenga en cuenta que los parámetros (pero no el functor) se pasan con la misma categoría de valor en la llamada final que cuando se pasan a la envoltura al curry. Por lo tanto en

auto functor = curry([](foo f, int) {}); auto curried = functor(foo {}); auto r0 = curried(0); auto r1 = curried(1);

esto significa que se pasa un foo movido desde al funtor subyacente cuando se calcula r1 .


Muchos de los ejemplos que proporcionaron las personas y que vi en otros lugares usaron clases de ayuda para hacer lo que hicieron. ¡Me di cuenta de que esto se vuelve trivial para escribir cuando haces eso!

#include <utility> // for declval #include <array> #include <cstdio> using namespace std; template< class F, class Arg > struct PartialApplication { F f; Arg arg; constexpr PartialApplication( F&& f, Arg&& arg ) : f(forward<F>(f)), arg(forward<Arg>(arg)) { } /* * The return type of F only gets deduced based on the number of arguments * supplied. PartialApplication otherwise has no idea whether f takes 1 or 10 args. */ template< class ... Args > constexpr auto operator() ( Args&& ...args ) -> decltype( f(arg,declval<Args>()...) ) { return f( arg, forward<Args>(args)... ); } }; template< class F, class A > constexpr PartialApplication<F,A> partial( F&& f, A&& a ) { return PartialApplication<F,A>( forward<F>(f), forward<A>(a) ); } /* Recursively apply for multiple arguments. */ template< class F, class A, class B > constexpr auto partial( F&& f, A&& a, B&& b ) -> decltype( partial(partial(declval<F>(),declval<A>()), declval<B>()) ) { return partial( partial(forward<F>(f),forward<A>(a)), forward<B>(b) ); } /* Allow n-ary application. */ template< class F, class A, class B, class ...C > constexpr auto partial( F&& f, A&& a, B&& b, C&& ...c ) -> decltype( partial(partial(declval<F>(),declval<A>()), declval<B>(),declval<C>()...) ) { return partial( partial(forward<F>(f),forward<A>(a)), forward<B>(b), forward<C>(c)... ); } int times(int x,int y) { return x*y; } int main() { printf( "5 * 2 = %d/n", partial(times,5)(2) ); printf( "5 * 2 = %d/n", partial(times,5,2)() ); }


Su función de curry es solo un subcaso ineficiente de std::bind ( std::bind1st y bind2nd no debe usarse más ahora que tenemos std::result_of )

Tus dos líneas leen de hecho

auto f5 = std::bind(f, 5, _1); auto g2 = std::bind(std::multiplies<int>(), 2, _1);

después de haber usado namespace std::placeholders . Esto evita cuidadosamente el boxeo en la std::function y permite al compilador en línea más fácilmente el resultado en el sitio de la llamada.

Para funciones de dos argumentos, hackear algo así como

auto bind1st(F&& f, T&& t) -> decltype(std::bind(std::forward<F>(f), std::forward<T>(t), _1)) { return std::bind(std::forward<F>(f), std::forward<T>(t), _1) }

puede funcionar, pero es difícil generalizar al caso variadic (para el cual terminaría reescribiendo gran parte de la lógica en std::bind ).

Tampoco el currying es de aplicación parcial. Currying tiene "firma"

((a, b) -> c) -> (a -> b -> c)

es decir. es la acción de transformar una función tomando dos argumentos en una función que devuelve una función. Tiene una función de operación inversa ( uncurry está en uncurry ) para los matemáticos: curry y uncurry son isomorfismos, y definen una adjunción. Este inverso es muy engorroso de escribir en C ++ (sugerencia: use std::result_of ).