Частичное приложение с лямбдой С++?
EDIT: я использую карри ниже, но были проинформированы об этом, а не о частичном приложении.
Я пытался выяснить, как написать функцию карри в С++, и я действительно понял это!
#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... ); };
}
И я написал версию для lambdas тоже.
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... ); };
}
Тестирование:
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));
}
Тьфу! Инициализация строки g2 настолько велика, что я мог бы также ее вручную загрузить.
auto g2 = [](int y){ return 2*y; };
Гораздо короче. Но поскольку цель состоит в том, чтобы иметь действительно общую и удобную функцию карри, я мог бы либо (1) написать лучшую функцию, либо (2) каким-то образом моя лямбда неявно построила std:: function? Я боюсь, что текущая версия нарушает правило наименьшего удивления, когда f не является свободной функцией. Особенно раздражает то, что функция make_function или аналогичного типа, о которой я знаю, кажется, существует. Действительно, моим идеальным решением было бы просто вызов std:: bind, но я не уверен, как его использовать с вариативными шаблонами.
PS: Никакого повышения, пожалуйста, но я успокоюсь, если ничего другого.
EDIT: Я уже знаю о std:: bind. Я бы не писал эту функцию, если std:: bind сделал именно то, что я хотел, с лучшим синтаксисом. Это должно быть скорее особым случаем, когда он связывает только первый элемент.
Как я уже сказал, мое идеальное решение должно использовать привязку, но если бы я хотел использовать это, я бы использовал это.
Ответы
Ответ 1
Многие примеры, предоставленные людьми, и что я видел в другом месте, использовали вспомогательные классы, чтобы делать то, что они сделали. Я понял, что это становится тривиально писать, когда вы это делаете!
#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)() );
}
Ответ 2
Ваша функция curry
- это просто уменьшенный неэффективный подканал std::bind
(std::bind1st
и bind2nd
больше не должен использоваться сейчас, если у нас есть std::result_of
)
Ваши две строки на самом деле считаются
auto f5 = std::bind(f, 5, _1);
auto g2 = std::bind(std::multiplies<int>(), 2, _1);
после использования namespace std::placeholders
. Это тщательно избегает бокса в std::function
и позволяет компилятору легко встраивать результат на сайт вызова.
Для функций двух аргументов, взломать что-то вроде
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)
}
может работать, но его сложно обобщить в вариационном случае (для которого вы в конечном итоге переписываете много логики в std::bind
).
Также currying не является частичным приложением. Currying имеет "подпись"
((a, b) -> c) -> (a -> b -> c)
т. это действие для преобразования функции, переводящей два аргумента в функцию, возвращающую функцию. Он имеет обратный uncurry
, выполняющий обратную операцию (для математиков: curry
и uncurry
- изоморфизмы и определяют присоединение). Этот обратный очень громоздкий для записи в С++ (подсказка: используйте std::result_of
).
Ответ 3
Это способ иметь currying на С++ и может быть или не быть релевантным после недавних изменений в OP.
Из-за перегрузки очень проблематично проверять функтор и обнаруживать его арность. Однако возможно, что с учетом функтора f
и аргумента a
мы можем проверить, является ли f(a)
допустимым выражением. Если это не так, мы можем сохранить a
и дать следующий аргумент b
, мы можем проверить, является ли f(a, b)
допустимым выражением и так далее. К остроумию:
#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), {} }; }
Вышеприведенный код компилируется с помощью моментального снимка GCC 4.8 (запрет копирования и вставки ошибок) при условии, что существует тип indices
и indices_for
. Этот вопрос и его ответ демонстрируют необходимость и реализацию таких вещей, где seq
играет роль indices
и gens
, которая может быть использована для реализации ( более удобно) indices_for
.
В приведенном выше примере проявляется большая осторожность, когда речь идет о категории ценности и времени жизни (возможных) временных рядов. curry
(и его сопроводительный тип, который является деталью реализации), должен быть максимально легким, хотя при этом он очень безопасен в использовании. В частности, использование, например:
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);
не копирует f
, a
или b
; и не приводит к зависанию ссылок на временные. Это все еще верно, даже если auto
заменяется на auto&&
(предполагая, что quux
является нормальным, но не под контролем curry
). В этом отношении по-прежнему возможно разработать различные политики (например, систематически распадаться).
Обратите внимание, что параметры (но не функтор) передаются с той же категорией значений в последнем вызове, что и при их передаче в карри. Следовательно, в
auto functor = curry([](foo f, int) {});
auto curried = functor(foo {});
auto r0 = curried(0);
auto r1 = curried(1);
это означает, что перемещенный-из foo
передается базовому функтору при вычислении r1
.
Ответ 4
С некоторыми функциями С++ 14 частичное приложение, работающее на лямбда, может быть реализовано довольно кратким способом.
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)();