卷曲一个带有无限参数的函数

时间:2016-01-27 13:43:10

标签: javascript functional-programming

使用ES5,你如何理解一个带有无限参数的函数。

var socket = require('socket.io');

mysocket = socket.connect('http://192.168.1.70:3000');
mysocket.emit('deviceevent', { param1: "update", param2: "0", param3: "1" });

上面的函数只需要三个参数,但我们希望我们的curried版本能够获取无限参数。

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

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

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

4 个答案:

答案 0 :(得分:5)

方法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:单级卷曲

如果你只想要一个级别的currying,那么这就是我要做的事情:

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:无限级别压缩

现在,这是一个更为通用的解决方案,具有无限的currying水平:

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生成的函数是递归curry,允许您根据需要多次应用它。

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方法直接。

答案 1 :(得分:5)

您的add功能不是很好的部分原因&#34;功能性&#34;是因为它试图做的不仅仅是添加传递给它的数字。让其他开发人员查看你的代码,看到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函数来制作其他curried函数引用,而不会有任何额外的麻烦。

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

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

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

答案 2 :(得分:0)

通过定义一个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);
然后我们可以讨论它,告诉它应该等到至少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版本的咖喱对于缺少...运算符而言并不那么漂亮:

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

其余的都一样......

注意:如果需要考虑效率,您可能不想在slice上使用arguments,而是明确地将其复制到新数组。

答案 3 :(得分:0)

这个游戏有点晚,但这是我的两分钱。基本上,这利用了一个事实,即函数也是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;
}