什么是函数柯里化

在维基百科中,对函数柯里化有着这样的定义:

在计算机科学领域,柯里化(Currying)指的是将一个接受多个参数的函数转化为一个仅接受单一参数(即原函数的第一个参数)的函数,并且该函数会返回一个新函数,这个新函数能够接收剩余的参数并最终返回计算结果。

由此可见,函数柯里化是一种函数变形的思想,它本身并不会直接执行函数,函数柯里化的思想可在多种编程语言中实现,但实现方式因语言而异。上述解释已经十分通俗易懂了。简单来讲,当传入的参数数量不足时,柯里化会借助闭包将当前传入的参数保存起来,然后返回一个新函数。后续这个新函数可以继续被调用以获取更多参数,新传入的参数会与闭包中保存的参数合并。当参数数量达到被柯里化函数的形参数量时,函数就会开始执行;若参数数量仍然不足,则会继续返回新函数来保存参数,如此循环往复。下面以 JavaScript 为例进行说明:

function add(...nums) {
  return nums.reduce((acc, num) => acc + num, 0);
}

// 对该函数进行柯里化处理后,就可以将多个参数拆分成单个或多个参数组合的形式传入,最终得到相同的结果。示例如下:
curryingAdd(1)(2)(3)(4); // 结果为 10
// 或者
curryingAdd(1, 2)(3)(4); // 结果为 10
// 又或者
curryingAdd(1)(2, 3, 4); // 结果为 10

柯里化的好处

乍看上述例子,你可能会觉得函数柯里化似乎没什么实际用途,反而让实现过程变得更加复杂。但实际上,它是一种高度抽象的编程规范,属于函数式编程思想的一部分,主要有以下优点:

参数复用

此外,柯里化还具备“提前确认”和“延迟执行”的特性。“提前确认”强调的是提前固定部分参数,明确函数的部分行为,例如在网络请求中提前确定请求方法;“延迟执行”强调的是函数不会立即执行,而是等所有必要参数都传入后才执行。它们虽然侧重点不同,但都体现了柯里化在灵活控制函数调用上的优势。在我看来,参数复用是函数柯里化最为显著的优势。合理运用这一特性,能够编写出更加优雅的代码,让程序的执行过程更贴合人类解决问题的思维方式。下面通过一段代码来进一步说明:

function ajax(method, url) {
  // 此处为 HTTP 请求的具体实现代码...
  console.log(`Sending ${method} request to ${url}`);
}

function currying(fn, ...args) {
  if (args.length >= fn.length) {
    return fn(...args);
  } else {
    return (...args2) => currying(fn, ...args, ...args2);
  }
}

// 对 ajax 方法进行柯里化处理
let curryingAjax = currying(ajax);

// 得到一个专门用于处理 GET 请求的函数,该操作仅需执行一次
let getAjax = curryingAjax("get");

// 结合具体业务逻辑,获取用户信息的接口请求
let getUserInfo = getAjax("http://api.test.com/getUserInfo");

// 结合具体业务逻辑,获取订单列表的接口请求
let getOrderList = getAjax("http://api.test.com/getOrderList");

// 如果是 post 请求
let postAjax = curryingAjax("post");
let postData = postAjax("http://api.test.com/submitData");

从上述代码可以看出,get 方法仅需传入 curryingAjax 函数一次,后续就可以多次调用 get 请求。先确定请求方法,再确定具体的请求地址,这样就实现了参数的复用。

柯里化完整代码

柯里化函数 currying 的实现逻辑如下:

  • 首先判断从 currying 函数的第二个参数开始到最后一个参数(即 args)的长度。若该长度等于被柯里化函数 fn 的形参数量,那么直接执行 fn 函数,并将 args 作为参数传入。
  • 若传入的参数(args)数量不足,就利用递归和闭包的特性,暂时保存已传入的参数,然后重新返回 currying 函数。
function currying(fn, ...args) {
  if (args.length >= fn.length) {
    return fn(...args);
  } else {
    return (...args2) => currying(fn, ...args, ...args2);
  }
}

// 使用示例
const add = (a, b, c) => {
  console.log(a + b + c);
};
const currying_add = currying(add);

// 不同参数传入方式的结果展示
currying_add(1, 2, 3);
currying_add(1)(2, 3);
currying_add(1, 2)(3);
currying_add(1)(2)(3);