web-dev-qa-db-fra.com

JavaScript curry: quelles sont les applications pratiques?

Je ne pense pas avoir encore réussi à faire curry. Je comprends ce que ça fait et comment le faire. Je ne peux tout simplement pas penser à une situation où je l’utiliserais.

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

Une des réponses mentionne l'animation. Des fonctions telles que slideUp, fadeIn prennent un élément comme argument et sont normalement une fonction curryre renvoyant la fonction d'ordre élevé avec la "fonction d'animation" intégrée par défaut. Pourquoi est-ce mieux que de simplement appliquer la fonction en haut avec quelques valeurs par défaut?

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

Comme demandé, voici quelques bonnes ressources sur JavaScript currying:

J'en ajouterai plus au fur et à mesure qu'ils apparaîtront dans les commentaires.


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

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

166
Dave Nolan

@Hank Gay

En réponse au commentaire de EmbiggensTheMind:

Je ne peux pas penser à un cas où currying - en lui-même - est utile en JavaScript; Il s'agit d'une technique permettant de convertir des 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 actuels (pas le lambda calcul) -, il est généralement associé à une application partielle. John Resig explique mieux , mais l’essentiel est que la logique soit appliquée à deux arguments ou plus, et que vous ne connaissiez que la ou les valeurs de certains de ces arguments. 

Vous pouvez utiliser application/currying partiel pour corriger ces valeurs connues et renvoyer une fonction n'acceptant que les inconnues, qui sera invoquée ultérieurement lorsque vous avez réellement les valeurs que vous souhaitez transmettre. Cela fournit un moyen astucieux d’éviter de vous répéter alors que vous auriez appelé les mêmes éléments JavaScript encore et encore 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" );
31
Hank Gay

Voici un utilisation intéressante ET pratique du curry en JavaScript utilisant 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 extension curry de Function, bien que, comme vous pouvez le constater, il n’utilise que apply (rien d’extrême):

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)));
    }
}
110
Prisoner ZERO

J'ai trouvé des fonctions qui ressemblent au functools.partial de python plus 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 utiliser ceci est lorsque vous souhaitez lier this dans une fonction à une valeur:

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

Désormais, lorsque callback est appelé, this pointe sur obj. Ceci est utile dans des situations d’événement ou pour économiser de l’espace car cela raccourcit généralement le code.

Le currying est similaire au partiel avec la différence que la fonction retournée n'accepte qu'un argument (si je comprends bien).

6
Armin Ronacher

Convenir avec Hank Gay - C'est extrêmement utile dans certains langages de programmation véritablement 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 dans la programmation fonctionnelle pure. Vous prenez un paramètre à la fois et construisez votre fonction. En JavaScript, cela est tout simplement inutile, malgré des exemples artificiels comme "convertisseur". Voici ce même code de convertisseur, sans avoir besoin de currying:

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 fait mention de l'histoire et de l'utilisation actuelle du curry plutôt que de ses propos désinvoltes. Pendant très longtemps après avoir lu cela, j'ai été bouleversé, jusqu'à ce que j'étudie la programmation fonctionnelle et réalise que c'est de là que tout venait.

Après mûre réflexion, je pense qu’il existe un cas d’utilisation valable pour le curry en JavaScript: si vous essayez d’écrire en utilisant des techniques de programmation purement fonctionnelles utilisant JavaScript. Cela semble être un cas d'utilisation rare cependant.

5
Byron Katz

Voici un exemple.

J'instrumentise de nombreux champs avec JQuery pour que je puisse voir ce que les utilisateurs font. Le code ressemble à ceci:

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

(Pour les utilisateurs non-JQuery, je dis qu'à chaque fois que deux champs sont mis en évidence ou perdus, je souhaite que la fonction trackActivity () soit appelée. Je pourrais également 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 qu'un de ces champs doit être traité différemment. J'aimerais pouvoir transmettre un paramètre à l'un de ces appels pour qu'il soit transmis à notre infrastructure de suivi. Avec currying, je peux.

2
William Pietri

Ce n'est pas une 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 ( MochiKit terminology. Je pense que d’autres bibliothèques donnent aux fonctions une méthode .curry qui fait la même chose) sont légèrement plus agréables et moins bruyants que les fonctions anonymes.

2
Marijn

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 complexe) sur la base de la saisie simple d'un autre développeur. Le curry n'est qu'une des techniques. Vous pouvez l'utiliser pour créer une API simplifiée permettant d'appeler une API complexe. Si vous êtes le développeur qui utilise l'API simplifiée (par exemple, vous utilisez jQuery pour effectuer des manipulations simples), vous n'avez pas besoin d'utiliser curry. Mais si vous voulez créer l’API simplifiée, Curry est votre ami. Vous devez écrire un framework javascript (comme jQuery, mootools) ou une bibliothèque, vous pourrez alors apprécier sa puissance. J'ai écrit une fonction curry améliorée à l'adresse http://blog.semanticsworks.com/2011/03/enhanced-curry-method.html . Vous n'avez pas besoin de la méthode curry pour effectuer le curry, cela vous aide simplement de le faire, 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 ().

1
Fred Yang

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

Je vais utiliser la bibliothèque lodash.js pour décrire concrètement ces concepts.

Exemple:

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

Application partielle:

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

Currying: 

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 application partielle, nous obtenons une fonction liée à certains paramètres prebound.

en liant, nous pouvons lier un contexte qui sera utilisé pour remplacer ‘this’, sinon la valeur par défaut d’une fonction sera la portée de la fenêtre.

Avis: Il n'est pas nécessaire de réinventer la roue. L'application partielle/la liaison/le curry 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.

1
Shishir Arora

En ce qui concerne les bibliothèques qui l'utilisent, il y a toujours Functional .

Quand est-ce utile en JS? Probablement aux mêmes moments, 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.

1
Hank Gay

Je dirais que, très probablement, toute la bibliothèque d’animation de JS utilise le currying. Plutôt que de devoir transmettre à chaque appel un ensemble d’éléments impactés et une fonction, décrivant le comportement de l’élément, en une fonction d’ordre supérieur assurant fonction comme "slideUp", "fadeIn" qui prend uniquement des éléments en tant qu'arguments, et qui ne sont que des fonctions currys renvoyant la fonction d'ordre élevé avec la "fonction d'animation" intégrée par défaut.

1
gizmo

Je voulais juste ajouter quelques ressources pour Functional.js:

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

Bibliothèque Functional.js mise à jour: https://github.com/loop-recur/FunctionalJS Quelques gentils assistants (désolé, rien de nouveau: p): /Loop-recur/PreludeJS

J'utilise beaucoup cette bibliothèque récemment pour réduire la répétition dans une bibliothèque d'assistance client js IRC. C'est une bonne chose - aide vraiment à nettoyer et à simplifier le code. 

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

0
megawac

Vous pouvez utiliser la liaison native pour une solution rapide à une 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

0
cstuncsik

Un autre coup dur, de travailler avec des promesses.

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

Premièrement, je commence par un appel ajax. J'ai un traitement spécifique à effectuer en cas de succès, mais en cas d'échec, je souhaite simplement donner à l'utilisateur le retour d'informations indiquant que l'appel de quelque chose a donné lieu à une erreur. Dans mon code actuel, le message d'erreur est affiché dans un panneau de démarrage, 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 afin de dire à l'utilisateur qu'un lot a échoué, je dois écrire cette information dans le gestionnaire d'erreurs, car tout ce qu'il reçoit 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é. analyser la réponse du serveur à propos de l'URL ayant échoué.

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 gestionnaire curry} au moment de l'exécution, avec le contexte d'appel connu à la date 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é l’utilisation généralisée 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

0
JL Peyret

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

0
Chris West

Je conviens que vous voudriez parfois lancer le processus en créant une pseudo-fonction qui conservera toujours la valeur du premier argument. 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 version qui ne contient que le code dont vous aurez besoin.

0
Clarence Fredericks