柯里化一个接受无限参数的函数

IT技术 javascript functional-programming
2021-02-27 10:31:47

使用 ES5,你如何对一个带有无限参数的函数进行柯里化。

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

上面的函数只接受三个参数,但我们希望柯里化版本能够接受无限参数。

因此,以下所有测试用例都应该通过:

var test = add(1);

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

这是我想出的解决方案:

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

但是,有人告诉我,它的风格不是很“实用”。

6个回答

您的add函数不是很“实用”的部分原因是它试图做的不仅仅是将传递给它的数字相加。如果其他开发人员查看您的代码,查看一个add函数,并且当他们调用它时,返回一个函数而不是总和,这会让其他开发人员感到困惑

例如:

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

函数式方法

函数式方法是创建一个函数,允许您对任何其他函数进行柯里化,并简化您的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;
    });
}

现在,如果你想咖喱这个函数,你只需要:

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

如果我还想加起来1, 2, 3,我可以这样做:

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

继续功能方法

此代码现在可以从任何地方重用。

您可以使用该 curry 函数来创建其他 curry 函数引用,而无需任何额外的麻烦。

坚持数学主题,假设我们有一个乘法函数,它将所有传递给它的数字相乘:

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

这种函数式柯里化方法允许您将这种方法用于任何函数,而不仅仅是数学函数。尽管提供的curry函数不支持所有边缘情况,但它为您的问题提供了一个功能性的、简单的解决方案,可以轻松构建。

方法一:使用 partial

一个简单的解决方案是使用partial如下:

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

方法二:单级柯里化

如果你只想要一级咖喱,那么这就是我会做的:

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

方法三:无限级柯里化

现在,这里有一个更通用的解决方案,具有无限级的柯里化:

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

运行总数是一个的中间结果求和running函数返回另一个可以被视为数字的函数(例如你可以做2 * running(21))。然而,因为它也是一个函数,所以你可以应用它(例如你可以做running(21)(21))。它之所以有效,是因为 JavaScript 使用该valueOf方法自动将对象强制转换为基元。

此外,由 产生的函数running是递归柯里化的,允许您根据需要多次将其应用于任意数量的参数。

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

您唯一需要注意的是,有时您需要running通过对其应用一元加运算符valueOf直接调用其方法来手动将结果强制转换为数字

要求不是传递数组输入。此外,我不是在寻找重新定义问题的人......但更多的是合适的解决方案是什么......谢谢你的尝试......
2021-04-24 10:31:47
@SMV 我更新了我的答案。这是您问题的合适解决方案吗?
2021-05-06 10:31:47

有一种更通用的方法是定义一个咖喱函数,该函数在评估内部函数时采用最少数量的参数。让我先使用 ES6(稍后使用 ES5),因为它使它更透明:

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

然后定义一个对所有参数求和的函数:

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

然后我们可以咖喱它,告诉它应该等到至少 2 个参数:

var add = curry(2, sum);

然后一切就绪:

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

您甚至可以add通过提供第一个参数来跳过创建

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

由于缺少...运算符,ES5 版本的 curry 并不漂亮

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

其余都一样...

注意:如果考虑效率,您可能不想使用sliceon arguments,而是将其显式复制到新数组中。

curry(n, f, ...[...a, ...ia]) 相当于 curry (n, f, ...a, ...ia)
2021-05-07 10:31:47

这场比赛有点晚了,但这是我的两分钱。基本上,这利用了函数也是 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;
}

类似于上面的问题。递归的第n级咖喱的总和

技巧:为了停止递归,我将 last () 作为空白传递**

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