【问题标题】:Currying a function that takes infinite arguments柯里化一个接受无限参数的函数
【发布时间】:2021-09-05 21:35:08
【问题描述】:

使用 ES5,如何 curry 一个接受无限参数的函数。

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

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

【问题讨论】:

  • 您可能在第一个代码块中缺少+return a + b c; 是指return a + b + c;
  • @unpollo ..typo 已修复
  • 你为什么要这么做?
  • 不过最好转到haskell
  • 测试(2);使用您的代码时返回 4。 sum += totalArguments[0]; 0 应该是i

标签: javascript functional-programming


【解决方案1】:

方法一:使用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;
}

方法 2:单级柯里化

如果你只想要一个级别的柯里化,那么我会这样做:

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

方法3:无限级柯里化

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

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 totalsummation 的中间结果。 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 的结果强制转换为一个数字,方法是对其应用unary plus operator 或直接调用其valueOf 方法。

【讨论】:

  • 要求不传递数组输入。另外,我不是在寻找重新定义问题的人..但更多合适的解决方案将是..感谢您的尝试..
  • @SMV 我更新了我的答案。这是解决您问题的合适方法吗?
【解决方案2】:

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

现在,如果你想 curry 这个函数,你可以这样做:

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 函数不支持所有边缘情况,但它为您的问题提供了一个实用、简单的解决方案,可以轻松构建。

【讨论】:

    【解决方案3】:

    类似于上面的问题。递归第n级curry之和

    技巧:为了停止递归,我将 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)())

    【讨论】:

      【解决方案4】:

      通过定义一个 curry 函数,它在计算内部函数时采用最少数量的参数,有更通用的方法。让我先使用 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);
      

      然后我们可以对它进行 curry,告诉它应该等到至少 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;
          });
      };
      

      其他的都一样……

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

      【讨论】:

      • curry(n, f, ...[...a, ...ia]) 等价于curry (n, f, ...a, ...ia)
      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

        带柯里化的无限和,你可以传递单个参数或多个直到无穷大:

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

        【讨论】:

        • 请在您的回答中提供更多详细信息。正如目前所写的那样,很难理解您的解决方案。
        猜你喜欢
        • 1970-01-01
        • 2016-01-01
        • 1970-01-01
        • 2021-02-07
        • 2012-10-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多