Вопрос по c++11, currying, c++, lambda – Частичное применение с C ++ лямбда?

17

РЕДАКТИРОВАТЬ: я использую карри ниже, но мне сообщили, что это вместо частичного применения.

Я пытался выяснить, как можно написать функцию карри на C ++, и я действительно понял это!

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

И я написал версию для лямбды тоже.

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: пожалуйста, нет, но я согласен, если ничего больше.

РЕДАКТИРОВАТЬ: я уже знаю о std :: bind. Я бы не писал эту функцию, если бы std :: bind делал именно то, что я хотел, с лучшим синтаксисом. Это должно быть более частным случаем, когда он связывает только первый элемент.

Как я уже сказал, мое идеальное решение должноuse привязать, но если бы я хотел использовать это, я бы использовал это.

Ваша функция карри имеет функциональность связывания. Вы могли бы альтернативно использоватьauto fn = std::bind([](int x, int y){return x*y;}, std::placeholders::_1, 5); mkaes
Вы говорите, нет повышения, но почему? Если вы не хотите использовать библиотеку, вы все равно можете скопировать предоставляемые ею функции. mydogisbox
Я понимаю, что ваша проблема в том, что вы хотите избежать использования заполнителей (возможно, вы могли бы сделать это более понятным в своем вопросе). я нашелthis similar questionК сожалению, пока нет однозначного ответа ... Luc Touraille
То, что вы имеете здесь, на самом деле не карри, это скорее какое-то частичное применение (см. Комментарии кthis question). Для частичного применения стандартная библиотека предоставляетstd::bind, но вам нужно будет использовать заполнители для параметров, которые вы не хотите связывать. В качестве альтернативы, вы можете вернуться к «старому»std::bind1st. Luc Touraille
mydogisbox: поскольку за пять лет, которые я занимался кодированием, я испытывал презрение к Boost. Я мог бы повторно реализовать какую-то функцию, если бы она была достаточно маленькой, но я не ожидаю, что от Boost будет мало и функционально. Кроме того, многие его функции стали стандартными. Я, однако, всегда открыт для того, чтобы оказаться неправым. SplinterOfChaos

Ваш Ответ

4   ответа
10

Ваш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).

Также карри не является частичным применением. Карри имеет «подпись»

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

то есть. это действие для преобразования функции, принимающей два аргумента, в функцию, возвращающую функцию. Имеет обратнуюuncurry выполнение обратной операции (для математиков:curry а такжеuncurry являются изоморфизмами и определяют присоединение). Это обратное очень громоздко писать на C ++ (подсказка: используйтеstd::result_of).

@LucTouraille: исправлено, спасибо.
Спасибо, теперь я понял разницу. Я предполагаю, что, к вашему сведению, я действительно нашел код C ++, который некоторое время карриgithub.com/LeszekSwirski/cpp-curry), но я понял, что я скорее имею частичное применение. SplinterOfChaos
Нет,std::bind не является хорошим частичным заявлением.std::bind имеет кучу багажа, который мешает быть чистым частичным заявлением, котороеwill укусить вас.
Я думаю, что ОП хочет избежать необходимости в заполнителях. IIUC, он хочетbind1st версия, которая работает с любым вызываемым объектом.bind вынуждает вас связывать все параметры, что может быть бременем, когда их много или когда вы пишете общий код.
Я думаю, что вы перепутали каррирование и некуривание: карри превращает n-арную функцию в «цепочку» унарных функций, в то время как неспешный делает обратное.
0

Многие примеры, которые предоставили люди и которые я видел в других местах, использовали вспомогательные классы, чтобы делать то, что они делали. Я понял, что писать это становится тривиально, когда ты это делаешь!

#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)() );
}
Этот код немного опасен, потому что F и Arg в шаблоне PartialApplication могут быть выведены как ссылки, если частично примененные функции вызываются с помощью lvalues. И F, и Arg в конструкторе PartialApplication не являются универсальными ссылками. std :: forwarding их имеет мало смысла. Я бы просто передал как by-value, так и std :: move внутри конструктора. Частичная функция с двумя аргументами должна использовать std :: remove_reference из типов F и A перед передачей в шаблон PartialApplication.
8

Это способ иметь карри в C ++ и может иметь или не иметь отношения после недавних изменений в 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.

+1 за использование..., ...
4

С некоторыми функциями C ++ 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)();
rest должно бытьauto&&...и используется какdecltype(rest)(rest)... неauto... и используется какrest..., Вы должны также захватывать вперед в C ++ 14, что делает ваш второйpartial перегрузка хитрее.live example, Это позволяет избежать ненужных копий вашего кода!

Похожие вопросы