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)

2voto

S2dent Punkte 901

Ein Beispiel für Currying wäre, wenn man Funktionen hat, von denen man im Moment nur einen der Parameter kennt:

Zum Beispiel:

func aFunction(str: String) {
    let callback = callback(str) // signature now is `NSData -> ()`
    performAsyncRequest(callback)
}

func callback(str: String, data: NSData) {
    // Callback code
}

func performAsyncRequest(callback: NSData -> ()) {
    // Async code that will call callback with NSData as parameter
}

Da Sie hier den zweiten Parameter für den Rückruf nicht kennen, wenn Sie ihn an performAsyncRequest(_:) müssten Sie einen weiteren Lambda / Closure erstellen, um diesen an die Funktion zu senden.

0 Stimmen

Es func callback selbst zurückkehren? Sie wird aufgerufen @ callback(str) así que let callback = callback(str) ist Callback nur der Rückgabewert von func callback

0 Stimmen

Nein, func callback(_:data:) akzeptiert zwei Parameter, hier gebe ich ihm nur einen, den String und wartet daher auf die nächste ( NSData ), deshalb jetzt let callback ist eine weitere Funktion, die auf die Übergabe von Daten wartet

2voto

catch23 Punkte 15336

Eine curried-Funktion wird auf mehrere Argumentlisten angewendet, anstatt nur auf eine.

Hier ist eine reguläre, nicht eilige Funktion, die zwei Int Parameter, x und y, hinzufügt:

scala> def plainOldSum(x: Int, y: Int) = x + y
plainOldSum: (x: Int,y: Int)Int
scala> plainOldSum(1, 2)
res4: Int = 3

Hier ist eine ähnliche Funktion, die mit einem Curry versehen ist. Stattdessen einer Liste mit zwei Int-Parametern, wenden Sie diese Funktion auf zwei Listen mit je einem Int-Parameter:

scala> def curriedSum(x: Int)(y: Int) = x + y
curriedSum: (x: Int)(y: Int)Intscala> second(2)
res6: Int = 3
scala> curriedSum(1)(2)
res5: Int = 3

Was hier passiert, ist, dass Sie beim Aufrufen von curriedSum erhalten Sie eigentlich zwei traditionelle Funktionsaufrufe hintereinander. Der erste Funktions nimmt einen einzelnen Int-Parameter namens x und gibt eine Funktion Wert für die zweite Funktion zurück. Diese zweite Funktion nimmt den Parameter Int y .

Hier ist eine Funktion namens first die im Geiste das tut, was die erste traditionelle Funktionsaufruf von curriedSum tun würde:

scala> def first(x: Int) = (y: Int) => x + y
first: (x: Int)(Int) => Int

Anwendung von 1 auf die erste Funktion - mit anderen Worten: Aufruf der ersten Funktion und Übergabe von 1 - ergibt die zweite Funktion:

scala> val second = first(1)
second: (Int) => Int = <function1>

Wendet man 2 auf die zweite Funktion an, erhält man das Ergebnis:

scala> second(2)
res6: Int = 3

1voto

V. S. Punkte 944

Hier finden Sie eine einfache Erklärung der Currying-Implementierung in C#. In den Kommentaren habe ich versucht zu zeigen, wie Currying nützlich sein kann:

public static class FuncExtensions {
    public static Func<T1, Func<T2, TResult>> Curry<T1, T2, TResult>(this Func<T1, T2, TResult> func)
    {
        return x1 => x2 => func(x1, x2);
    }
}

//Usage
var add = new Func<int, int, int>((x, y) => x + y).Curry();
var func = add(1);

//Obtaining the next parameter here, calling later the func with next parameter.
//Or you can prepare some base calculations at the previous step and then
//use the result of those calculations when calling the func multiple times 
//with different input parameters.

int result = func(1);

1voto

"Currying" ist der Prozess, bei dem eine Funktion mit mehreren Argumenten in eine Reihe von Funktionen umgewandelt wird, die jeweils ein einziges Argument annehmen und eine Funktion mit einem einzigen Argument zurückgeben, oder, im Fall der letzten Funktion, das eigentliche Ergebnis zurückgeben.

1voto

Mark Reed Punkte 85468

Die anderen Antworten haben erklärt, was Currying ist: Die Übergabe von weniger Argumenten an eine Curried-Funktion, als sie erwartet, ist kein Fehler, sondern gibt eine Funktion zurück, die den Rest der Argumente erwartet und das gleiche Ergebnis liefert, als wenn Sie alle Argumente auf einmal übergeben hätten.

Ich werde versuchen zu begründen, warum das nützlich ist. Es ist eines dieser Werkzeuge, von denen man nie wusste, dass man sie braucht, bis man sie braucht. Currying ist vor allem ein Weg, um Ihre Programme ausdrucksstärker zu machen - Sie können Operationen mit weniger Code miteinander kombinieren.

Wenn Sie zum Beispiel eine curried-Funktion haben add können Sie das Äquivalent von JS schreiben x => k + x (oder Python lambda x: k + x oder Rubin { |x| k + x } oder Lisp (lambda (x) (+ k x)) oder ) als nur add(k) . In Haskelll können Sie sogar den Operator verwenden: (k +) o (+ k) (Bei nicht-kommutativen Operatoren kann man mit den beiden Formen in beide Richtungen curryen: (/ 9) ist eine Funktion, die eine Zahl durch 9 dividiert, was wahrscheinlich der häufigste Anwendungsfall ist, aber es gibt auch (9 /) für eine Funktion, die 9 durch ihr Argument dividiert). Die Curried-Version ist nicht nur kürzer, sondern enthält auch keine erfundenen Parameternamen wie die x die in allen anderen Versionen zu finden sind. Das ist nicht nötig. Sie definieren eine Funktion, die eine Konstante k zu einer Zahl addiert, und Sie müssen dieser Zahl keinen Namen geben, nur um über die Funktion zu sprechen. Oder auch nur, um sie zu definieren. Dies ist ein Beispiel für das, was man als "punktfreien Stil" bezeichnet. Sie können Operationen miteinander kombinieren, wenn Sie nichts anderes als die Operationen selbst angeben. Man muss keine anonymen Funktionen deklarieren, die nichts anderes tun, als eine Operation auf ihr Argument anzuwenden, denn *das ist es, was die Operationen bereits sind.

Dies ist sehr praktisch bei Funktionen höherer Ordnung, wenn sie auf eine Curry-freundliche Weise definiert sind. Zum Beispiel kann eine curried map(fn, list) ermöglicht die Definition eines Mappers mit nur map(fn) die später auf jede Liste angewendet werden kann. Aber das Currying einer Map, die stattdessen als map(list, fn) ermöglicht es Ihnen lediglich, eine Funktion zu definieren, die eine andere Funktion auf eine konstante Liste anwendet, was im Allgemeinen wahrscheinlich weniger nützlich ist.

Currying reduziert die Notwendigkeit für Dinge wie Pipes und Threading. In Clojure könnte man eine Temperaturumwandlungsfunktion mit Hilfe des Threading-Makros definieren -> : (defn f2c (deg) (-> deg (- 32) (* 5) (/ 9)) . Das ist cool, es liest sich schön von links nach rechts ("32 subtrahieren, mit 5 multiplizieren und durch 9 dividieren") und man muss den Parameter nur zweimal statt einmal für jede Unteroperation erwähnen aber es funktioniert nur, weil -> ist ein Makro, das das gesamte Formular syntaktisch umwandelt, bevor etwas ausgewertet wird. Es verwandelt sich hinter den Kulissen in einen regulären verschachtelten Ausdruck: (/ (* (- deg 32) 5) 9) . Wenn die mathematischen Operationen kuriert wären, bräuchte man kein Makro, um sie so schön zu kombinieren, wie in Haskell let f2c = (subtract 32) & (* 5) & (/ 9) . (Obwohl es zugegebenermaßen idiomatischer wäre, die Funktionskomposition zu verwenden, die von rechts nach links gelesen wird: (/ 9) . (* 5) . (subtract 32) .)

Auch hier ist es schwer, gute Demo-Beispiele zu finden; Currying ist am nützlichsten in komplexen Fällen, in denen es die Lesbarkeit der Lösung wirklich verbessert, aber diese erfordern so viele Erklärungen, nur um das Problem zu verstehen, dass die allgemeine Lektion über Currying im Lärm untergehen kann.

1 Stimmen

Sie haben viele Beispiele für das Wie gegeben, aber kein einziges gutes Argument für das Warum. Könnten Sie diesen Punkt näher erläutern, denn ich glaube, darauf haben Sie zu Beginn Ihres Beitrags angespielt?

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