How can currying be done in C++?

前端 未结 10 1469
余生分开走
余生分开走 2020-11-28 03:39

What is currying?

How can currying be done in C++?

Please Explain binders in STL container?

10条回答
  •  被撕碎了的回忆
    2020-11-28 04:14

    1. What is currying?

    Currying simply means a transformation of a function of several arguments to a function of a single argument. This is most easily illustrated using an example:

    Take a function f that accepts three arguments:

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

    If we want to call f, we have to provide all of its arguments f(1,"some string",19.7f).

    Then a curried version of f, let's call it curried_f=curry(f) only expects a single argument, that corresponds to the first argument of f, namely the argument a. Additionally, f(1,"some string",19.7f) can also be written using the curried version as curried_f(1)("some string")(19.7f). The return value of curried_f(1) on the other hand is just another function, that handles the next argument of f. In the end, we end up with a function or callable curried_f that fulfills the following equality:

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

    2. How can currying be achieved in C++?

    The following is a little bit more complicated, but works very well for me (using c++11)... It also allows currying of arbitrary degree like so: auto curried=curry(f)(arg1)(arg2)(arg3) and later auto result=curried(arg4)(arg5). Here it goes:

    #include 
    
    namespace _dtl {
    
        template  struct
        _curry;
    
        // specialization for functions with a single argument
        template  struct
        _curry> {
            using
            type = std::function;
            
            const type
            result;
            
            _curry(type fun) : result(fun) {}
            
        };
    
        // recursive specialization for functions with more arguments
        template  struct
        _curry> {
            using
            remaining_type = typename _curry >::type;
            
            using
            type = std::function;
            
            const type
            result;
            
            _curry(std::function fun)
            : result (
                [=](const T& t) {
                    return _curry>(
                        [=](const Ts&...ts){ 
                            return fun(t, ts...); 
                        }
                    ).result;
                }
            ) {}
        };
    }
    
    template  auto
    curry(const std::function fun)
    -> typename _dtl::_curry>::type
    {
        return _dtl::_curry>(fun).result;
    }
    
    template  auto
    curry(R(* const fun)(Ts...))
    -> typename _dtl::_curry>::type
    {
        return _dtl::_curry>(fun).result;
    }
    
    #include 
    
    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;
    }
    

    View output

    OK, as Samer commented, I should add some explanations as to how this works. The actual implementation is done in the _dtl::_curry, while the template functions curry are only convenience wrappers. The implementation is recursive over the arguments of the std::function template argument FUNCTION.

    For a function with only a single argument, the result is identical to the original function.

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

    Here the tricky thing: For a function with more arguments, we return a lambda whose argument is bound to the first argument to the call to fun. Finally, the remaining currying for the remaining N-1 arguments is delegated to the implementation of _curry with one less template argument.

    Update for c++14 / 17:

    A new idea to approach the problem of currying just came to me... With the introduction of if constexpr into c++17 (and with the help of void_t to determine if a function is fully curried), things seem to get a lot easier:

    template< class, class = std::void_t<> > struct 
    needs_unapply : std::true_type { };
     
    template< class T > struct 
    needs_unapply()())>> : std::false_type { };
    
    template  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::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);
    }
    

    See code in action on here. With a similar approach, here is how to curry functions with arbitrary number of arguments.

    The same idea seems to work out also in C++14, if we exchange the constexpr if with a template selection depending on the test needs_unapply::value:

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

提交回复
热议问题