JavaScript-Curry: Was sind die praktischen Anwendungen?


172

Ich glaube nicht, dass ich schon Curry gemacht habe. Ich verstehe, was es tut und wie es geht. Ich kann mir einfach keine Situation vorstellen, in der ich sie nutzen würde.

Wo verwenden Sie Currying in JavaScript (oder wo verwenden die Hauptbibliotheken es)? Beispiele für DOM-Manipulationen oder allgemeine Anwendungsentwicklungen sind willkommen.

Eine der Antworten erwähnt Animation. Funktionen wie slideUp, fadeInnehmen ein Element als Argumente und gibt normalerweise ein curried Funktion die Funktion höherer Ordnung mit der Default „Animationsfunktion“ built-in zurückkehrt. Warum ist das besser, als nur die übergeordnete Funktion mit einigen Standardeinstellungen anzuwenden?

Gibt es irgendwelche Nachteile bei der Verwendung?

Wie hier angefordert, gibt es einige gute Ressourcen zum JavaScript-Currying:

Ich werde mehr hinzufügen, wenn sie in den Kommentaren auftauchen.


Entsprechend den Antworten sind Currying und teilweise Anwendung im Allgemeinen Bequemlichkeitstechniken.

Wenn Sie eine übergeordnete Funktion häufig „verfeinern“, indem Sie sie mit derselben Konfiguration aufrufen, können Sie die übergeordnete Funktion curry (oder Resigs Teilfunktion verwenden), um einfache, präzise Hilfsmethoden zu erstellen.


Können Sie einer Ressource einen Link hinzufügen, der beschreibt, was JS-Currying ist? Ein Tutorial oder ein Blog-Beitrag wäre toll.
Eric Schoonover

2
svendtofte.com ist langwierig, aber wenn Sie den gesamten Abschnitt von "Ein Crashkurs in ML" überspringen und erneut bei "Wie schreibe ich Curry-JavaScript?" beginnen, wird dies eine großartige Einführung in das Currying in js.
Danio

1
Dies ist ein guter Ausgangspunkt, um zu verstehen, was Curry und Teilanwendung wirklich ist: slid.es/gsklee/functional-programming-in-5-minutes
gsklee

1
Der Link zu svendtofte.comsieht tot aus - gefunden auf dem WayBack-Computer unter web.archive.org/web/20130616230053/http://www.svendtofte.com/… Entschuldigung, blog.morrisjohns.com/javascript_closures_for_dummies scheint nicht verfügbar zu sein Auch
Phatskat

1
Übrigens ist Resigs Version von Partial insofern mangelhaft (sicherlich nicht "auf dem Geld"), als es wahrscheinlich fehlschlagen wird, wenn einem der vorinitialisierten ("Curry") Argumente der undefinierte Wert zugewiesen wird . Jeder, der an einer guten Curry-Funktion interessiert ist, sollte das Original von Oliver Steeles funcitonal.js erhalten , da es dieses Problem nicht hat.
RobG

Antworten:


35

@Hank Gay

Als Antwort auf den Kommentar von EmbiggensTheMind:

Ich kann mir keinen Fall vorstellen, in dem Currying - für sich genommen - in JavaScript nützlich ist. Es ist eine Technik zum Konvertieren von Funktionsaufrufen mit mehreren Argumenten in Ketten von Funktionsaufrufen mit einem einzigen Argument für jeden Aufruf. JavaScript unterstützt jedoch mehrere Argumente in einem einzelnen Funktionsaufruf.

In JavaScript - und ich gehe davon aus, dass die meisten anderen tatsächlichen Sprachen (nicht Lambda-Kalkül) - wird dies jedoch häufig mit einer Teilanwendung in Verbindung gebracht. John Resig erklärt es besser , aber das Wesentliche ist, dass es eine Logik gibt, die auf zwei oder mehr Argumente angewendet wird, und Sie kennen nur die Werte für einige dieser Argumente.

Sie können Teilanwendung / Currying verwenden, um diese bekannten Werte zu korrigieren und eine Funktion zurückzugeben, die nur die Unbekannten akzeptiert, die später aufgerufen wird, wenn Sie tatsächlich die Werte haben, die Sie übergeben möchten. Auf diese Weise können Sie vermeiden, dass Sie sich wiederholen, wenn Sie dieselben integrierten JavaScript-Funktionen immer wieder mit denselben Werten bis auf einen aufgerufen hätten. Um Johns Beispiel zu stehlen:

String.prototype.csv = String.prototype.split.partial(/,\s*/);
var results = "John, Resig, Boston".csv();
alert( (results[1] == "Resig") + " The text values were split properly" );


6
Das ist wirklich eine schlechte Antwort. Currying hat nichts mit teilweiser Anwendung zu tun. Currying ermöglicht die Funktionszusammensetzung. Die Funktionszusammensetzung ermöglicht die Wiederverwendung von Funktionen. Die Wiederverwendung von Funktionen erhöht die Wartbarkeit des Codes. So einfach ist das!

2
@ftor Sir, Sie sind eine sehr schlechte Antwort. Beim Currying geht es offensichtlich darum, Funktionen schmackhafter zu machen. Sie haben den Punkt eindeutig verfehlt.
Callat

112

Hier ist eine interessante UND praktische Anwendung des Currying in JavaScript, bei der Verschlüsse verwendet werden :

function converter(toUnit, factor, offset, input) {
    offset = offset || 0;
    return [((offset + input) * factor).toFixed(2), toUnit].join(" ");
}

var milesToKm = converter.curry('km', 1.60936, undefined);
var poundsToKg = converter.curry('kg', 0.45460, undefined);
var farenheitToCelsius = converter.curry('degrees C', 0.5556, -32);

milesToKm(10);            // returns "16.09 km"
poundsToKg(2.5);          // returns "1.14 kg"
farenheitToCelsius(98);   // returns "36.67 degrees C"

Dies beruht auf einer curryErweiterung von Function, obwohl, wie Sie sehen können, nur verwendet wird apply(nichts Besonderes):

Function.prototype.curry = function() {
    if (arguments.length < 1) {
        return this; //nothing to curry with - return function
    }
    var __method = this;
    var args = toArray(arguments);
    return function() {
        return __method.apply(this, args.concat([].slice.apply(null, arguments)));
    }
}

5
Das ist toll! Ich sehe es ähnlich wie das Lisp-Zitat, das sagt "Lisp ist eine programmierbare Programmiersprache"
Santiagobasulto

2
Interessant, aber dieses Beispiel scheint nicht zu funktionieren. offset+inputwird undefined + 1.60936in Ihrem milesToKmBeispiel sein; das ergibt NaN.
Nathan Long

3
@ Nathan - Offset kann nicht undefiniert sein - es ist standardmäßig 0
AngusC

6
Nach dem, was ich (gerade) gelesen habe, ist "Curry" normalerweise nicht Teil der Trickkiste einer Funktion, es sei denn, Sie verwenden die Prototype-Bibliothek oder fügen sie selbst hinzu. Sehr cool.
Roboprog

11
Dasselbe kann mit der ES5-Methode bind () erreicht werden. Bind erstellt eine neue Funktion, die beim Aufruf die ursprüngliche Funktion mit dem Kontext ihres ersten Arguments und mit der nachfolgenden Folge von Argumenten (vor allen an die neue Funktion übergebenen) aufruft. Sie können also ... var milesToKm = converter.bind (this, 'km', 1.60936); oder var farenheitToCelsius = converter.bind (dies, 'Grad C', 0,5556, -32); Das erste Argument, der Kontext, ist hier irrelevant, so dass Sie einfach undefiniert übergeben können. Natürlich müssten Sie den Prototyp der Basisfunktion mit Ihrer eigenen Bindemethode für Nicht-ES5-Fallback erweitern
hacklikecrack

7

Ich fand Funktionen, die Python ähneln, functools.partialnützlicher in JavaScript:

function partial(fn) {
  return partialWithScope.apply(this,
    Array.prototype.concat.apply([fn, this],
      Array.prototype.slice.call(arguments, 1)));
}

function partialWithScope(fn, scope) {
  var args = Array.prototype.slice.call(arguments, 2);
  return function() {
    return fn.apply(scope, Array.prototype.concat.apply(args, arguments));
  };
}

Warum sollten Sie es verwenden wollen? Eine häufige Situation, in der Sie dies verwenden möchten, ist, wenn Sie thiseine Funktion an einen Wert binden möchten :

var callback = partialWithScope(Object.function, obj);

Wenn jetzt ein Rückruf aufgerufen wird, thiszeigt auf obj. Dies ist nützlich in Ereignissituationen oder um Platz zu sparen, da dadurch der Code normalerweise kürzer wird.

Currying ist ähnlich wie partiell, mit dem Unterschied, dass die Funktion, die das Currying zurückgibt, nur ein Argument akzeptiert (soweit ich das verstehe).


7

Einverstanden mit Hank Gay - Es ist äußerst nützlich in bestimmten funktionalen Programmiersprachen - weil es ein notwendiger Teil ist. In Haskell können Sie beispielsweise einfach nicht mehrere Parameter in eine Funktion übernehmen - dies ist in der reinen Funktionsprogrammierung nicht möglich. Sie nehmen jeweils einen Parameter und bauen Ihre Funktion auf. In JavaScript ist es trotz erfundener Beispiele wie "Konverter" einfach unnötig. Hier ist derselbe Konvertercode, ohne dass ein Curry erforderlich ist:

var converter = function(ratio, symbol, input) {
    return (input*ratio).toFixed(2) + " " + symbol;
}

var kilosToPoundsRatio = 2.2;
var litersToUKPintsRatio = 1.75;
var litersToUSPintsRatio = 1.98;
var milesToKilometersRatio = 1.62;

converter(kilosToPoundsRatio, "lbs", 4); //8.80 lbs
converter(litersToUKPintsRatio, "imperial pints", 2.4); //4.20 imperial pints
converter(litersToUSPintsRatio, "US pints", 2.4); //4.75 US pints
converter(milesToKilometersRatio, "km", 34); //55.08 km

Ich wünschte, Douglas Crockford hätte in "JavaScript: The Good Parts" eher die Geschichte und den tatsächlichen Gebrauch von Curry erwähnt als seine beiläufigen Bemerkungen. Die längste Zeit, nachdem ich das gelesen hatte, war ich verblüfft, bis ich funktionale Programmierung studierte und erkannte, dass es von dort kam.

Nach einigem Nachdenken gehe ich davon aus, dass es einen gültigen Anwendungsfall für das Currying in JavaScript gibt: Wenn Sie versuchen, mit rein funktionalen Programmiertechniken unter Verwendung von JavaScript zu schreiben. Scheint jedoch ein seltener Anwendungsfall zu sein.


2
Ihr Code ist viel einfacher zu verstehen als der von Prisoner Zero und löst das gleiche Problem, ohne Curry oder etwas Komplexes. Sie haben 2 Daumen hoch und er hat fast 100. Gehen Sie Figur.
DR01D

2

Es ist keine Magie oder so ... nur eine angenehme Abkürzung für anonyme Funktionen.

partial(alert, "FOO!") ist äquivalent zu function(){alert("FOO!");}

partial(Math.max, 0) entspricht function(x){return Math.max(0, x);}

Die Aufrufe von Partial ( MochiKit- Terminologie. Ich denke, einige andere Bibliotheken geben Funktionen eine .curry-Methode, die dasselbe tut) sehen etwas besser und weniger verrauscht aus als die anonymen Funktionen.


1

Für Bibliotheken, die es verwenden, gibt es immer Functional .

Wann ist es in JS nützlich? Wahrscheinlich zur gleichen Zeit ist es in anderen modernen Sprachen nützlich, aber das einzige Mal, dass ich mir vorstellen kann, es zu verwenden, ist in Verbindung mit einer teilweisen Anwendung.


Danke Hank - können Sie bitte erweitern, wenn es allgemein nützlich ist?
Dave Nolan

1

Ich würde sagen, dass höchstwahrscheinlich alle Animationsbibliotheken in JS Currying verwenden. Anstatt für jeden Aufruf eine Reihe von betroffenen Elementen und eine Funktion, die beschreibt, wie sich das Element verhalten soll, an eine Funktion höherer Ordnung übergeben zu müssen, die das gesamte Timing-Material sicherstellt, ist es für den Kunden im Allgemeinen einfacher, sie als öffentliche API freizugeben Funktion wie "slideUp", "fadeIn", die nur Elemente als Argumente verwendet und nur einige Curry-Funktionen sind, die die Funktion höherer Ordnung mit der integrierten Standard-Animationsfunktion zurückgeben.


Warum ist es besser, die höhere Funktion zu curry, als sie einfach mit einigen Standardeinstellungen aufzurufen?
Dave Nolan

1
Weil es viel modularer ist, eine "doMathOperation" mit einer Addition / Multiplikation / Quadrat / Modul / anderen Berechnung auf Wunsch zu curry, als sich alle "Standardwerte" vorzustellen, die die höhere Funktion unterstützen könnte.
Gizmo

1

Hier ist ein Beispiel.

Ich instrumentiere eine Reihe von Feldern mit JQuery, damit ich sehen kann, was Benutzer vorhaben. Der Code sieht folgendermaßen aus:

$('#foo').focus(trackActivity);
$('#foo').blur(trackActivity);
$('#bar').focus(trackActivity);
$('#bar').blur(trackActivity);

(Für Nicht-JQuery-Benutzer möchte ich, dass jedes Mal, wenn einige Felder den Fokus erhalten oder verlieren, die Funktion trackActivity () aufgerufen werden soll. Ich könnte auch eine anonyme Funktion verwenden, müsste sie jedoch duplizieren 4 mal, also habe ich es herausgezogen und benannt.)

Nun stellt sich heraus, dass eines dieser Felder anders behandelt werden muss. Ich möchte in der Lage sein, einen Parameter für einen dieser Aufrufe zu übergeben, der an unsere Tracking-Infrastruktur weitergeleitet wird. Mit Curry kann ich.


1

JavaScript-Funktionen werden in anderen Funktionssprachen Lamda genannt. Es kann verwendet werden, um eine neue API (leistungsfähigere oder komplexere Funktion) basierend auf der einfachen Eingabe eines anderen Entwicklers zu erstellen. Curry ist nur eine der Techniken. Sie können damit eine vereinfachte API erstellen, um eine komplexe API aufzurufen. Wenn Sie der Entwickler sind, der die vereinfachte API verwendet (zum Beispiel verwenden Sie jQuery, um einfache Manipulationen durchzuführen), müssen Sie kein Curry verwenden. Aber wenn Sie die vereinfachte API erstellen möchten, ist Curry Ihr Freund. Sie müssen ein Javascript-Framework (wie jQuery, mootools) oder eine Bibliothek schreiben, dann können Sie dessen Leistungsfähigkeit schätzen. Ich habe eine erweiterte Curry-Funktion unter http://blog.semanticsworks.com/2011/03/enhanced-curry-method.html geschrieben. Sie brauchen nicht die Curry-Methode, um Curry zu machen, es hilft nur, Curry zu machen, aber Sie können es immer manuell tun, indem Sie eine Funktion A () {} schreiben, um eine andere Funktion B () {} zurückzugeben. Um es interessanter zu machen, verwenden Sie die Funktion B (), um eine andere Funktion C () zurückzugeben.


1

Ich kenne den alten Thread, muss aber zeigen, wie dieser in Javascript-Bibliotheken verwendet wird:

Ich werde die Bibliothek lodash.js verwenden, um diese Konzepte konkret zu beschreiben.

Beispiel:

var fn = function(a,b,c){ 
return a+b+c+(this.greet || '); 
}

Teilanwendung:

var partialFnA = _.partial(fn, 1,3);

Currying:

var curriedFn = _.curry(fn);

Bindung:

var boundFn = _.bind(fn,object,1,3 );//object= {greet: ’!'}

Verwendung:

curriedFn(1)(3)(5); // gives 9 
or 
curriedFn(1,3)(5); // gives 9 
or 
curriedFn(1)(_,3)(2); //gives 9


partialFnA(5); //gives 9

boundFn(5); //gives 9!

Unterschied:

Nach dem Curry erhalten wir eine neue Funktion ohne vorgebundene Parameter.

Nach teilweiser Anwendung erhalten wir eine Funktion, die mit einigen Parametern vorgebunden ist.

Beim Binden können wir einen Kontext binden, der verwendet wird, um 'dies' zu ersetzen. Wenn nicht gebunden, ist der Standardbereich einer Funktion der Fensterbereich.

Hinweis: Das Rad muss nicht neu erfunden werden. Teilanwendung / Bindung / Currying sind sehr eng miteinander verbunden. Sie können den Unterschied oben sehen. Verwenden Sie diese Bedeutung überall und die Leute werden erkennen, was Sie tun, ohne Probleme beim Verständnis zu haben, und Sie müssen weniger Code verwenden.


0

Ich bin damit einverstanden, dass Sie manchmal den Ball ins Rollen bringen möchten, indem Sie eine Pseudofunktion erstellen , die immer den Wert des ersten Arguments enthält. Glücklicherweise bin ich auf eine brandneue JavaScript-Bibliothek namens jPaq gestoßen (h ttp: // jpaq.org/ ), das diese Funktionalität bietet. Das Beste an der Bibliothek ist, dass Sie Ihren eigenen Build herunterladen können, der nur den Code enthält, den Sie benötigen.



0

Ich wollte nur einige Ressourcen für Functional.js hinzufügen:

Vortrag / Konferenz mit Erläuterungen zu einigen Anwendungen http://www.youtube.com/watch?v=HAcN3JyQoyY

Aktualisierte Functional.js-Bibliothek: https://github.com/loop-recur/FunctionalJS Einige nette Helfer (sorry neu hier, kein Ruf: p): / loop-recur / PreludeJS

Ich habe diese Bibliothek in letzter Zeit häufig verwendet, um die Wiederholung in einer js IRC-Client-Hilfsbibliothek zu reduzieren. Es ist großartiges Zeug - hilft wirklich dabei, Code zu bereinigen und zu vereinfachen.

Wenn die Leistung zu einem Problem wird (diese Bibliothek ist jedoch ziemlich leicht), ist es außerdem einfach, sie mithilfe einer nativen Funktion neu zu schreiben.


0

Sie können native Bindung für eine schnelle einzeilige Lösung verwenden

function clampAngle(min, max, angle) {
    var result, delta;
    delta = max - min;
    result = (angle - min) % delta;
    if (result < 0) {
        result += delta;
    }
    return min + result;
};

var clamp0To360 = clampAngle.bind(null, 0, 360);

console.log(clamp0To360(405)) // 45


0

Ein weiterer Versuch, mit Versprechungen zu arbeiten.

(Haftungsausschluss: JS noob, der aus der Python-Welt stammt. Auch dort wird Currying nicht allzu oft verwendet, kann aber gelegentlich nützlich sein. Deshalb habe ich die Currying-Funktion deaktiviert - siehe Links).

Zuerst beginne ich mit einem Ajax-Anruf. Ich muss eine bestimmte Verarbeitung für den Erfolg durchführen, aber bei einem Fehler möchte ich dem Benutzer nur das Feedback geben, dass das Aufrufen von etwas zu einem Fehler geführt hat . In meinem eigentlichen Code zeige ich die Fehlerrückmeldung in einem Bootstrap-Bereich an, verwende hier jedoch nur die Protokollierung.

Ich habe meine Live-URL geändert, damit dies fehlschlägt.

function ajax_batch(e){
    var url = $(e.target).data("url");

    //induce error
    url = "x" + url;

    var promise_details = $.ajax(
        url,
        {
            headers: { Accept : "application/json" },
            // accepts : "application/json",
            beforeSend: function (request) {
                if (!this.crossDomain) {
                    request.setRequestHeader("X-CSRFToken", csrf_token);
                }
        },
        dataType : "json",
        type : "POST"}
    );
    promise_details.then(notify_batch_success, fail_status_specific_to_batch);
}

Um dem Benutzer mitzuteilen, dass ein Stapel fehlgeschlagen ist, muss ich diese Informationen in die Fehlerbehandlungsroutine schreiben, da nur eine Antwort vom Server angezeigt wird.

Ich habe immer noch nur die Informationen zum Zeitpunkt der Codierung verfügbar - in meinem Fall habe ich eine Reihe möglicher Stapel, aber ich weiß nicht, welcher fehlgeschlagen ist, um die Serverantwort über die fehlgeschlagene URL zu analysieren.

function fail_status_specific_to_batch(d){
    console.log("bad batch run, dude");
    console.log("response.status:" + d.status);
}

Machen wir das. Die Konsolenausgabe lautet:

Konsole:

bad batch run, dude utility.js (line 109) response.status:404

Nun wollen sie ändern die Dinge ein wenig und verwenden Sie einen wiederverwendbaren generischen Ausfall - Handler, sondern auch eine , die ist curried die bekannte-at-Code-Zeit Aufruf Kontext zur Laufzeit mit den beiden und den Laufzeitinfo von Veranstaltung.

    ... rest is as before...
    var target = $(e.target).text();
    var context = {"user_msg": "bad batch run, dude.  you were calling :" + target};
    var contexted_fail_notification = curry(generic_fail, context); 

    promise_details.then(notify_batch_success, contexted_fail_notification);
}

function generic_fail(context, d){
    console.log(context);
    console.log("response.status:" + d.status);
}

function curry(fn) {
     var slice = Array.prototype.slice,
        stored_args = slice.call(arguments, 1);
     return function () {
        var new_args = slice.call(arguments),
              args = stored_args.concat(new_args);
        return fn.apply(null, args);
     };
}

Konsole:

Object { user_msg="bad batch run, dude. you were calling :Run ACL now"} utility.js (line 117) response.status:404 utility.js (line 118)

In Anbetracht der weit verbreiteten Verwendung von Rückrufen in JS scheint Currying ein recht nützliches Werkzeug zu sein.

https://javascriptweblog.wordpress.com/2010/04/05/curry-cooking-up-tastier-functions/ http://www.drdobbs.com/open-source/currying-and-partial-functions-in- javasc / 231001821? pgno = 2

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.