什么是函数柯里化

函数柯里化(Currying)是一种在函数式编程中使用的技术,其主要目的是将一个接受多个参数的函数转换成一系列使用一个参数的函数。 这样做的好处是允许你创建一些部分应用的函数,预先固定一些参数,使得代码更简洁,便于复用和组合。

柯里化快速入门

接下来,我们来通过一个简单的示例,让大家快速体会函数柯里化。

我们有一个求和的函数:

function add(x, y) {
    return x + y;
}
console.log(add(1, 2)); // 3
console.log(add(5, 7)); // 12

在上面的示例中,我们有一个 add 函数,接收两个形参,返回两形参的和。在调用的时候,我们每次也需要传递两个参数。

我们对其进行柯里化,如下:

function add(x) {
    return function (y) {
        return x + y;
    }
}
console.log(add(1)(2)); // 3
console.log(add(5)(7)); // 3

在上面的代码中,我们对 add 函数进行了柯里化改造,只接受一个参数,但是返回的也不是值了,而是返回一个函数,这个函数也接收一个参数,然后利用闭包的特性,可以访问到最开始传入的 x 的值,最终返回 x 和 y 的和。

通过上面的这个示例,我们能够体会到前面所说的柯里化函数的特点:

一个柯里化的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。

柯里化有哪些应用场景和优势

函数柯里化在函数式编程中有很多应用场景和优势

以下是一些常见的应用场景和优势:

参数复用

柯里化可以使我们预先固定一些参数,形成一个部分应用的函数,这样可以将相同参数的重复使用降到最低。这有利于减少参数传递的冗余,使代码更简洁。

代码示例:

function multiply(a, b) {
  return a * b;
}

const double = curry(multiply)(2);
const triple = curry(multiply)(3);

console.log(double(5)); // 10
console.log(triple(5)); // 15

延迟计算

柯里化允许我们将函数调用分批进行,而不是一次性传递所有参数。这样,我们可以在需要的时候进行最后的计算,提高性能。

代码示例:

const data = [1, 2, 3, 4, 5];
const curriedFilter = curry((predicate, arr) => arr.filter(predicate));

const greaterThanThree = (num) => num > 3;
const filterGreaterThanThree = curriedFilter(greaterThanThree);

// 延迟计算:先创建过滤函数,最后传入数据时才执行
const result = filterGreaterThanThree(data);
console.log(result); // [4, 5]

代码组合和复用

柯里化有助于创建可以被复用或组合成更复杂形式的函数。这使我们能够构建更加模块化和可扩展的代码库。

代码示例:

const curriedMap = curry((fn, arr) => arr.map(fn));

const doubleAll = curriedMap(double);
const tripleAll = curriedMap(triple);

console.log(doubleAll([1, 2, 3])); // [2, 4, 6]
console.log(tripleAll([1, 2, 3])); // [3, 6, 9]

更易读的代码

柯里化技术可以让我们的代码更加模块化和函数式,进而提高代码的可读性。柯里化函数更加聚焦于单一职责,这样可以让代码逻辑更清晰。

一道经典的柯里化面试题

实现一个 add 方法,使计算结果能够满足如下预期:

add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;

要完成上面的需求,我们就可以使用柯里化函数:

function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = Array.prototype.slice.call(arguments);

    // 在内部声明一个函数,利用闭包的特性保存 _args 并收集所有的参数值
    var _adder = function () {
        _args.push(...arguments);
        return _adder;
    };

    // 这个是最后输出的时候被调用的,return 后面如果是函数体,
    // 为了输出函数体字符串会自动调用 toString 方法
    // 利用 toString 隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    _adder.toString = function () {
        return _args.reduce(function (a, b) {
            return a + b;
        });
    }

    // 这个 return 是第一次调用的时候返回上面的函数体,
    // 这样后面所有的括号再执行的时候就是执行 _adder 函数体
    return _adder;
}
console.log(add(1)(2)(3).toString()); // 6
console.log(add(1, 2, 3)(4).toString()); // 10
console.log(add(1)(2)(3)(4)(5).toString()); // 15
console.log(add(2, 6)(1).toString()); // 9

面试回答:什么是函数柯里化?

参考答案:

柯里化(currying)又称部分求值。一个柯里化的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。

举个例子,就是把原本:

function(arg1,arg2) 变成 function(arg1)(arg2) function(arg1,arg2,arg3) 变成 function(arg1)(arg2)(arg3) function(arg1,arg2,arg3,arg4) 变成 function(arg1)(arg2)(arg3)(arg4)

总而言之,就是将:

function(arg1,arg2,…,argn) 变成 function(arg1)(arg2)…(argn)