1. Что такое карри?
Каррирование просто означает преобразование функции нескольких аргументов в функцию одного аргумента. Это легче всего проиллюстрировать на примере:
Возьмем функцию f
, которая принимает три аргумента:
int
f(int a,std::string b,float c)
{
// do something with a, b, and c
return 0;
}
Если мы хотим вызвать f
, мы должны предоставить все его аргументы f(1,"some string",19.7f)
.
Затем каррированная версия f
, назовем ее curried_f=curry(f)
, ожидает только одного аргумента, который соответствует первому аргументу f
, а именно аргументу a
. Кроме того, f(1,"some string",19.7f)
также может быть написано с использованием карри версии как curried_f(1)("some string")(19.7f)
. Возвращаемое значение curried_f(1)
, с другой стороны, является просто еще одной функцией, которая обрабатывает следующий аргумент f
. В итоге мы получаем функцию или функцию curried_f
, которая выполняет следующее равенство:
curried_f(first_arg)(second_arg)...(last_arg) == f(first_arg,second_arg,...,last_arg).
2. Как достичь карри в C ++?
Следующее немного более сложно, но работает очень хорошо для меня (используя c ++ 11) ... Это также позволяет выполнять каррирование в произвольной степени, например: auto curried=curry(f)(arg1)(arg2)(arg3)
и позже auto result=curried(arg4)(arg5)
. Вот оно:
#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;
}
Просмотр вывода
Хорошо, как прокомментировал Самер, я должен добавить некоторые пояснения относительно того, как это работает. Реальная реализация выполняется в _dtl::_curry
, в то время как функции шаблона curry
являются только вспомогательными обертками Реализация рекурсивна по аргументам std::function
аргумента шаблона FUNCTION
.
Для функции с одним аргументом результат идентичен исходной функции.
_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;
}
) {}
Вот хитрость: для функции с большим количеством аргументов мы возвращаем лямбду, аргумент которой связан с первым аргументом при вызове fun
. Наконец, оставшееся каррирование для оставшихся N-1
аргументов делегируется реализации _curry<Ts...>
с одним меньшим аргументом шаблона.
Обновление для c ++ 14/17:
Новая идея подойти к проблеме каррирования пришла ко мне ... С введением if constexpr
в c ++ 17 (и с помощью void_t
, чтобы определить, является ли функция полностью каррированной), кажется, все становится намного проще:
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);
}
Смотрите код в действии на здесь . При аналогичном подходе здесь - это способ карри функций с произвольным числом аргументов.
Эта же идея, похоже, работает и в C ++ 14, если мы поменяем constexpr if
на выбор шаблона в зависимости от теста 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);
}