Curry JavaScript: quelles sont les applications pratiques?

172

Je ne pense pas avoir encore goûté au curry. Je comprends ce qu'il fait et comment le faire. Je ne peux tout simplement pas penser à une situation dans laquelle je l'utiliserais.

Où utilisez-vous le curry en JavaScript (ou où les principales bibliothèques l'utilisent-elles)? Les manipulations DOM ou les exemples généraux de développement d'applications sont les bienvenus.

L'une des réponses mentionne l'animation. Des fonctions comme slideUp, fadeInprennent un élément comme argument et sont normalement une fonction curry retournant la fonction d'ordre supérieur avec la "fonction d'animation" intégrée par défaut. Pourquoi est-ce mieux que d'appliquer simplement la fonction supérieure avec certaines valeurs par défaut?

Y a-t-il des inconvénients à l'utiliser?

Comme demandé, voici quelques bonnes ressources sur le curry JavaScript:

J'ajouterai plus au fur et à mesure qu'ils apparaissent dans les commentaires.


Ainsi, selon les réponses, le curry et l'application partielle en général sont des techniques de commodité.

Si vous «affinez» fréquemment une fonction de haut niveau en l'appelant avec la même configuration, vous pouvez curry (ou utiliser le partiel de Resig) la fonction de niveau supérieur pour créer des méthodes d'aide simples et concises.

Dave Nolan
la source
pouvez-vous ajouter un lien vers une ressource qui décrit ce qu'est le curry JS? un tutoriel ou un article de blog serait génial.
Eric Schoonover le
2
svendtofte.com est long, mais si vous sautez toute la section de "Un cours intensif en ML" et recommencez à "Comment écrire du JavaScript curry", cela devient une excellente introduction au curry en js.
danio
1
C'est un bon point de départ pour comprendre ce qu'est vraiment le curry et l'application partielle: slid.es/gsklee/functional-programming-in-5-minutes
gsklee
1
Le lien vers svendtofte.comsemble mort - trouvé sur la machine WayBack à l'adresse web.archive.org/web/20130616230053/http : //www.svendtofte.com/... Désolé, blog.morrisjohns.com/javascript_closures_for_dummies semble être en panne trop
phatskat
1
BTW, la version de Resig de partial est déficiente (certainement pas "on the money") en ce qu'elle échouera probablement si l'un des arguments pré-initialisés ("curry") reçoit la valeur indéfinie . Toute personne intéressée par une bonne fonction de curry devrait obtenir l'original du funcitonal.js d' Oliver Steele , car il n'a pas ce problème.
RobG

Réponses:

35

@Hank Gay

En réponse au commentaire d'EmbiggensTheMind:

Je ne peux pas penser à un exemple où le curry - en soi - est utile en JavaScript; c'est une technique pour convertir les appels de fonction avec plusieurs arguments en chaînes d'appels de fonction avec un seul argument pour chaque appel, mais JavaScript prend en charge plusieurs arguments dans un seul appel de fonction.

En JavaScript - et je suppose que la plupart des autres langages réels (pas le calcul lambda) - il est généralement associé à une application partielle, cependant. John Resig l' explique mieux , mais l'essentiel est d'avoir une logique qui sera appliquée à deux ou plusieurs arguments, et vous ne connaissez que la ou les valeurs de certains de ces arguments.

Vous pouvez utiliser l'application partielle / currying pour corriger ces valeurs connues et renvoyer une fonction qui n'accepte que les inconnues, à invoquer plus tard lorsque vous avez réellement les valeurs que vous souhaitez transmettre. Cela fournit un moyen astucieux d'éviter de vous répéter lorsque vous auriez appelé les mêmes éléments intégrés JavaScript à plusieurs reprises avec toutes les mêmes valeurs sauf une. Pour voler l'exemple de John:

String.prototype.csv = String.prototype.split.partial(/,\s*/);
var results = "John, Resig, Boston".csv();
alert( (results[1] == "Resig") + " The text values were split properly" );
Hank Gay
la source
2
J'ai essayé de l'expliquer avec des exemples et des démos dans mon article écrit.
Zaheer Ahmed
6
C'est vraiment une mauvaise réponse. Le curry n'a rien à voir avec une application partielle. Le curry permet la composition fonctionnelle. La composition des fonctions permet la réutilisation des fonctions. La réutilisation des fonctions augmente la maintenabilité du code. C'est si facile!
2
@ftor monsieur, vous êtes une très mauvaise réponse. Le curry consiste évidemment à rendre les fonctions plus savoureuses. Vous avez clairement manqué le point.
Callat le
112

Voici une utilisation intéressante ET pratique du curry en JavaScript qui utilise des fermetures :

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"

Cela repose sur une curryextension de Function, bien que comme vous pouvez le voir, il n'utilise que apply(rien de trop compliqué):

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)));
    }
}
Prisoner ZERO
la source
5
C'est bien! Je le vois similaire à la citation de lisp qui dit "Lisp est un langage de programmation programmable"
santiagobasulto
2
Intéressant, mais cet exemple ne semble pas fonctionner. offset+inputsera undefined + 1.60936dans votre milesToKmexemple; cela se traduit par NaN.
Nathan Long
3
@Nathan - le décalage ne peut pas être indéfini - il vaut 0 par défaut
AngusC
6
D'après ce que j'ai lu (tout à l'heure), "curry" ne fait normalement pas partie du sac d'astuces d'une fonction, à moins que vous n'utilisiez la bibliothèque Prototype ou que vous l'ajoutiez vous-même. Très cool, cependant.
Roboprog
11
La même chose peut être obtenue avec la méthode ES5 bind (). Bind crée une nouvelle fonction qui, lorsqu'elle est appelée, appelle la fonction d'origine avec le contexte de son premier argument et avec la séquence suivante d'arguments (précédant tout passé à la nouvelle fonction). Vous pouvez donc faire ... var milesToKm = converter.bind (this, 'km', 1.60936); ou var farenheitToCelsius = convertisseur.bind (ceci, 'degrés C', 0,5556, -32); Le premier argument, le contexte, ceci n'est pas pertinent ici, vous pouvez donc simplement passer undefined. Bien sûr, vous devrez augmenter le prototype de fonction de base avec votre propre méthode de liaison pour le repli non ES5
hacklikecrack
7

J'ai trouvé des fonctions qui ressemblent à celles de python functools.partialplus utiles en 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));
  };
}

Pourquoi voudriez-vous l'utiliser? Une situation courante où vous souhaitez l'utiliser est lorsque vous souhaitez lier thisune fonction à une valeur:

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

Désormais, lorsque le rappel est appelé, thispointe vers obj. Ceci est utile dans les situations d'événements ou pour économiser de l'espace car cela raccourcit généralement le code.

Le curry est similaire à partiel à la différence que la fonction renvoyée par le curry n'accepte qu'un seul argument (pour autant que je sache).

Armin Ronacher
la source
7

D'accord avec Hank Gay - C'est extrêmement utile dans certains vrais langages de programmation fonctionnels - car c'est une partie nécessaire. Par exemple, dans Haskell, vous ne pouvez tout simplement pas prendre plusieurs paramètres dans une fonction - vous ne pouvez pas le faire en programmation fonctionnelle pure. Vous prenez un paramètre à la fois et développez votre fonction. En JavaScript, c'est simplement inutile, malgré des exemples artificiels comme "convertisseur". Voici ce même code de convertisseur, sans avoir besoin de curry:

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

Je souhaite vivement que Douglas Crockford, dans "JavaScript: The Good Parts", ait donné une certaine mention de l'histoire et de l'utilisation réelle du curry plutôt que de ses remarques désinvoltes. Pendant le plus long moment après avoir lu cela, j'ai été perplexe, jusqu'à ce que j'étudie la programmation fonctionnelle et que je réalise que c'est de là que ça vient.

Après réflexion, je suppose qu'il existe un cas d'utilisation valable pour le curry en JavaScript: si vous essayez d'écrire en utilisant des techniques de programmation fonctionnelle pure utilisant JavaScript. Cela semble être un cas d'utilisation rare.

Byron Katz
la source
2
Votre code est beaucoup plus facile à comprendre que celui de Prisoner Zero et il résout le même problème sans curry ni quoi que ce soit de complexe. Vous avez 2 pouces vers le haut et il en a presque 100. Allez comprendre.
DR01D
2

Ce n'est pas de la magie ou quoi que ce soit ... juste un raccourci agréable pour les fonctions anonymes.

partial(alert, "FOO!") est équivalent à function(){alert("FOO!");}

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

Les appels à partial ( terminologie MochiKit . Je pense que certaines autres bibliothèques donnent aux fonctions une méthode .curry qui fait la même chose) semblent légèrement plus agréables et moins bruyants que les fonctions anonymes.

Marijn
la source
1

Quant aux bibliothèques qui l'utilisent, il y a toujours Functional .

Quand est-ce utile dans JS? Probablement les mêmes fois, il est utile dans d'autres langues modernes, mais le seul moment où je peux me voir l'utiliser est en conjonction avec une application partielle.

Hank Gay
la source
Merci Hank - pouvez-vous expliquer quand cela est utile en général?
Dave Nolan le
1

Je dirais que, très probablement, toute la bibliothèque d'animation de JS utilise le currying. Plutôt que d'avoir à passer pour chaque appel un ensemble d'éléments impactés et une fonction, décrivant comment l'élément doit se comporter, à une fonction d'ordre supérieur qui garantira tout le temps, il est généralement plus facile pour le client de publier, en tant qu'API publique, certains fonction comme "slideUp", "fadeIn" qui ne prend que des éléments comme arguments, et qui sont juste une fonction curry retournant la fonction d'ordre supérieur avec la "fonction d'animation" intégrée par défaut.

gadget
la source
Pourquoi est-il préférable de curry la fonction supérieur plutôt que de l'appeler simplement avec des valeurs par défaut?
Dave Nolan le
1
Parce que c'est beaucoup plus modulaire de pouvoir curry une "doMathOperation" avec une addition / multiplication / carré / module / autre-calucation à souhait que d'imaginer tous les "défauts" que la fonction supérieure pourrait supporter.
gizmo le
1

Voici un exemple.

J'instrumentation un tas de champs avec JQuery afin que je puisse voir ce que font les utilisateurs. Le code ressemble à ceci:

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

(Pour les utilisateurs non-JQuery, je dis que chaque fois que quelques champs obtiennent ou perdent le focus, je veux que la fonction trackActivity () soit appelée. Je pourrais aussi utiliser une fonction anonyme, mais je devrais la dupliquer 4 fois, alors je l'ai sorti et l'ai nommé.)

Maintenant, il s'avère que l'un de ces champs doit être traité différemment. J'aimerais pouvoir transmettre un paramètre à l'un de ces appels à transmettre à notre infrastructure de suivi. Avec le curry, je peux.

William Pietri
la source
1

Les fonctions JavaScript sont appelées lamda dans un autre langage fonctionnel. Il peut être utilisé pour composer une nouvelle api (fonction plus puissante ou plus complexe) basée sur la simple entrée d'un autre développeur. Le curry n'est qu'une des techniques. Vous pouvez l'utiliser pour créer une API simplifiée pour appeler une API complexe. Si vous êtes le développeur qui utilise l'API simplifiée (par exemple, vous utilisez jQuery pour faire une manipulation simple), vous n'avez pas besoin d'utiliser curry. Mais si vous souhaitez créer l'API simplifiée, le curry est votre ami. Vous devez écrire un framework javascript (comme jQuery, mootools) ou une bibliothèque, alors vous pouvez apprécier sa puissance. J'ai écrit une fonction curry améliorée, à http://blog.semanticsworks.com/2011/03/enhanced-curry-method.html. Vous n'avez pas besoin de la méthode curry pour faire du curry, cela aide simplement à faire du curry, mais vous pouvez toujours le faire manuellement en écrivant une fonction A () {} pour renvoyer une autre fonction B () {}. Pour le rendre plus intéressant, utilisez la fonction B () pour renvoyer une autre fonction C ().

Fred Yang
la source
1

Je connais son ancien fil mais je vais devoir montrer comment cela est utilisé dans les bibliothèques javascript:

J'utiliserai la bibliothèque lodash.js pour décrire ces concepts concrètement.

Exemple:

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

Application partielle:

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

Curry:

var curriedFn = _.curry(fn);

Contraignant:

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

usage:

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!

différence:

après currying, nous obtenons une nouvelle fonction sans paramètres pré-liés.

après une application partielle, nous obtenons une fonction qui est liée à certains paramètres pré-liés.

dans la liaison, nous pouvons lier un contexte qui sera utilisé pour remplacer 'this', sinon la valeur par défaut de toute fonction sera la portée de la fenêtre.

Conseil: il n'est pas nécessaire de réinventer la roue. Application partielle / liaison / currying sont très liés. Vous pouvez voir la différence ci-dessus. Utilisez ce sens n'importe où et les gens reconnaîtront ce que vous faites sans problèmes de compréhension et vous devrez utiliser moins de code.

Shishir Arora
la source
0

Je conviens que parfois vous aimeriez lancer le bal en créant une pseudo-fonction qui aura toujours la valeur du premier argument renseignée. Heureusement, je suis tombé sur une toute nouvelle bibliothèque JavaScript appelée jPaq (h ttp: // jpaq.org/ ) qui fournit cette fonctionnalité. La meilleure chose à propos de la bibliothèque est le fait que vous pouvez télécharger votre propre build qui ne contient que le code dont vous aurez besoin.

Clarence Fredericks
la source
0

Je viens d'écrire un exemple jPaq qui montre quelques applications intéressantes de la fonction curry. Découvrez-le ici: Currying Up String Functions

Chris West
la source
0

Je voulais juste ajouter des ressources pour Functional.js:

Conférence / conférence expliquant certaines applications http://www.youtube.com/watch?v=HAcN3JyQoyY

Mise à jour de la bibliothèque Functional.js: https://github.com/loop-recur/FunctionalJS Quelques bons helpers (désolé nouveau ici, pas de réputation: p): / loop-recur / PreludeJS

J'ai beaucoup utilisé cette bibliothèque récemment pour réduire la répétition dans une bibliothèque d'aide de clients IRC js. C'est génial - cela aide vraiment à nettoyer et à simplifier le code.

De plus, si les performances deviennent un problème (mais que cette bibliothèque est assez légère), il est facile de simplement réécrire en utilisant une fonction native.

mégawac
la source
0

Vous pouvez utiliser la liaison native pour une solution rapide en une seule ligne

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

cstuncsik
la source
0

Un autre coup de couteau, de travailler avec des promesses.

(Clause de non-responsabilité: JS noob, venant du monde Python. Même là, le curry n'est pas très utilisé, mais il peut être utile à l'occasion. J'ai donc créé la fonction currying - voir les liens)

Tout d'abord, je commence par un appel ajax. J'ai un traitement spécifique à faire en cas de succès, mais en cas d'échec, je veux juste dire à l'utilisateur que l'appel de quelque chose a entraîné une erreur . Dans mon code actuel, j'affiche le retour d'erreur dans un panneau d'amorçage, mais j'utilise simplement la journalisation ici.

J'ai modifié mon URL en direct pour que cela échoue.

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);
}

Maintenant, ici pour dire à l'utilisateur qu'un lot a échoué, je dois écrire cette information dans le gestionnaire d'erreurs, car tout ce qu'il obtient est une réponse du serveur.

Je n'ai toujours que les informations disponibles au moment du codage - dans mon cas, j'ai un certain nombre de lots possibles, mais je ne sais pas lequel a échoué lors de l'analyse de la réponse du serveur à propos de l'URL échouée.

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

Faisons le. La sortie de la console est:

console:

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

Maintenant, changeons un peu les choses et utilisons un gestionnaire d'échec générique réutilisable, mais aussi un qui est curry au moment de l'exécution avec à la fois le contexte d'appel connu au moment du code et les informations d'exécution disponibles à partir de l'événement.

    ... 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);
     };
}

console:

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

Plus généralement, étant donné la généralisation de l'utilisation des rappels dans JS, le curry semble être un outil très utile.

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

JL Peyret
la source