在一些函数式编程语言里面,对函数的描述不是被调用,而是被应用。而在JS里面,我们可以用Function.prototype.apply()来“应用”一个函数。

// define a function
var sayHi = function (who) {
    return "Hello" + (who ? ", " + who : "") + "!";
};

// invoke a function
sayHi(); // "Hello"
sayHi('world'); // "Hello, world!"

// apply a function
sayHi.apply(null, ["hello"]); // "Hello, hello!"

以上我们可以认为,调用一个函数实际上就是在给它应用一堆参数。那是否可以只传一部分参数而不传全部呢?

假设有一个函数add(),用于将x与y相加,下面是计算步骤:

// for illustration purposes
// not valid JavaScript

// we have this function
function add(x, y) {
    return x + y;
}

// and we know the arguments
add(5, 4);

// step 1 -- substitute one argument
function add(5, y) {
    return 5 + y;
}

// step 2 -- substitute the other argument
function add(5, 4) {
    return 5 + 4;
}

step1就是部分应用:我们只给它应用了一个参数。

执行一个部分应用的时候,并不能获得结果,而是另外一个函数。我们假设有一个函数curry(),它处理add()的部分应用:

var add = function (x, y) {
    return x + y;
};

// full application
add(5, 4); // 9

//a new function
var newadd = curry(add, 5);

// applying an argument to the new function
newadd(4); // 9

部分应用给了我们另外一个函数,这个函数在稍后调用的时候可以接收其他参数。

让函数理解并处理部分应用的过程,叫做柯里化(Currying)。


我们试着让add()本身来处理部分应用:

// a curried add

function add(x, y) {
    if (typeof y === "undefined") { 
        return function (y) {
            return x + y;
        };
    }

    // full application
    return x + y;
}

返回的函数创建了闭包,能访问外部函数的参数。

进一步考虑:能不能对任意一个函数进行处理,得到一个新函数,使它能够处理部分参数?(即上面假设的curry()函数)下面实现curry()函数:

function curry(fn) {
    var slice = Array.prototype.slice,
    stored_args = slice.call(arguments, 1);
    return function () {
        var new_args = slice.call(arguments),
        args = stored_args.concat(new_args);
        return fn.apply(null, args);
    };
}

❶curry()有点复杂。注意到在JS中arguments不是一个真正的数组,利用Array.prototype.slice()将其转换成数组,以便更好地操作。

❷当curry第一次调用的时候,stored_args存储了调用时除去第一个之外的参数,因为第一个参数是要被柯里化的函数。

❸curry()返回了一个新函数,当这个新函数调用时,它能过闭包访问到已经存储了部分参数的stored_args和slice。

❹最后,新函数只需合并老的部分应用的参数(stored_args)和新的参数(new_args),将其应用到原来的函数fn(通过闭包访问)即可。

下面是一些测试:

// a normal function
function add(x, y) {
    return x + y;
}

// curry a function to get a new function
var newadd = curry(add, 5);
newadd(4); // 9

// another option -- call the new function directly
curry(add, 6)(7); // 13

————————————————————————————————

// a normal function
function add(a, b, c, d, e) {
    return a + b + c + d + e;
}

// works with any number of arguments
curry(add, 1, 2, 3)(5, 5); // 16

// two-step currying
var addOne = curry(add, 1);
addOne(10, 10, 10, 10); // 41
var addSix = curry(addOne, 2, 3);
addSix(5, 5); // 16
posted on 2013-03-04 01:20  no_particular  阅读(751)  评论(1编辑  收藏  举报