Curry JavaScript: quali sono le applicazioni pratiche?


172

Non credo di aver ancora criticato il curry. Capisco cosa fa e come farlo. Non riesco proprio a pensare a una situazione che la userei.

Dove stai usando il curry in JavaScript (o dove lo usano le principali librerie)? Manipolazione DOM o esempi generali di sviluppo di applicazioni sono benvenuti.

Una delle risposte menziona l'animazione. Funzioni come slideUp, fadeInprendono un elemento come argomenti e sono normalmente una funzione curry che restituisce la funzione di ordine superiore con la "funzione di animazione" predefinita incorporata. Perché è meglio che semplicemente applicare la funzione di rialzo con alcune impostazioni predefinite?

Ci sono degli svantaggi nell'usarlo?

Come richiesto qui ci sono alcune buone risorse sul curriculum JavaScript:

Aggiungerò altro man mano che spuntano nei commenti.


Quindi, secondo le risposte, il curry e l'applicazione parziale in generale sono tecniche di praticità.

Se stai spesso “perfezionando” una funzione di alto livello chiamandola con la stessa configurazione, puoi curry (o usare il parziale di Resig) la funzione di livello superiore per creare metodi di supporto semplici e concisi.


puoi aggiungere un collegamento a una risorsa che descrive cos'è il curry JS? un tutorial o un post sul blog sarebbe fantastico.
Eric Schoonover,

2
svendtofte.com è lungimirante ma se salti l'intera sezione da "Un corso intensivo in ML" e ricomincia da "Come scrivere JavaScript al curry" diventa un'ottima introduzione al curry in js.
danio,

1
Questo è un buon punto di partenza per capire cos'è veramente il curry e l'applicazione parziale: slid.es/gsklee/functional-programming-in-5-minutes
gsklee

1
Il link per svendtofte.comsembrare morto - l'ho trovato sulla macchina WayBack anche se su web.archive.org/web/20130616230053/http://www.svendtofte.com/… Siamo spiacenti, blog.morrisjohns.com/javascript_closures_for_dummies sembra essere inattivo anche
phatskat il

1
A proposito, la versione parziale di Resig è carente (certamente non "on the money") in quanto probabilmente fallirà se uno degli argomenti pre-inizializzati ("curry") viene dato il valore indefinito . Chiunque sia interessato a una buona funzione di curry dovrebbe ottenere l'originale dal funcitonal.js di Oliver Steele , poiché non ha questo problema.
RobG

Risposte:


35

@Hank Gay

In risposta al commento di EmbiggensTheMind:

Non riesco a pensare a un'istanza in cui il curry - di per sé - è utile in JavaScript; è una tecnica per convertire le chiamate di funzione con più argomenti in catene di chiamate di funzione con un singolo argomento per ogni chiamata, ma JavaScript supporta più argomenti in una singola chiamata di funzione.

In JavaScript, e presumo che la maggior parte delle altre lingue effettive (non il calcolo lambda), è comunemente associata a un'applicazione parziale. John Resig lo spiega meglio , ma l'essenza è che hanno una logica che verrà applicata a due o più argomenti, e conosci solo i valori di alcuni di quegli argomenti.

È possibile utilizzare un'applicazione / curry parziale per correggere quei valori noti e restituire una funzione che accetta solo gli incogniti, da invocare in seguito quando si hanno effettivamente i valori che si desidera passare. Ciò fornisce un modo elegante per evitare di ripetersi quando avresti chiamato più volte gli stessi built-in JavaScript con gli stessi valori tranne uno. Per rubare l'esempio di Giovanni:

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
Questa è davvero una cattiva risposta. Currying non ha nulla a che fare con un'applicazione parziale. Currying abilita la composizione delle funzioni. La composizione della funzione consente il riutilizzo della funzione. Il riutilizzo delle funzioni aumenta la manutenibilità del codice. È così facile!

2
@ftor signore, sei una pessima risposta. Currying significa ovviamente rendere le funzioni più gustose. Hai chiaramente perso il punto.
Callat,

112

Ecco un uso interessante E pratico del curry in JavaScript che utilizza chiusure :

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"

Questo si basa su curryun'estensione di Function, anche se come puoi vedere usa solo apply(niente di troppo elegante):

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
Questo è fantastico! Lo vedo simile alla citazione lisp che dice "Lisp è un linguaggio di programmazione programmabile"
santiagobasulto,

2
Interessante, ma questo esempio non sembra funzionare. offset+inputsarà undefined + 1.60936nel tuo milesToKmesempio; che risulta in NaN.
Nathan Long,

3
@Nathan - l'offset non può essere definito - il valore predefinito è 0
AngusC

6
Da quello che ho letto (solo ora), "curry" normalmente non fa parte del bagaglio di trucchi di una funzione, a meno che tu non stia usando la libreria Prototype o la aggiunga tu stesso. Molto bello, però.
Roboprog,

11
Lo stesso può essere ottenuto con il metodo bind () ES5. Bind crea una nuova funzione che, quando chiamata, chiama la funzione originale con il contesto del suo primo argomento e con la successiva sequenza di argomenti (che precedono quelli passati alla nuova funzione). Quindi puoi fare ... var milesToKm = converter.bind (this, 'km', 1.60936); o var farenheitToCelsius = converter.bind (questo, 'gradi C', 0,5556, -32); Il primo argomento, il contesto, questo, è irrilevante qui, quindi potresti semplicemente passare indefinito. Ovviamente dovresti aumentare il prototipo della funzione di base con il tuo metodo di bind per il fallback non ES5
hacklikecrack

7

Ho trovato funzioni simili a Python functools.partialpiù utili 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));
  };
}

Perché dovresti usarlo? Una situazione comune in cui si desidera utilizzare questo è quando si desidera associare thisuna funzione a un valore:

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

Ora quando viene chiamato il callback, thispunta a obj. Ciò è utile in situazioni di eventi o per risparmiare un po 'di spazio perché in genere riduce il codice.

Il curry è simile al parziale con la differenza che la funzione restituita dal curry accetta solo un argomento (per quanto ho capito).


7

Accordarsi con Hank Gay - È estremamente utile in alcuni veri linguaggi di programmazione funzionale - perché è una parte necessaria. Ad esempio, in Haskell semplicemente non è possibile applicare più parametri a una funzione, non è possibile farlo nella pura programmazione funzionale. Prendi un parametro alla volta e costruisci la tua funzione. In JavaScript è semplicemente superfluo, nonostante esempi inventati come "convertitore". Ecco lo stesso codice del convertitore, senza la necessità di 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

Vorrei tanto che Douglas Crockford, in "JavaScript: The Good Parts", avesse menzionato la storia e l'uso effettivo del curry piuttosto che le sue osservazioni disinvolte. Per molto tempo dopo averlo letto, sono rimasto sbalordito, fino a quando non ho studiato la programmazione funzionale e ho capito da dove veniva.

Dopo aver riflettuto un po 'di più, suppongo che ci sia un caso d'uso valido per curry in JavaScript: se stai cercando di scrivere usando tecniche di programmazione funzionale pura usando JavaScript. Sembra un caso d'uso raro però.


2
Il tuo codice è molto più facile da capire rispetto a quello del Prigioniero Zero e risolve lo stesso problema senza curry o nulla di complesso. Hai 2 pollici in su e ne ha quasi 100. Vai a capire.
DR01D,

2

Non è magia o altro ... solo una piacevole scorciatoia per funzioni anonime.

partial(alert, "FOO!") è equivalente a function(){alert("FOO!");}

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

Le chiamate alla parziale ( terminologia di MochiKit . Penso che alcune altre librerie diano alle funzioni un metodo .curry che fa la stessa cosa) sembrano leggermente più belle e meno rumorose delle funzioni anonime.


1

Per quanto riguarda le librerie che lo usano, c'è sempre funzionale .

Quando è utile in JS? Probabilmente le stesse volte è utile in altre lingue moderne, ma l'unica volta che riesco a vedermi usarlo è in congiunzione con un'applicazione parziale.


Grazie Hank - per favore, puoi approfondire quando è utile in generale?
Dave Nolan,

1

Direi che, molto probabilmente, tutta la libreria di animazione in JS sta usando il curry. Piuttosto che dover passare per ogni chiamata un insieme di elementi interessati e una funzione, descrivendo come dovrebbe comportarsi l'elemento, a una funzione di ordine superiore che garantirà tutte le cose di temporizzazione, è generalmente più facile per il cliente rilasciare, come API pubblica alcuni funziona come "slideUp", "fadeIn" che accetta solo elementi come argomenti e che sono solo alcune funzioni al curry che restituiscono la funzione di ordine superiore con la "funzione di animazione" predefinita incorporata.


Perché è meglio eseguire il curry della funzione di livello superiore anziché semplicemente chiamarla con alcune impostazioni predefinite?
Dave Nolan,

1
Perché è molto più modulare essere in grado di elaborare un "doMathOperation" con un'aggiunta / moltiplicazione / quadrato / modulo / altra calutazione a piacimento piuttosto che immaginare tutto il "default" che la funzione superiore potrebbe supportare.
Gizmo,

1

Ecco un esempio

Sto strumentando un sacco di campi con JQuery in modo da poter vedere cosa stanno facendo gli utenti. Il codice è simile al seguente:

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

(Per gli utenti non JQuery, sto dicendo che ogni volta che un paio di campi ottengono o perdono il focus, voglio che venga chiamata la funzione trackActivity (). Potrei anche usare una funzione anonima, ma dovrei duplicarla 4 volte, quindi l'ho estratto e l'ho chiamato.)

Ora si scopre che uno di quei campi deve essere gestito in modo diverso. Vorrei poter trasmettere un parametro su una di quelle chiamate da passare alla nostra infrastruttura di monitoraggio. Con il curry, posso.


1

Le funzioni JavaScript sono chiamate lamda in altri linguaggi funzionali. Può essere utilizzato per comporre un nuovo api (funzione più potente o complext) in base al semplice input di un altro sviluppatore. Il curry è solo una delle tecniche. Puoi usarlo per creare un'API semplificata per chiamare un'API complessa. Se sei lo sviluppatore che utilizza l'API semplificato (ad esempio, usi jQuery per fare semplici manipolazioni), non è necessario usare curry. Ma se vuoi creare l'API semplificata, il curry è tuo amico. Devi scrivere un framework javascript (come jQuery, mootools) o una libreria, quindi puoi apprezzarne il potere. Ho scritto una funzione di curry avanzata, su http://blog.semanticsworks.com/2011/03/enhanced-curry-method.html. Non è necessario il metodo curry per eseguire il curry, è solo utile per eseguire il curry, ma puoi sempre farlo manualmente scrivendo una funzione A () {} per restituire un'altra funzione B () {}. Per renderlo più interessante, utilizzare la funzione B () per restituire un'altra funzione C ().


1

Conosco il suo vecchio thread ma dovrò mostrare come viene utilizzato nelle librerie javascript:

Userò la libreria lodash.js per descrivere concretamente questi concetti.

Esempio:

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

Applicazione parziale:

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

accattivarsi:

var curriedFn = _.curry(fn);

Rilegatura:

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

utilizzo:

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!

differenza:

dopo il curry otteniamo una nuova funzione senza parametri prestabiliti.

dopo un'applicazione parziale otteniamo una funzione che è associata ad alcuni parametri prebound.

in associazione possiamo associare un contesto che verrà utilizzato per sostituire "questo", se non associato il valore predefinito di qualsiasi funzione sarà l'ambito della finestra.

Avviso: non è necessario reinventare la ruota. L'applicazione parziale / rilegatura / curry sono molto correlate. Puoi vedere la differenza sopra. Usa questo significato ovunque e le persone riconosceranno ciò che stai facendo senza problemi di comprensione, inoltre dovrai usare meno codice.


0

Sono d'accordo che a volte ti piacerebbe far rotolare la palla creando una pseudo-funzione che avrà sempre il valore del primo argomento compilato. Fortunatamente, mi sono imbattuto in una nuova libreria JavaScript chiamata jPaq (h ttp: // jpaq.org/ ) che fornisce questa funzionalità. La cosa migliore della libreria è il fatto che è possibile scaricare la propria build che contiene solo il codice necessario.



0

Volevo solo aggiungere alcune risorse per Functional.js:

Conferenza / conferenza che spiega alcune applicazioni http://www.youtube.com/watch?v=HAcN3JyQoyY

Libreria Functional.js aggiornata: https://github.com/loop-recur/FunctionalJS Alcuni simpatici aiutanti (mi dispiace nuovo qui, nessuna reputazione: p): / loop-recur / PreludeJS

Ho usato questa libreria molto di recente per ridurre la ripetizione in una libreria helper di client IRC js. È roba fantastica: aiuta davvero a ripulire e semplificare il codice.

Inoltre, se le prestazioni diventano un problema (ma questa libreria è piuttosto leggera), è facile riscrivere semplicemente usando una funzione nativa.


0

È possibile utilizzare il binding nativo per una soluzione rapida a una riga

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

Un'altra pugnalata, dal lavorare con le promesse.

(Dichiarazione di non responsabilità: JS Noob, proveniente dal mondo Python. Anche lì, il curry non è molto utilizzato, ma può tornare utile in alcune occasioni. Quindi ho paralizzato la funzione di curry - vedi link)

In primo luogo, sto iniziando con una chiamata Ajax. Ho qualche elaborazione specifica da fare in caso di successo, ma in caso di insuccesso, voglio solo dare all'utente il feedback che chiamare qualcosa ha provocato un errore . Nel mio codice attuale, visualizzo il feedback degli errori in un pannello bootstrap, ma sto solo usando la registrazione qui.

Ho modificato il mio URL live per farlo fallire.

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

Ora, qui per dire all'utente che un batch non è riuscito, ho bisogno di scrivere quelle informazioni nel gestore degli errori, perché tutto ciò che sta ottenendo è una risposta dal server.

Ho ancora le informazioni disponibili solo al momento della codifica - nel mio caso ho un numero di possibili batch, ma non so quale sia fallito durante l'analisi della risposta del server sull'URL fallito.

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

Facciamolo. L'output della console è:

console:

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

Ora cambiamo un po 'le cose e usiamo un gestore di errori generici riutilizzabile, ma anche uno che è al curry in fase di esecuzione con sia il contesto di chiamata al momento del codice noto sia le informazioni di runtime disponibili dall'evento.

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

Più in generale, dato quanto sia diffuso l'utilizzo del callback in JS, il curry sembra uno strumento abbastanza utile da avere.

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.