Currying una funzione che prende argomenti infiniti

Usando ES5, come fai a curry una funzione che prende argomenti infiniti.

function add(a, b, c) { return a + b + c; } 

La funzione sopra richiede solo tre argomenti, ma vogliamo che la nostra versione curried sia in grado di prendere argomenti infiniti.

Quindi, di tutti i seguenti casi di test dovrebbero passare:

 var test = add(1); test(2); //should return 3 test(2,3); //should return 6 test(4,5,6); //should return 16 

Ecco la soluzione che mi è venuta in mente:

 function add(a, b, c) { var args = Array.prototype.slice.call(arguments); return function () { var secondArgs = Array.prototype.slice.call(arguments); var totalArguments = secondArgs.concat(args); var sum = 0; for (i = 0; i < totalArguments.length; i++) { sum += totalArguments[0]; } return sum; } } 

Tuttavia, mi è stato detto che non è molto “funzionale” nello stile.

Parte del motivo per cui la funzione di add non è molto “funzionale” è perché sta tentando di fare qualcosa di più che semplicemente sumre numeri passati ad essa. Sarebbe fonte di confusione per gli altri sviluppatori di guardare il tuo codice, vedere una funzione di add , e quando lo chiamano, ottenere una funzione restituita a loro invece della sum.

Per esempio:

 //Using your add function, I'm expecting 6 add(1,2,3) //Returns another function = confusing! 

L’approccio funzionale

L’approccio funzionale sarebbe quello di creare una funzione che ti consenta di utilizzare qualsiasi altra funzione e semplificare la tua add function :

 function curry(fn) { var args = Array.prototype.slice.call(arguments, 1); return function () { return fn.apply(this, args.concat( Array.prototype.slice.call(arguments, 0) )); } } function add() { var args = Array.prototype.slice.call(arguments); return args.reduce(function (previousValue, currentValue) { return previousValue + currentValue; }); } 

Ora, se vuoi eseguire questa funzione, dovresti semplicemente:

 var curry1 = curry(add, 1); console.log( curry1(2), // Logs 3 curry1(2, 3), // Logs 6 curry1(4, 5, 6) // Logs 16 ); //You can do this with as many arguments as you want var curry15 = curry(add, 1,2,3,4,5); console.log(curry15(6,7,8,9)); // Logs 45 

Se voglio ancora aggiungere 1, 2, 3 posso solo fare:

 add(1,2,3) //Returns 6, AWESOME! 

Continuare l’approccio funzionale

Questo codice sta diventando riutilizzabile da ogni parte.

È ansible utilizzare la funzione curry per creare altri riferimenti a funzioni al curry senza alcun problema aggiuntivo.

Seguendo il tema della matematica, diciamo che abbiamo una funzione di moltiplicazione che ha moltiplicato tutti i numeri passati ad essa:

 function multiply() { var args = Array.prototype.slice.call(arguments); return args.reduce(function (previousValue, currentValue) { return previousValue * currentValue; }); } multiply(2,4,8) // Returns 64 var curryMultiply2 = curry(multiply, 2); curryMultiply2(4,8) // Returns 64 

Questo approccio funzionale al curry ti consente di adottare questo approccio per qualsiasi funzione, non solo per quelle matematiche. Sebbene la funzione curry fornita non supporti tutti i casi limite, offre una soluzione semplice e funzionale al problema che può essere facilmente costruita.

Metodo 1: utilizzo partial

Una soluzione semplice sarebbe usare partial come segue:

 Function.prototype.partial = function () { var args = Array.prototype.concat.apply([null], arguments); return Function.prototype.bind.apply(this, args); }; var test = add.partial(1); alert(test(2)); // 3 alert(test(2,3)); // 6 alert(test(4,5,6)); // 16 function add() { var sum = 0; var length = arguments.length; for (var i = 0; i < length; i++) sum += arguments[i]; return sum; } 

C’è un approccio più generico definendo una funzione curry che prende il numero minimo di argomenti quando valuta la funzione interna. Consentitemi di usare prima ES6 (ES5 in seguito), poiché lo rende più trasparente:

 var curry = (n, f, ...a) => a.length >= n ? f(...a) : (...ia) => curry(n, f, ...[...a, ...ia]); 

Quindi definire una funzione che riassume tutti gli argomenti:

 var sum = (...args) => args.reduce((a, b) => a + b); 

allora possiamo farlo, dicendo che dovrebbe aspettare almeno 2 argomenti:

 var add = curry(2, sum); 

Quindi tutto si adatta al luogo:

 add(1, 2, 3) // returns 6 var add1 = add(1); add1(2) // returns 3 add1(2,3) // returns 6 add1(4,5,6) // returns 16 

Puoi anche saltare la creazione di add fornendo il primo argomento (i):

 var add1 = curry(2, sum, 1); 

La versione ES5 di curry non è così carina per la mancanza di ... operatore:

 function curry(n, f) { var a = [].slice.call(arguments, 2); return a.length >= n ? f.apply(null, a) : function () { var ia = [].slice.call(arguments); return curry.apply(null, [n, f].concat(a).concat(ia)); }; } function sum() { return [].slice.call(arguments).reduce(function (a, b) { return a + b; }); }; 

Il rest è lo stesso…

Nota: se l’efficienza è un problema, potresti non voler utilizzare slice sugli arguments , ma copiarlo esplicitamente in un nuovo array.