829 Stimmen

Was ist "Currying"?

Ich habe in mehreren Artikeln und Blogs Hinweise auf Curried-Funktionen gesehen, aber ich kann keine gute Erklärung finden (oder zumindest eine, die Sinn macht!)

13 Stimmen

[Gemäß der Definition einer kartesischen geschlossenen Kategorie gibt es eine fest Familie von Adjunktionen (natürlich parametrisiert durch A) zwischen X -> X x A und X -> X ^ A. Die Isomorphismen hom(X x A, Y) <-> hom(X, Y^A) sind die curry y uncurry Funktionen von Haskell. Wichtig ist hier, dass diese Isomorphismen von vornherein festgelegt und somit in die Sprache "eingebaut" sind.

3 Stimmen

Es gibt ein schönes Tutorial hier für currying in haskell learnyouahaskell.com/higher-order-functions#curried-functions Kurzkommentare sind, dass add x y = x+y (curried) ist anders als add (x, y)=x+y (ohne Eile)

1107voto

Kyle Cronin Punkte 74993

Currying bedeutet, dass man eine Funktion, die mehrere Argumente benötigt, in eine Reihe von Funktionen aufteilt, die jeweils nur ein Argument benötigen. Hier ist ein Beispiel in JavaScript:

function add (a, b) {
  return a + b;
}

add(3, 4); // returns 7

Dies ist eine Funktion, die zwei Argumente, a und b, annimmt und deren Summe zurückgibt. Wir werden diese Funktion nun kurieren:

function add (a) {
  return function (b) {
    return a + b;
  }
}

Dies ist eine Funktion, die ein Argument benötigt, a und gibt eine Funktion zurück, die ein weiteres Argument benötigt, b und diese Funktion gibt deren Summe zurück.

add(3)(4);

var add3 = add(3);

add3(4);

Die erste Anweisung gibt 7 zurück, wie die Anweisung add(3, 4) Erklärung. Die zweite Anweisung definiert eine neue Funktion namens add3 die zu ihrem Argument 3 hinzufügt. (Das ist es, was manche eine Schließung nennen.) Die dritte Anweisung verwendet die add3 Operation, um 3 zu 4 zu addieren, was wiederum 7 als Ergebnis ergibt.

327 Stimmen

Wie kann ich dieses Konzept in der Praxis nutzen?

61 Stimmen

@Strawberry, nehmen wir an, Sie haben eine Liste von Zahlen in einer [1, 2, 3, 4, 5] die Sie mit einer beliebigen Zahl multiplizieren möchten. In Haskell kann ich schreiben map (* 5) [1, 2, 3, 4, 5] die gesamte Liste zu multiplizieren mit 5 und erzeugt so die Liste [5, 10, 15, 20, 25] .

93 Stimmen

Ich verstehe, was die Kartenfunktion tut, aber ich bin mir nicht sicher, ob ich den Punkt verstehe, den Sie mir zu verdeutlichen versuchen. Wollen Sie damit sagen, dass die map-Funktion das Konzept des Currys darstellt?

145voto

Alex Martelli Punkte 805329

In einer Algebra der Funktionen ist der Umgang mit Funktionen, die mehrere Argumente annehmen (oder ein äquivalentes Argument, das ein N-Tupel ist), etwas unelegant -- aber, wie Moses Schönfinkel (und unabhängig davon Haskell Curry) bewiesen hat, ist das nicht nötig: alles was man braucht, sind Funktionen, die ein Argument annehmen.

Wie gehen Sie also mit etwas um, das Sie ganz natürlich ausdrücken würden, wie zum Beispiel, f(x,y) ? Nun, Sie nehmen das als gleichwertig mit f(x)(y) -- f(x) nennen Sie es g ist eine Funktion, und Sie wenden diese Funktion auf y . Mit anderen Worten, Sie haben nur Funktionen, die ein Argument benötigen - aber einige dieser Funktionen geben andere Funktionen zurück (die ebenfalls ein Argument benötigen;-).

Wie immer, wikipedia hat einen schönen zusammenfassenden Eintrag dazu, mit vielen nützlichen Hinweisen (wahrscheinlich auch zu Ihren Lieblingssprachen;-) sowie einer etwas strengeren mathematischen Behandlung.

1 Stimmen

Ich vermute, dass ich ähnliche Bemerkungen wie oben gemacht habe - ich habe nicht gesehen, dass funktionale Sprachen Funktionen auf ein einziges Argument beschränken. Irre ich mich?

1 Stimmen

@hoohoo: Funktionale Sprachen beschränken Funktionen nicht generell auf ein einziges Argument. Auf einer niedrigeren, mathematischeren Ebene ist es jedoch viel einfacher, mit Funktionen umzugehen, die nur ein Argument annehmen. (Im Lambda-Kalkül zum Beispiel nehmen Funktionen jeweils nur ein Argument an.)

1 Stimmen

GUT. Dann eine andere Frage. Ist die folgende Aussage wahr? Lambda-Kalkül kann als Modell für funktionale Programmierung verwendet werden, aber funktionale Programmierung ist nicht unbedingt angewandtes Lambda-Kalkül.

124voto

Shea Daniels Punkte 3136

Hier ein konkretes Beispiel:

Angenommen, Sie haben eine Funktion, die die auf ein Objekt wirkende Gravitationskraft berechnet. Wenn Sie die Formel nicht kennen, können Sie sie finden aquí . Diese Funktion nimmt die drei erforderlichen Parameter als Argumente auf.

Da Sie sich auf der Erde befinden, wollen Sie nur Kräfte für Objekte auf diesem Planeten berechnen. In einer funktionalen Sprache könnten Sie die Masse der Erde an die Funktion übergeben und sie dann teilweise auswerten. Was Sie zurückbekommen würden, ist eine andere Funktion, die nur zwei Argumente benötigt und die Gravitationskraft von Objekten auf der Erde berechnet. Dies wird als Currying bezeichnet.

3 Stimmen

Als Kuriosität bietet die Prototype-Bibliothek für JavaScript eine "curry"-Funktion, die ziemlich genau das tut, was Sie hier erklärt haben: prototypejs.org/api/funktion/curry

0 Stimmen

11 Stimmen

Das klingt für mich nach einer Teilanwendung. Meinem Verständnis nach kann man, wenn man Currying anwendet, Funktionen mit einem einzigen Argument erstellen und sie zu komplizierteren Funktionen zusammensetzen. Übersehe ich etwas?

107voto

Adzz Punkte 1189

Es kann eine Möglichkeit sein, Funktionen zu verwenden, um andere Funktionen zu erstellen.

In Javascript:

let add = function(x){
  return function(y){ 
   return x + y
  };
};

würde uns erlauben, es so zu nennen:

let addTen = add(10);

Wenn dies geschieht, wird die 10 wird übergeben als x ;

let add = function(10){
  return function(y){
    return 10 + y 
  };
};

was bedeutet, dass wir diese Funktion zurückbekommen:

function(y) { return 10 + y };

Wenn Sie also anrufen

 addTen();

Sie rufen wirklich an:

 function(y) { return 10 + y };

Wenn Sie dies also tun:

 addTen(4)

ist es dasselbe wie:

function(4) { return 10 + 4} // 14

Also unser addTen() addiert immer zehn zu dem, was wir eingeben. Wir können ähnliche Funktionen auf dieselbe Weise erstellen:

let addTwo = add(2)       // addTwo(); will add two to whatever you pass in
let addSeventy = add(70)  // ... and so on...

Die offensichtliche Folgefrage ist nun, warum in aller Welt Sie das überhaupt tun wollen? Es verwandelt das, was eine eifrige Operation war x + y in ein System verwandeln, durch das man einfach hindurchgehen kann, was bedeutet, dass wir mindestens zwei Dinge tun können 1. teure Operationen zwischenspeichern 2. Abstraktionen im funktionalen Paradigma erreichen.

Stellen Sie sich vor, unsere Curry-Funktion sähe so aus:

let doTheHardStuff = function(x) {
  let z = doSomethingComputationallyExpensive(x)
  return function (y){
    z + y
  }
}

Wir könnten diese Funktion einmal aufrufen und dann das Ergebnis weitergeben, damit es an vielen Stellen verwendet werden kann, was bedeutet, dass wir die rechenintensiven Aufgaben nur einmal erledigen:

let finishTheJob = doTheHardStuff(10)
finishTheJob(20)
finishTheJob(30)

Wir können Abstraktionen auf ähnliche Weise erhalten.

27 Stimmen

Die beste Schritt-für-Schritt-Erklärung eines inhärent sequentiellen Prozesses, die ich hier gesehen habe, und vielleicht die beste, erklärendste Antwort von allen.

7 Stimmen

@jonsilver Ich würde das Gegenteil sagen, keine gute Erklärung. Ich stimme zu, dass es gut ist, das Beispiel zu erklären, aber die Leute neigen dazu, zu denken: "Ja, das ist klar, aber ich hätte das Gleiche auch anders machen können, also wozu soll das Currying gut sein?" Mit anderen Worten, ich wünschte, es gäbe gerade genug Kontext oder Erklärung, um nicht nur zu erklären, wie Currying funktioniert, sondern auch, warum es im Vergleich zu anderen Möglichkeiten, zehn zu addieren, keine nutzlose und triviale Beobachtung ist.

5 Stimmen

Die ursprüngliche Frage lautete: "Was ist es?", nicht: "Warum ist es nützlich?

50voto

ljs Punkte 35909

Currying ist eine Transformation, die auf Funktionen angewandt werden kann, damit sie ein Argument weniger als bisher annehmen können.

In F# können Sie zum Beispiel eine Funktion folgendermaßen definieren:-

let f x y z = x + y + z

Hier nimmt die Funktion f die Parameter x, y und z und summiert sie so auf:-

f 1 2 3

Rückgabe 6.

Aus unserer Definition können wir also die Curry-Funktion für f definieren:-

let curry f = fun x -> f x

Dabei ist 'fun x -> f x' eine Lambda-Funktion, die in C# gleichbedeutend mit x => f(x) ist. Diese Funktion gibt die gewünschte Funktion ein und gibt eine Funktion zurück, die nimmt ein einziges Argument und gibt die angegebene Funktion zurück, wobei das erste Argument auf das Eingabeargument gesetzt ist.

Anhand unseres vorherigen Beispiels können wir einen Curry von f wie folgt erhalten:-

let curryf = curry f

Wir können dann Folgendes tun:-

let f1 = curryf 1

Damit erhalten wir eine Funktion f1, die gleich ist mit f1 y z = 1 + y + z. Das bedeutet, dass wir Folgendes tun können:-

f1 2 3

Das ergibt 6.

Dieser Prozess wird oft mit der "Anwendung von Teilfunktionen" verwechselt, die folgendermaßen definiert werden kann:-

let papply f x = f x

Wir können es jedoch auf mehr als einen Parameter ausdehnen, d.h.:-

let papply2 f x y = f x y
let papply3 f x y z = f x y z
etc.

Eine partielle Anwendung nimmt die Funktion und den/die Parameter und gibt eine Funktion zurück, die einen oder mehrere Parameter weniger benötigt. Wie die beiden vorangegangenen Beispiele zeigen, ist diese Funktion direkt in der Standard-F#-Funktionsdefinition implementiert, so dass wir das vorherige Ergebnis folgendermaßen erreichen können:-

let f1 = f 1
f1 2 3

Das Ergebnis ist 6.

Zusammenfassend:-

Der Unterschied zwischen Currying und partieller Funktionsanwendung besteht darin, dass:-

Currying nimmt eine Funktion und stellt eine neue Funktion bereit, die ein einziges Argument akzeptiert und die angegebene Funktion zurückgibt, wobei ihr erstes Argument auf dieses Argument gesetzt wird. So können wir Funktionen mit mehreren Parametern als eine Reihe von Funktionen mit einem Argument darstellen . Beispiel:-

let f x y z = x + y + z
let curryf = curry f
let f1 = curryf 1
let f2 = curryf 2
f1 2 3
6
f2 1 3
6

Die Anwendung einer Teilfunktion ist direkter - sie nimmt eine Funktion und ein oder mehrere Argumente und gibt eine Funktion zurück, bei der die ersten n Argumente auf die angegebenen n Argumente gesetzt sind. Beispiel:-

let f x y z = x + y + z
let f1 = f 1
let f2 = f 2
f1 2 3
6
f2 1 3
6

0 Stimmen

Methoden in C# müssten also erst "curried" werden, bevor sie teilweise angewendet werden können?

0 Stimmen

"Dies ermöglicht es uns, Funktionen mit mehreren Parametern als eine Reihe von Funktionen mit einem einzigen Argument darzustellen" - perfekt, das hat alles für mich geklärt. Danke

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