What is currying?
How can currying be done in C++?
Please Explain binders in STL container?
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).
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.
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:
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);
}