Curry de una función que toma infinitos argumentos

7 minutos de lectura

avatar de usuario
tiempo de ejecución cero

Usando ES5, ¿cómo curras una función que toma infinitos argumentos?

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

La función anterior toma solo tres argumentos, pero queremos que nuestra versión curry pueda tomar infinitos argumentos.

Por lo tanto, de todos los siguientes casos de prueba deben pasar:

var test = add(1);

test(2);     //should return 3
test(2,3);   //should return 6
test(4,5,6); //should return 16

Aquí está la solución que se me ocurrió:

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

Sin embargo, me han dicho que no tiene un estilo muy “funcional”.

  • Puede que te falte un + en el primer fragmento de código: por return a + b c; Quieres decir return a + b + c;?

    – despollo

    27/01/2016 a las 14:00

  • @unpollo .. se corrigió el error tipográfico

    – tiempo de ejecución cero

    27 de enero de 2016 a las 14:01

  • ¿Por qué quieres hacer eso?

    – Aadit M Shah

    28 de enero de 2016 a las 14:54

  • Mejor muévete a haskell aunque.

    – candelero

    9 de febrero de 2018 a las 8:31

  • prueba(2); devolvió 4 al usar su código。 sum += totalArguments[0]; 0 debería ser i

    – bucle

    08/03/2018 a las 13:40

avatar de usuario
Aadit M Shah

Método 1: Usar partial

Una solución simple sería usar partial como sigue:

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

Método 2: curry de un solo nivel

Si solo desea un nivel de curry, esto es lo que haría:

var test = add(1);

alert(test(2));     // 3
alert(test(2,3));   // 6
alert(test(4,5,6)); // 16

function add() {
    var runningTotal = 0;
    var length = arguments.length;
    for (var i = 0; i < length; i++)
        runningTotal += arguments[i];

    return function () {
        var sum = runningTotal;
        var length = arguments.length;
        for (var i = 0; i < length; i++)
            sum += arguments[i];
        return sum;
    };
}

Método 3: curry de nivel infinito

Ahora, aquí hay una solución más general con infinitos niveles de curry:

var add = running(0);

var test = add(1);

alert(+test(2));     // 3
alert(+test(2,3));   // 6
alert(+test(4,5,6)); // 16

function running(total) {
    var summation = function () {
        var sum = total;
        var length = arguments.length;
        for (var i = 0; i < length; i++)
            sum += arguments[i];
        return running(sum);
    }

    summation.valueOf = function () {
        return total;
    };

    return summation;
}

UN total acumulado es el resultado intermedio de un suma. Él running función devuelve otra función que se puede tratar como un número (por ejemplo, puede hacer 2 * running(21)). Sin embargo, debido a que también es una función, puede aplicarla (por ejemplo, puede hacer running(21)(21)). Funciona porque JavaScript usa el valueOf método para obligar automáticamente a los objetos a convertirse en primitivos.

Además, la función producida por running se procesa recursivamente, lo que le permite aplicarlo tantas veces a tantos argumentos como desee.

var resultA = running(0);
var resultB = resultA(1,2);
var resultC = resultB(3,4,5);
var resultD = resultC(6,7,8,9);

alert(resultD + resultD(10)); // 100

function running(total) {
    var summation = function () {
        var sum = total;
        var length = arguments.length;
        for (var i = 0; i < length; i++)
            sum += arguments[i];
        return running(sum);
    }

    summation.valueOf = function () {
        return total;
    };

    return summation;
}

Lo único que debe tener en cuenta es que a veces debe forzar manualmente el resultado de running en un número ya sea aplicando el operador unario más a él o llamando a su valueOf método directamente.

  • el requisito es no pasar una entrada de matriz. Además, no estoy buscando a alguien que redefina el problema… pero más de lo que sería una solución adecuada… gracias por intentarlo…

    – tiempo de ejecución cero

    28 de enero de 2016 a las 15:48

  • @SMV Actualicé mi respuesta. ¿Es esa una solución adecuada a su problema?

    – Aadit M Shah

    28/01/2016 a las 16:45

avatar de usuario
KevBot

Parte de la razón por la que su add La función no es muy “funcional” se debe a que intenta hacer más que simplemente sumar los números que se le pasan. Sería confuso para otros desarrolladores mirar su código, ver un add función, y cuando la llaman, obtienen una función que se les devuelve en lugar de la suma.

Por ejemplo:

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

El enfoque funcional

El enfoque funcional sería crear una función que le permita curry cualquier otra función, y simplifica tu 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;
    });
}

Ahora, si desea curry esta función, simplemente haría:

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

Si todavía quiero agregar 1, 2, 3 solo puedo hacer:

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

Continuando con el enfoque funcional

Este código ahora se está volviendo reutilizable en todas partes.

Puede usar esa función curry para hacer referencias a otras funciones curry sin ningún problema adicional.

Siguiendo con el tema de las matemáticas, digamos que teníamos una función de multiplicación que multiplicaba todos los números que se le pasaban:

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

Este enfoque de curry funcional le permite aplicar ese enfoque a cualquier función, no solo a las matemáticas. Aunque el suministrado curry La función no es compatible con todos los casos extremos, ofrece una solución funcional y simple a su problema que se puede desarrollar fácilmente.

avatar de usuario
Sindhu

Similar al problema anterior. Suma de curry de nivel n por recursión

Truco: Para detener la recursividad estoy pasando last () como en blanco**

function sum(num1) {
        return (num2) => {
            if(!num2) {
                return num1;
            }
            return sum(num1 + num2);
        }
    }
console.log('Sum :', sum(1)(2)(3)(4)(5)(6)(7)(8)())

avatar de usuario
jJ’

Existe un enfoque más genérico al definir una función curry que toma un número mínimo de argumentos cuando evalúa la función interna. Permítanme usar ES6 primero (ES5 después), ya que lo hace más transparente:

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

Luego defina una función que sume todos los argumentos:

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

luego podemos curry, diciéndole que debe esperar hasta al menos 2 argumentos:

var add = curry(2, sum);

Entonces todo encaja en su lugar:

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

Incluso puede omitir la creación add proporcionando los primeros argumentos:

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

La versión ES5 de curry no es tan bonita por la falta de ... operador:

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

El resto es lo mismo…

Nota: si la eficiencia es una preocupación, es posible que no desee utilizar slice sobre argumentspero cópielo explícitamente en una nueva matriz.

Un poco tarde en este juego, pero aquí están mis dos centavos. Básicamente, esto explota el hecho de que las funciones también son objetos en JavaScript.

function add(x) {
  if (x === undefined) {
    return add.numbers.reduce((acc, elem) => acc + elem, 0);
  } else {
    if (add.numbers) {
      add.numbers.push(x);
    } else {
      add.numbers = [x];
    }
  }
  return add;
}

avatar de usuario
ouflak

Suma infinita con curry, puede pasar un solo parámetro o múltiples hasta infinito:

function adding(...arg) {

    return function clousureReturn(...arg1) {
        if (!arguments.length) {
            let finalArr = [...arg, ...arg1];
            let total = finalArr.reduce((sum, ele) => sum + ele);
            return total;
        }

        return adding(...arg, ...arg1)
    }
}

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad