webentwicklung-frage-antwort-db.com.de

Was ist der Unterschied zwischen Currying und Teilbewerbung?

Ich sehe ziemlich oft im Internet verschiedene Beschwerden, dass die Beispiele für Currys anderer Leute keine Currys sind, sondern nur teilweise Anwendung finden.

Ich habe keine anständige Erklärung dafür gefunden, was eine Teilanwendung ist oder wie sie sich vom Currying unterscheidet. Es scheint eine allgemeine Verwirrung zu geben, wobei äquivalente Beispiele an einigen Stellen als Currys beschrieben werden und an anderen Stellen teilweise angewendet werden.

Könnte mir jemand eine Definition beider Begriffe und Einzelheiten zu deren Unterschieden geben?

404
SpoonMeiser

Currying konvertiert eine einzelne Funktion von n Argumenten in n Funktionen mit a jeweils ein Argument. Folgende Funktion vorausgesetzt:

function f(x,y,z) { z(x(y));}

Wenn Curry wird:

function f(x) { lambda(y) { lambda(z) { z(x(y)); } } }

Um die vollständige Anwendung von f (x, y, z) zu erhalten, müssen Sie dies tun:

f(x)(y)(z);

In vielen funktionalen Sprachen können Sie f x y z Schreiben. Wenn Sie nur f x y Oder f (x) (y) aufrufen, erhalten Sie eine teilweise angewendete Funktion - der Rückgabewert ist a Schließen von lambda(z){z(x(y))} mit Übergabe der Werte von x und y an f(x,y).

Eine Möglichkeit, eine Teilanwendung zu verwenden, besteht darin, Funktionen als Teilanwendungen von verallgemeinerten Funktionen zu definieren, wie fold:

function fold(combineFunction, accumulator, list) {/* ... */}
function sum     = curry(fold)(lambda(accum,e){e+accum}))(0);
function length  = curry(fold)(lambda(accum,_){1+accum})(empty-list);
function reverse = curry(fold)(lambda(accum,e){concat(e,accum)})(empty-list);

/* ... */
@list = [1, 2, 3, 4]
sum(list) //returns 10
@f = fold(lambda(accum,e){e+accum}) //f = lambda(accumulator,list) {/*...*/}
f(0,list) //returns 10
@g = f(0) //same as sum
g(list)  //returns 10
234
Mark Cidade

Der einfachste Weg zu sehen, wie sie sich unterscheiden, besteht darin, ein reales Beispiel zu betrachten. Nehmen wir an, wir haben eine Funktion Add, die 2 Zahlen als Eingabe annimmt und eine Zahl als Ausgabe zurückgibt, z. Add(7, 5) gibt 12 zurück. In diesem Fall:

  • Teilweise anwenden Die Funktion Add mit einem Wert 7 Gibt uns eine neue Funktion als Ausgabe. Diese Funktion selbst nimmt 1 Nummer als Eingabe und gibt eine Nummer aus. So wie:

    Partial(Add, 7); // returns a function f2 as output
    
                     // f2 takes 1 number as input and returns a number as output
    

    Also können wir das machen:

    f2 = Partial(Add, 7);
    f2(5); // returns 12;
           // f2(7)(5) is just a syntactic shortcut
    
  • Currying Die Funktion Add gibt uns eine neue Funktion als Ausgabe. Diese Funktion selbst nimmt eine Zahl als Ein- und Ausgabe und noch eine weitere neue Funktion . Diese dritte Funktion nimmt dann 1 Zahl als Eingabe und gibt eine Zahl als Ausgabe zurück. So wie:

    Curry(Add); // returns a function f2 as output
    
                // f2 takes 1 number as input and returns a function f3 as output
                // i.e. f2(number) = f3
    
                // f3 takes 1 number as input and returns a number as output
                // i.e. f3(number) = number
    

    Also können wir das machen:

    f2 = Curry(Add);
    f3 = f2(7);
    f3(5); // returns 12
    

Mit anderen Worten, "Currying" und "Partial Application" sind zwei völlig verschiedene Funktionen. Currying benötigt genau 1 Eingabe, während Teilanwendung 2 (oder mehr) Eingaben benötigt.

Obwohl beide eine Funktion als Ausgabe zurückgeben, weisen die zurückgegebenen Funktionen, wie oben gezeigt, völlig unterschiedliche Formen auf.

151
Pacerier

Hinweis: Dies stammt aus F # Basics , einem hervorragenden Einführungsartikel für .NET-Entwickler, die sich mit funktionaler Programmierung befassen.

Currying bedeutet, eine Funktion mit vielen Argumenten in eine Reihe von Funktionen aufzuteilen, die jeweils ein Argument enthalten und letztendlich dasselbe Ergebnis wie die ursprüngliche Funktion liefern. Currying ist wahrscheinlich das herausforderndste Thema für Entwickler, die noch keine Erfahrung mit funktionaler Programmierung haben, insbesondere, weil es oft mit Teilanwendungen verwechselt wird. In diesem Beispiel sehen Sie beide bei der Arbeit:

let multiply x y = x * y    
let double = multiply 2
let ten = double 5

Sie sollten sofort ein Verhalten feststellen, das sich von den meisten zwingenden Sprachen unterscheidet. Die zweite Anweisung erstellt eine neue Funktion mit dem Namen double, indem ein Argument an eine Funktion übergeben wird, die zwei benötigt. Das Ergebnis ist eine Funktion, die ein int-Argument akzeptiert und dieselbe Ausgabe liefert, als hätten Sie multiplizieren mit x gleich 2 und y gleich diesem Argument aufgerufen. In Bezug auf das Verhalten ist es dasselbe wie der folgende Code:

let double2 z = multiply 2 z

Oft wird fälschlicherweise behauptet, Multiplikation würde zu Double verarbeitet. Dies ist aber nur ein wenig wahr. Die Multiplikationsfunktion wird gewechselt, dies geschieht jedoch, wenn sie definiert ist, da Funktionen in F # standardmäßig gewechselt werden. Wenn die Doppelfunktion erstellt wird, ist es genauer zu sagen, dass die Multiplikationsfunktion teilweise angewendet wird.

Die Multiplikationsfunktion ist wirklich eine Reihe von zwei Funktionen. Die erste Funktion akzeptiert ein int-Argument und gibt eine andere Funktion zurück, die x effektiv an einen bestimmten Wert bindet. Diese Funktion akzeptiert auch ein int-Argument, das Sie sich als den Wert vorstellen können, der an y gebunden werden soll. Nach dem Aufruf dieser zweiten Funktion sind x und y beide gebunden, so dass das Ergebnis das Produkt von x und y ist, wie es im Körper von double definiert ist.

Um double zu erzeugen, wird die erste Funktion in der Kette der Multiplikationsfunktionen ausgewertet, um die Multiplikation teilweise anzuwenden. Die resultierende Funktion erhält den Namen double. Wenn double ausgewertet wird, verwendet es sein Argument zusammen mit dem teilweise angewendeten Wert, um das Ergebnis zu erstellen.

48
dodgy_coder

Interessante Frage. Nach einigem Suchen gab "Partial Function Application is not currying" die beste Erklärung, die ich gefunden habe. Ich kann nicht sagen, dass der praktische Unterschied für mich besonders offensichtlich ist, aber dann bin ich kein FP Experte...

Eine weitere nützliche Seite (die ich zugegebenermaßen noch nicht vollständig gelesen habe) ist "Currying and Partial Application with Java Closures" .

Es sieht so aus, als ob dies ein weithin verwirrtes Begriffspaar ist, wohlgemerkt.

29
Jon Skeet

Ich habe dies in einem anderen Thread beantwortet https://stackoverflow.com/a/12846865/1685865 . Kurz gesagt, bei der Teilfunktionsanwendung geht es darum, einige Argumente einer gegebenen multivariablen Funktion zu korrigieren, um eine andere Funktion mit weniger Argumenten zu erhalten, während es bei Currying darum geht, eine Funktion von N Argumenten in eine unäre Funktion umzuwandeln, die eine unäre Funktion zurückgibt ... [Ein Beispiel für Curry wird am Ende dieses Beitrags angezeigt.]

Das Currying ist meistens von theoretischem Interesse: Man kann Berechnungen nur mit unären Funktionen ausdrücken (d. H. , jede Funktion ist unär). In der Praxis und als Nebenprodukt ist es eine Technik, die viele nützliche (aber nicht alle) Teilfunktionsanwendungen trivial machen kann, wenn die Sprache über Curry-Funktionen verfügt. Auch hier ist es nicht das einzige Mittel, Teilanwendungen zu implementieren. Sie könnten also auf Szenarien stoßen, in denen eine teilweise Anwendung auf andere Weise erfolgt, die Leute dies jedoch als Currying verwechseln.

(Beispiel für Currying)

In der Praxis würde man nicht einfach schreiben

lambda x: lambda y: lambda z: x + y + z

oder das entsprechende Javascript

function (x) { return function (y){ return function (z){ return x + y + z }}}

anstatt

lambda x, y, z: x + y + z

um des Currys willen.

13
Ji Han

Currying ist eine Funktion von einem Argument, das eine Funktion f annimmt und eine neue Funktion h zurückgibt. Beachten Sie, dass h ein Argument von X nimmt und eine Funktion zurückgibt, die Y auf Z abbildet:

curry(f) = h 
f: (X x Y) -> Z 
h: X -> (Y -> Z)

Teilanwendung ist eine Funktion von zwei (oder mehr) Argumenten, die eine Funktion f und ein oder mehrere zusätzliche Argumente zu f und gibt eine neue Funktion g zurück:

part(f, 2) = g
f: (X x Y) -> Z 
g: Y -> Z

Die Verwirrung entsteht, weil bei einer Zwei-Argument-Funktion die folgende Gleichheit gilt:

partial(f, a) = curry(f)(a)

Beide Seiten ergeben die gleiche Einargumentfunktion.

Die Gleichheit gilt nicht für Funktionen mit höherer Arität, da in diesem Fall beim Ausführen eine Funktion mit einem Argument zurückgegeben wird, während bei teilweiser Anwendung eine Funktion mit mehreren Argumenten zurückgegeben wird.

Der Unterschied liegt auch im Verhalten, während beim Currying die gesamte ursprüngliche Funktion rekursiv transformiert wird (einmal für jedes Argument), ist die teilweise Anwendung nur eine Ersetzung in einem Schritt.

Quelle: Wikipedia Currying .

6
Roland

Der Unterschied zwischen Curry und Teilapplikation lässt sich am besten anhand des folgenden JavaScript-Beispiels veranschaulichen:

function f(x, y, z) {
    return x + y + z;
}

var partial = f.bind(null, 1);

6 === partial(2, 3);

Eine teilweise Anwendung führt zu einer Funktion mit geringerer Arität; Im obigen Beispiel hat f eine Arität von 3, während partial nur eine Arität von 2 hat. Noch wichtiger ist, dass eine teilweise angewendete Funktion das Ergebnis zurückgeben würde sofort nach dem Aufruf , keine weitere Funktion in der laufenden Kette. Wenn Sie also etwas wie partial(2)(3) sehen, handelt es sich in Wirklichkeit nicht um eine teilweise Anwendung.

Weitere Lektüre:

5
gsklee

Einfache Antwort

Curry: ruft eine Funktion auf, teilt sie in mehrere Aufrufe auf und liefert ein Argument pro Aufruf.

Partial: lässt Sie eine Funktion aufrufen, indem Sie sie in mehrere Aufrufe aufteilen und pro Aufruf mehrere Argumente bereitstellen.


Einfache Hinweise

Mit beiden können Sie eine Funktion aufrufen, die weniger Argumente liefert (oder besser kumulativ liefert). Tatsächlich binden beide (bei jedem Aufruf) einen bestimmten Wert an bestimmte Argumente der Funktion.

Der wirkliche Unterschied ist erkennbar, wenn die Funktion mehr als 2 Argumente enthält.


Einfaches e (c) (Beispiel)

(in Javascript)

function process(context, success_callback, error_callback, subject) {...}

warum immer die Argumente wie den Kontext und die Rückrufe übergeben, wenn sie immer gleich sind? Binden Sie einfach einige Werte für die Funktion

processSubject = _.partial(process, my_context, my_success, my_error)

und rufe es auf subject1 und foobar mit auf

processSubject('subject1');
processSubject('foobar');

Bequem, nicht wahr? ????

Mit currying müssten Sie jeweils ein Argument übergeben

curriedProcess = _.curry(process);
processWithBoundedContext = curriedProcess(my_context);
processWithCallbacks = processWithBoundedContext(my_success)(my_error); // note: these are two sequential calls

result1 = processWithCallbacks('subject1');
// same as: process(my_context, my_success, my_error, 'subject1');
result2 = processWithCallbacks('foobar'); 
// same as: process(my_context, my_success, my_error, 'foobar');

Haftungsausschluss

Ich habe alle akademischen/mathematischen Erklärungen übersprungen. Weil ich es nicht weiß. Vielleicht hat es geholfen ????

2
Kamafeather

Ich könnte mich hier sehr irren, da ich keinen ausgeprägten Hintergrund in theoretischer Mathematik oder funktionaler Programmierung habe, aber aus meinem kurzen Streifzug in FP scheint es, dass das Currying dazu neigt, eine Funktion von N Argumenten in N Funktionen eines Arguments umzuwandeln. Während eine teilweise Anwendung [in der Praxis] mit variablen Funktionen mit einer unbestimmten Anzahl von Argumenten besser funktioniert. Ich weiß, dass einige der Beispiele in früheren Antworten dieser Erklärung widersprechen, aber es hat mir am meisten geholfen, die Konzepte zu trennen. Betrachten Sie dieses Beispiel (geschrieben in CoffeeScript für Prägnanz, ich entschuldige mich, wenn es weiter verwirrt, aber bitten Sie um Klarstellung, falls erforderlich):

# partial application
partial_apply = (func) ->
  args = [].slice.call arguments, 1
  -> func.apply null, args.concat [].slice.call arguments

sum_variadic = -> [].reduce.call arguments, (acc, num) -> acc + num

add_to_7_and_5 = partial_apply sum_variadic, 7, 5

add_to_7_and_5 10 # returns 22
add_to_7_and_5 10, 11, 12 # returns 45

# currying
curry = (func) ->
  num_args = func.length
  helper = (prev) ->
    ->
      args = prev.concat [].slice.call arguments
      return if args.length < num_args then helper args else func.apply null, args
  helper []

sum_of_three = (x, y, z) -> x + y + z
curried_sum_of_three = curry sum_of_three
curried_sum_of_three 4 # returns a function expecting more arguments
curried_sum_of_three(4)(5) # still returns a function expecting more arguments
curried_sum_of_three(4)(5)(6) # returns 15
curried_sum_of_three 4, 5, 6 # returns 15

Dies ist natürlich ein erfundenes Beispiel, aber beachten Sie, dass die teilweise Anwendung einer Funktion, die eine beliebige Anzahl von Argumenten akzeptiert, es uns ermöglicht, eine Funktion mit einigen vorläufigen Daten auszuführen. Das Ausführen einer Funktion ist ähnlich, ermöglicht es uns jedoch, eine N-Parameter-Funktion in Teilen auszuführen, bis alle N-Parameter berücksichtigt werden.

Auch dies ist meine Ansicht von Dingen, die ich gelesen habe. Wenn jemand anderer Meinung ist, würde ich mich über einen Kommentar darüber freuen, warum und nicht über eine sofortige Ablehnung. Wenn das CoffeeScript schwer zu lesen ist, besuchen Sie bitte coffeescript.org, klicken Sie auf "try coffeescript" und fügen Sie meinen Code ein, um die kompilierte Version zu sehen, die (hoffentlich) sinnvoller sein kann. Vielen Dank!

2
sunny-mittal

Ich hatte diese Frage beim Lernen sehr oft und wurde sie seitdem oft gestellt. Der einfachste Weg, den ich beschreiben kann, ist, dass beide gleich sind :) Lassen Sie mich erklären ... es gibt offensichtlich Unterschiede.

Sowohl Teilanwendung als auch Curry erfordern die Angabe von Argumenten für eine Funktion, möglicherweise nicht alle auf einmal. Ein ziemlich kanonisches Beispiel ist das Hinzufügen von zwei Zahlen. Im Pseudocode (tatsächlich JS ohne Schlüsselwörter) kann die Basisfunktion die folgende sein:

add = (x, y) => x + y

Wenn ich eine "addOne" -Funktion haben möchte, könnte ich sie teilweise anwenden oder curry:

addOneC = curry(add, 1)
addOneP = partial(add, 1)

Jetzt ist die Verwendung klar:

addOneC(2) #=> 3
addOneP(2) #=> 3

Was ist der Unterschied? Nun, es ist subtil, aber bei einer Teilanwendung müssen einige Argumente angegeben werden, und die zurückgegebene Funktion wird dann die Hauptfunktion beim nächsten Aufruf ausführen, wohingegen das Currying so lange wartet, bis alle erforderlichen Argumente vorliegen:

curriedAdd = curry(add) # notice, no args are provided
addOne = curriedAdd(1) # returns a function that can be used to provide the last argument
addOne(2) #=> returns 3, as we want

partialAdd = partial(add) # no args provided, but this still returns a function
addOne = partialAdd(1) # oops! can only use a partially applied function once, so now we're trying to add one to an undefined value (no second argument), and we get an error

Kurz gesagt, verwenden Sie eine Teilanwendung, um einige Werte vorab zu füllen, da Sie wissen, dass sie beim nächsten Aufruf der Methode ausgeführt wird und alle nicht bereitgestellten Argumente undefiniert bleiben. Verwenden Sie currying, wenn Sie eine teilweise angewendete Funktion so oft wie nötig zurückgeben möchten, um die Funktionssignatur zu erfüllen. Ein letztes ausgedachtes Beispiel:

curriedAdd = curry(add)
curriedAdd()()()()()(1)(2) # ugly and dumb, but it works

partialAdd = partial(add)
partialAdd()()()()()(1)(2) # second invocation of those 7 calls fires it off with undefined parameters

Hoffe das hilft!

UPDATE: Einige Sprachen oder Bibliotheksimplementierungen ermöglichen es Ihnen, eine Arität (Gesamtanzahl der Argumente in der abschließenden Bewertung) an die teilweise Anwendungsimplementierung zu übergeben, die meine beiden Beschreibungen möglicherweise zu einem verwirrenden Durcheinander zusammenführt weitgehend austauschbar.

2
sunny-mittal

Für mich muss partielle Anwendung eine neue Funktion erstellen, bei der die verwendeten Argumente vollständig in die resultierende Funktion integriert werden.

Die meisten funktionalen Sprachen implementieren das Currying durch Zurücksenden eines Abschlusses: Bei teilweiser Anwendung nicht unter Lambda auswerten. Damit eine Teilanwendung interessant ist, müssen wir zwischen Currying und Teilanwendung unterscheiden und Teilanwendung als Currying plus Bewertung unter Lambda betrachten.

2

Es gibt hier noch andere gute Antworten, aber ich glaube, dass dieses Beispiel (nach meinem Verständnis) in Java für einige Leute von Nutzen sein könnte:

public static <A,B,X> Function< B, X > partiallyApply( BiFunction< A, B, X > aBiFunction, A aValue ){
    return b -> aBiFunction.apply( aValue, b );
}

public static <A,X> Supplier< X > partiallyApply( Function< A, X > aFunction, A aValue ){
    return () -> aFunction.apply( aValue );
}

public static <A,B,X> Function<  A, Function< B, X >  > curry( BiFunction< A, B, X > bif ){
    return a -> partiallyApply( bif, a );
}

Beim Currying erhalten Sie eine Ein-Argument-Funktion zum Erstellen von Funktionen, bei der eine Teilanwendung eine Wrapper-Funktion erstellt, die ein oder mehrere Argumente fest codiert.

Wenn Sie kopieren und einfügen möchten, ist Folgendes lauter, aber benutzerfreundlicher, da die Typen milder sind:

public static <A,B,X> Function< ? super B, ? extends X > partiallyApply( final BiFunction< ? super A, ? super B, X > aBiFunction, final A aValue ){
    return b -> aBiFunction.apply( aValue, b );
}

public static <A,X> Supplier< ? extends X > partiallyApply( final Function< ? super A, X > aFunction, final A aValue ){
    return () -> aFunction.apply( aValue );
}

public static <A,B,X> Function<  ? super A,  Function< ? super B, ? extends X >  > curry( final BiFunction< ? super A, ? super B, ? extends X > bif ){
    return a -> partiallyApply( bif, a );
}
1
Tobogganski

Beim Schreiben verwechselte ich das Curry und das Entcurren. Sie sind inverse Transformationen von Funktionen. Es ist wirklich egal, was Sie welche nennen, solange Sie wissen, was die Transformation und ihre Umkehrung darstellen.

Uncurrying ist nicht sehr klar definiert (oder vielmehr gibt es "widersprüchliche" Definitionen, die alle den Geist der Idee widerspiegeln). Grundsätzlich bedeutet dies, dass eine Funktion, die mehrere Argumente akzeptiert, in eine Funktion umgewandelt wird, die nur ein einziges Argument akzeptiert. Beispielsweise,

(+) :: Int -> Int -> Int

Wie wandeln Sie dies in eine Funktion um, die ein einziges Argument akzeptiert? Du betrügst natürlich!

plus :: (Int, Int) -> Int

Beachten Sie, dass plus jetzt ein einziges Argument akzeptiert (das aus zwei Dingen besteht). Super!

Was ist der Sinn davon? Wenn Sie eine Funktion mit zwei Argumenten und zwei Argumenten haben, ist es gut zu wissen, dass Sie die Funktion auf die Argumente anwenden können und trotzdem das erhalten, was Sie erwarten. Tatsächlich gibt es bereits die entsprechenden Installationsanwendungen, sodass Sie keine expliziten Mustervergleiche durchführen müssen. Alles was du tun musst, ist:

(uncurry (+)) (1,2)

Was ist eine Teilfunktionsanwendung? Es ist eine andere Art, eine Funktion in zwei Argumenten in eine Funktion mit einem Argument umzuwandeln. Es funktioniert jedoch anders. Nehmen wir noch einmal (+) als Beispiel. Wie können wir daraus eine Funktion machen, die ein einzelnes Int als Argument verwendet? Wir betrügen!

((+) 0) :: Int -> Int

Das ist die Funktion, die jedem Int null hinzufügt.

((+) 1) :: Int -> Int

addiert 1 zu einem beliebigen Int. In jedem dieser Fälle wird (+) "teilweise angewendet".

0
nomen

Ich gehe davon aus, dass die meisten Leute, die diese Frage stellen, bereits mit den grundlegenden Konzepten vertraut sind, sodass sie nicht mehr darüber sprechen müssen. Die Überlappung ist der verwirrende Teil.

Möglicherweise können Sie die Konzepte vollständig nutzen, aber Sie verstehen sie zusammen als diese pseudo-atomare amorphe konzeptuelle Unschärfe. Was fehlt, ist zu wissen, wo die Grenze zwischen ihnen ist.

Anstatt zu definieren, was jedes ist, ist es einfacher, nur die Unterschiede hervorzuheben - die Grenze.

Currying ist, wenn Sie definieren die Funktion.

Teilanwendung ist, wenn Sie Aufruf die Funktion.

Anwendung ist mathematisch gesprochen, um eine Funktion aufzurufen.

Partial Die Anwendung erfordert den Aufruf einer Curry-Funktion und das Abrufen einer Funktion als Rückgabetyp.

0
Brennan Cheung