Partial application with a C++ lambda?

前端 未结 4 1773
粉色の甜心
粉色の甜心 2021-02-04 05:21

EDIT: I use curry below, but have been informed this is instead partial application.

I\'ve been trying to figure out how one would write a curry function in C++, and i a

相关标签:
4条回答
  • 2021-02-04 05:27

    With some C++14 features, partial application that works on lambda's can be implemented in a pretty concise way.

    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)();
    
    0 讨论(0)
  • 2021-02-04 05:40

    This is a way to have currying in C++ and may or may not be relevant after the recent edits to the OP.

    Due to overloading it is very problematic to inspect a functor and detect its arity. What is possible however is that given a functor f and an argument a, we can check if f(a) is a valid expression. If it isn't, we can store a and given a following argument b we can check if f(a, b) is a valid expression, and so on. To wit:

    #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), {} }; }
    

    The above code compiles using a snapshot of GCC 4.8 (barring copy-and-paste errors), provided that there is an indices type and an indices_for utility. This question and its answer demonstrates the need and implementation of such things, where seq plays the role of indices and gens can be used to implement a (more convenient) indices_for.

    Great care is taken in the above when it comes to value category and lifetime of (possible) temporaries. curry (and its accompanying type, which is an implementation detail) is designed to be as lightweight as possible while still making it very, very safe to use. In particular, usage such as:

    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);
    

    does not copy f, a or b; nor does it result in dangling references to temporaries. This all still holds true even if auto is substituted with auto&& (assuming quux is sane, but that's beyond the control of curry). It's still possible to come up with different policies in that regard (e.g. systematically decaying).

    Note that parameters (but not the functor) are passed with the same value category in the final call as when they're passed to the curried wrapper. Hence in

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

    this means that a moved-from foo is passed to the underlying functor when computing r1.

    0 讨论(0)
  • 2021-02-04 05:41

    A lot of the examples people provided and that i saw elsewhere used helper classes to do whatever they did. I realized this becomes trivial to write when you do that!

    #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)() );
    }
    
    0 讨论(0)
  • 2021-02-04 05:48

    Your curry function is just a scaled down inefficient subcase of std::bind (std::bind1st and bind2nd should not be used anymore now that we have std::result_of)

    Your two lines read in fact

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

    after having used namespace std::placeholders. This carefully avoids the boxing into std::function and allows the compiler to inline more easily the result at the call site.

    For functions of two arguments, hacking something like

    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)
    }
    

    may work, but it is difficult to generalize to the variadic case (for which you'd end up rewriting a lot of the logic in std::bind).

    Also currying is not partial application. Currying has "signature"

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

    ie. it is the action to transform a function taking two arguments into a function returning a function. It has an inverse uncurry performing the reverse operation (for mathematicians: curry and uncurry are isomorphisms, and define an adjunction). This inverse is very cumbersome to write in C++ (hint: use std::result_of).

    0 讨论(0)
提交回复
热议问题