DrillLab
第 34 / 105 道34 / 105 · #299

什么是柯里化

What is currying

先自己答,再往下看Answer it yourself first

一句话:把「一次收 n 个参数」的函数 改成「每次收一个、返回一个新函数」, 收满了才真正计算。

add(1, 2, 3) 变成add(1)(2)(3)。 实现靠闭包记住已经收到的参数

有什么用(别只说「炫技」):

  • 参数复用——const log = level => msg => console.log(`[${level}] ${msg}`),然后 const warn = log("WARN")
  • 延迟执行—— 参数没收齐就不干活, 适合配置式 API。
  • 函数组合—— 组合要求每个函数只收一个参数, 柯里化正好把多参函数改造成这个形状。

会追问:「柯里化和偏函数(partial application)什么区别?」—— 柯里化严格一次一个; 偏函数是一次固定几个、剩下的以后给bind 就是偏函数)。 这个区分问得不少。

还会让你手写一个通用 curry—— 思路是:参数够了就调,不够就返回一个继续收的函数。

In one line: take a function that receives n arguments at once and reshape it into one that takes one at a time and returns a new function, only computing once they are all in.

add(1, 2, 3) becomes add(1)(2)(3). The implementation rests on a closure remembering the arguments received so far.

What it is good for — do not just say “showing off”:

  • Reusing arguments const log = level => msg => console.log(`[${level}] ${msg}`), then const warn = log("WARN").
  • Deferred execution — nothing runs until every argument has arrived, which suits configuration-style APIs.
  • Function composition — composition wants every function to take a single argument, and currying reshapes multi-argument functions into exactly that.

Follow-up: “What is the difference between currying and partial application?” — currying is strictly one argument at a time; partial application fixes a few now and takes the rest later (bind is partial application). This distinction comes up a lot.

They will also ask you to write a generic curry — the idea is: if you have enough arguments, call the function; if not, return one that keeps collecting.

JavaScript柯里化Currying示意Illustrative
1// 手写通用柯里化:参数够了就算,不够就继续收
2function curry(fn) {
3 return function curried(...args) {
4 if (args.length >= fn.length) return fn.apply(this, args);
5 return (...rest) => curried.apply(this, [...args, ...rest]);
6 };
7}
8
9const add = curry((a, b, c) => a + b + c);
10add(1)(2)(3); // 6
11add(1, 2)(3); // 6
12add(1)(2, 3); // 6
13
14// 实际用途:参数复用
15const log = (level) => (msg) => console.log(`[${level}] ${msg}`);
16const warn = log("WARN");
17warn("磁盘快满了"); // [WARN] 磁盘快满了
1// A generic curry by hand: if there are enough arguments, run; otherwise keep collecting
2function curry(fn) {
3 return function curried(...args) {
4 if (args.length >= fn.length) return fn.apply(this, args);
5 return (...rest) => curried.apply(this, [...args, ...rest]);
6 };
7}
8
9const add = curry((a, b, c) => a + b + c);
10add(1)(2)(3); // 6
11add(1, 2)(3); // 6
12add(1)(2, 3); // 6
13
14// A real use: reusing an argument
15const log = (level) => (msg) => console.log(`[${level}] ${msg}`);
16const warn = log("WARN");
17warn("disk almost full"); // [WARN] disk almost full
关键是 fn.length —— 函数声明时的形参个数。注意带默认值或 ...rest 的参数不计入 length,所以这个通用实现对它们不适用。The key is fn.length —— the number of parameters the function declares. Note that a parameter with a default value, and a ...rest parameter, do not count towards length, so this generic implementation does not work for them.