497 Stimmen

Was ist der Unterschied zwischen Currying und partieller Anwendung?

Ich erlebe im Internet häufig, dass sich andere darüber beschweren, dass ihre Beispiele für das Striegeln gar kein Striegeln sind, sondern nur eine teilweise Anwendung.

Ich habe keine vernünftige Erklärung dafür gefunden, was partielle Anwendung ist oder wie sie sich vom Curry unterscheidet. Es scheint eine allgemeine Verwirrung zu herrschen, wobei gleichwertige Beispiele an einigen Stellen als Currying und an anderen als Partial Application bezeichnet werden.

Könnte mir jemand eine Definition der beiden Begriffe geben und erläutern, wie sie sich unterscheiden?

4voto

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

Die meisten funktionalen Sprachen implementieren Currying, indem sie eine Closure zurückgeben: Sie evaluieren nicht unter Lambda, wenn sie teilweise angewendet werden. Damit die partielle Anwendung interessant ist, müssen wir also einen Unterschied zwischen Currying und partieller Anwendung machen und die partielle Anwendung als Currying plus Auswertung unter Lambda betrachten.

4voto

sunny-mittal Punkte 467

Ich könnte mich hier sehr irren, da ich keinen starken Hintergrund in theoretischer Mathematik oder funktionaler Programmierung habe, aber aus meinem kurzen Streifzug durch FP scheint es, dass Currying dazu neigt, eine Funktion mit N Argumenten in N Funktionen mit einem Argument zu verwandeln, während partielle Anwendung [in der Praxis] besser mit variablen Funktionen mit einer unbestimmten Anzahl von Argumenten 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 (der Kürze halber in CoffeeScript geschrieben, ich entschuldige mich, wenn es noch mehr Verwirrung stiftet, aber bitte fragen Sie nach, wenn Sie eine Klarstellung benötigen):

# 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 auszuführen, aber mit einigen vorläufigen Daten. Eine Funktion zu kuratieren ist ähnlich, ermöglicht es aber, eine Funktion mit N Parametern stückweise auszuführen, bis alle N Parameter berücksichtigt sind, aber nur solange.

Nochmals, das ist meine Meinung nach dem, was ich gelesen habe. Wenn jemand anderer Meinung ist, würde ich mich über einen Kommentar freuen und nicht über ein sofortiges Downvote. 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) mehr Sinn ergibt. Vielen Dank!

4voto

Brennan Cheung Punkte 3403

Ich gehe davon aus, dass die meisten Leute, die diese Frage stellen, bereits mit den grundlegenden Konzepten vertraut sind, so dass es nicht notwendig ist, darüber zu sprechen. Es sind die Überschneidungen, die verwirrend sind.

Sie sind vielleicht in der Lage, die Konzepte in vollem Umfang zu nutzen, aber Sie verstehen sie zusammen als diese pseudo-atomare, amorphe Begriffsunschärfe. Was fehlt, ist das Wissen, wo die Grenze zwischen ihnen verläuft.

Anstatt zu definieren, was jeder einzelne von ihnen ist, ist es einfacher, nur ihre Unterschiede hervorzuheben - die Grenze.

Currying ist, wenn Sie definieren. die Funktion.

Teilweise Anwendung ist, wenn Sie aufrufen die Funktion.

Anmeldung ist der mathematische Ausdruck für den Aufruf einer Funktion.

Teilweise Anwendung erfordert den Aufruf einer Curried-Funktion und den Erhalt einer Funktion als Rückgabetyp.

3voto

Sled Punkte 17564

Es gibt noch andere gute Antworten hier, 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 );
}

Mit Currying erhalten Sie also eine Ein-Argument-Funktion zum Erstellen von Funktionen, während die partielle Anwendung eine Wrapper-Funktion erzeugt, die ein oder mehrere Argumente fest kodiert.

Wenn Sie kopieren und einfügen möchten, ist die folgende Version zwar lauter, aber auch angenehmer zu handhaben, da die Typen nachsichtiger 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 );
}

1voto

nomen Punkte 3526

Beim Schreiben dieses Artikels habe ich "curry" und "uncurry" verwechselt. Sie sind inverse Transformationen von Funktionen. Es ist wirklich egal, wie man was nennt, solange man versteht, was die Transformation und ihre Umkehrung darstellen.

Uncurrying ist nicht sehr klar definiert (oder besser gesagt, es gibt "widersprüchliche" Definitionen, die alle den Geist der Idee erfassen). Im Grunde bedeutet es, dass eine Funktion, die mehrere Argumente benötigt, in eine Funktion umgewandelt wird, die nur ein einziges Argument benötigt. Zum Beispiel,

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

Wie macht man nun daraus eine Funktion, die ein einziges Argument benötigt? Man schummelt natürlich!

plus :: (Int, Int) -> Int

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

Was ist der Sinn der Sache? Nun, wenn Sie eine Funktion haben, die zwei Argumente benötigt, und Sie haben ein Paar von Argumenten, ist es schön zu wissen, dass Sie die Funktion auf die Argumente anwenden können und trotzdem das bekommen, was Sie erwarten. Und in der Tat gibt es bereits die Möglichkeit, dies zu tun, so dass Sie keine Dinge wie explizite Mustervergleiche durchführen müssen. Alles, was Sie tun müssen, ist:

(uncurry (+)) (1,2)

Was also ist die Anwendung von Teilfunktionen? Es ist eine andere Art, eine Funktion mit zwei Argumenten in eine Funktion mit einem Argument zu verwandeln. Sie funktioniert allerdings anders. Nehmen wir wieder (+) als Beispiel. Wie können wir sie in eine Funktion umwandeln, die ein einziges Int als Argument hat? Wir schummeln!

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

Das ist die Funktion, die zu jedem Int eine Null addiert.

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

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X