2012-07-24 7 views
16

EDIT: Ich benutze Curry unten, aber wurde informiert, dass dies stattdessen teilweise Anwendung ist.Teilanwendung mit einem C++ Lambda?

Ich habe versucht herauszufinden, wie man eine Curry-Funktion in C++ schreiben würde, und ich habe es tatsächlich herausgefunden!

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

Und ich schrieb auch eine Version für 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...); }; 
} 

Die Tests:

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

Yuck! Die Zeileninitialisierung g2 ist so groß, dass ich sie genauso gut manuell curren könnte.

auto g2 = [](int y){ return 2*y; }; 

Viel kürzer. Aber da die Absicht ist, eine wirklich generische und bequeme Curry-Funktion zu haben, könnte ich entweder (1) eine bessere Funktion schreiben oder (2) irgendwie mein Lambda, um implizit eine std :: -Funktion zu konstruieren? Ich fürchte, die aktuelle Version verletzt die Regel der geringsten Überraschung, wenn f keine freie Funktion ist. Besonders ärgerlich ist, dass keine make_function oder ähnliche Funktion, die ich kenne, existiert. Meine ideale Lösung wäre einfach ein Aufruf von std :: bind, aber ich bin mir nicht sicher, wie ich es mit variadischen Vorlagen verwenden soll.

PS: Kein Boost, bitte, aber ich werde mich beruhigen, wenn nichts anderes.

EDIT: Ich kenne bereits std :: bind. Ich würde diese Funktion nicht schreiben, wenn std :: bind genau das tut, was ich mit der besten Syntax wollte. Dies sollte eher ein Spezialfall sein, bei dem nur das erste Element gebunden wird.

Wie gesagt, meine ideale Lösung sollte binden, aber wenn ich das verwenden wollte, würde ich das verwenden.

+0

Sie sagen, kein Boost, aber warum? Wenn Sie die Bibliothek nicht verwenden möchten, können Sie die bereitgestellten Funktionen kopieren. – mydogisbox

+0

Ihre Curry-Funktion hat die Funktionalität von bind. Du könntest alternativ 'auto fn = std :: bind ([] (int x, int y) {return x * y;}, std :: platzhalter :: _ 1, 5);' – mkaes

+0

mydogisbox: Weil in den fünf Jahren benutzen Ich habe programmiert, ich bin eine Verachtung für Boost geworden. Ich könnte ein Feature neu implementieren, wenn es ausreichend klein ist, aber ich erwarte von Boost nicht klein und funktional. Plus, viele seiner Funktionen sind Standard geworden. Ich bin jedoch immer offen dafür, sich als falsch zu erweisen. – SplinterOfChaos

Antwort

0

Viele der Beispiele, die Leute zur Verfügung gestellt haben und die ich anderswo gesehen habe, haben Hilfsklassen benutzt, um zu tun, was auch immer sie getan haben. Ich merkte, dass es trivial wird, wenn du das machst!

#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)()); 
} 
+0

Dieser Code ist ein wenig gefährlich, weil beide F und Arg in der PartialApplication-Vorlage könnten als Referenzen abgeleitet werden, wenn partiell angewendete Funktionen mit lvalues ​​aufgerufen werden. Sowohl F als auch Arg im PartialApplication-Konstruktor sind keine universellen Referenzen. std :: das Weiterleiten von ihnen macht wenig Sinn. Ich würde einfach By-Wert und Std :: Move innerhalb des Konstruktors übergeben. Die zwei Argument-Funktion partiell muss std :: remove_reference von F- und A-Typen verwenden, bevor sie an die PartialApplication-Vorlage übergeben wird. – Sumant

10

Ihre curry Funktion ist nur ein abgespeckte ineffizienter Unterfall von std::bind (std::bind1st und bind2nd soll jetzt nicht mehr verwendet werden, dass wir std::result_of haben)

Ihre zwei Zeilen zu lesen in der Tat

auto f5 = std::bind(f, 5, _1); 
auto g2 = std::bind(std::multiplies<int>(), 2, _1); 

nachdem verwendet namespace std::placeholders. Dies vermeidet sorgfältig das Einbetten in std::function und ermöglicht es dem Compiler, das Ergebnis an der Aufruf-Site leichter zu inline zu bringen.

Für Funktionen zweier Argumente, Hacking etwas wie

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) 
} 

arbeiten kann, aber es ist schwierig, den variadische Fall zu verallgemeinern (für das Sie eine Menge von der Logik würde am Ende in std::bind Umschreiben) .

Auch Currying ist keine teilweise Anwendung. Currying hat "Unterschrift"

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

ie. es ist die Aktion, um eine Funktion zu transformieren, die zwei Argumente zu einer Funktion macht, die eine Funktion zurückgibt. Es hat eine inverse uncurry Durchführung der umgekehrten Operation (für Mathematiker: curry und uncurry sind Isomorphismen und definieren eine Adjunktion). Diese Umkehrung ist in C++ sehr umständlich zu schreiben (Hinweis: Verwenden Sie std::result_of).

+1

Ich denke, das OP möchte die Notwendigkeit von Platzhaltern vermeiden. IIUC, er will eine 'bind1st'-Version, die mit jedem aufrufbaren Objekt funktioniert. 'bind' zwingt Sie, alle Parameter zu binden, was eine Last sein kann, wenn es viele davon gibt, oder wenn Sie generischen Code schreiben. –

+2

Ich glaube, du hast Currying und Uncurry verwechselt: Curry verwandelt eine n-ary-Funktion in eine "Kette" unärer Funktionen, während Uncurrying das Gegenteil bewirkt. –

+0

@LucTouraille: Korrigiert, danke. –

8

Dies ist eine Möglichkeit, in C++ zu curryen und kann nach den letzten Änderungen am OP relevant sein oder auch nicht.

Aufgrund der Überlastung ist es sehr problematisch, einen Funktor zu inspizieren und zu erkennen. Was jedoch möglich ist, ist, dass wir bei einem Funktor f und einem Argument a prüfen können, ob f(a) ein gültiger Ausdruck ist. Wenn dies nicht der Fall ist, können wir a speichern und mit folgendem Argument b überprüfen, ob f(a, b) ein gültiger Ausdruck ist und so weiter. Nämlich:

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

Der obige Code kompiliert mit einem Schnappschuss von GCC 4.8 (abgesehen von Copy-and-Paste-Fehlern), vorausgesetzt, dass es ein indices Typen und ein indices_for Dienstprogramm. This question und seine Antwort zeigt die Notwendigkeit und Umsetzung solcher Dinge, wo seq spielt die Rolle von indices und kann verwendet werden, um eine (bequemere) indices_for zu implementieren.

In Bezug auf Wertkategorie und Lebensdauer von (möglichen) Provisorien wird sehr sorgfältig darauf geachtet. curry (und sein begleitender Typ, der ein Implementierungsdetail ist) ist so entworfen, um so leicht wie möglich zu sein, während es immer noch sehr, sehr sicher zu verwenden ist. Insbesondere Nutzung wie:

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); 

kopiert nicht f, a oder b; noch führt es dazu, dass Referenzen auf Provisorien erzeugt werden. Dies gilt immer noch, selbst wenn durch auto&& ersetzt wird (vorausgesetzt, quux ist gesund, aber das ist außerhalb der Kontrolle von curry). Es ist immer noch möglich, diesbezüglich verschiedene Richtlinien zu entwickeln (z. B. systematisch zu verfallen).

Beachten Sie, dass Parameter (aber nicht der Funktor) mit der gleichen Wertkategorie im letzten Aufruf übergeben werden, als wenn sie an den Curry-Wrapper übergeben werden. Daher wird in

auto functor = curry([](foo f, int) {}); 
auto curried = functor(foo {}); 
auto r0 = curried(0); 
auto r1 = curried(1); 

Dies bedeutet, dass eine eingefahrene von foo an dem darunterliegenden Funktors geben wird, wenn r1 berechnen.

+0

+1 für die Verwendung von [..., ...] (http://stackoverflow.com/questions/5625600/what-is-the-meaning-of-token) – Cubbi

3

Mit einigen C++ 14 Funktionen kann eine partielle Anwendung, die auf Lambdas funktioniert, ziemlich übersichtlich implementiert werden.

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)(); 
+1

'rest' sollte' auto && ... 'sein und als' declltype (rest) (rest) ... 'nicht' auto ... 'und als' rest ...' verwendet werden. Sie sollten auch in C++ 14 capture-by-forward sein, was Ihre zweite "partielle" Überlastung schwieriger macht. [Live-Beispiel] (http://coliru.stacked-crooked.com/a/69e7c7249fa6c5a8). Dies vermeidet viele unnötige Kopien Ihres Codes! – Yakk