JavaScript 的秘密生活:Currying vs. Partial Application
Source: Dev.to
实用方法:部分应用(Partial Application)
// A standard, multi‑argument function
function multiply(a, b) {
return a * b;
}
// Manually creating partial application via a closure factory
function createMultiplier(factor) {
// We "lock in" the 'factor' argument here
return function (number) {
return number * factor;
};
}
// Usage
const double = createMultiplier(2); // We applied '2' to the first spot
console.log(double(10)); // 20
部分应用是一种 手动 技巧:你预先填入通用函数的某些参数,生成一个等待剩余参数的新函数。
Original Function: [ Slot A ] [ Slot B ] [ Slot C ]
Partial Application:
We choose to fill Slot A now.
Resulting Function: [ 10 ] [ Slot B ] [ Slot C ]
结构化方法:柯里化(Currying)
柯里化是对函数签名的 结构性转换。它把多参数函数转换为一系列一元(单参数)函数的链式调用。
const formatUrl = protocol => domain => path => `${protocol}://${domain}/${path}`;
// Partial Application – manual process
// We can fill any arguments we want, in any order we design
const addFive = (b, c) => add(5, b, c);
// Currying – structural transformation
// It forces a rigid chain: one arg at a time
const curriedAdd = a => b => c => a + b + c;
// You MUST call it like this:
curriedAdd(1)(2)(3);
尝试打破链式调用会失败:
// This won't work as expected with a truly curried function:
// It returns a function expecting 'path', it does NOT use 'myblog.com' as the domain yet!
formatUrl('https', 'myblog.com');
类比:三明治制作师 vs. 装配线
部分应用的三明治制作师
你有一个通用的三明治配方,需要 面包、肉 和 奶酪。如果你已经知道今天的面包是酸面包,就提前把酸面包片准备好。你手动“部分应用”了面包这个参数,使后续步骤更快。
柯里化的装配线
想象一个拥有三个工作站的工业三明治机器人:
- 工作站 1 – 只接受 面包,并把结果传递给工作站 2。
- 工作站 2 – 只接受 肉,并把结果传递给工作站 3。
- 工作站 3 – 只接受 奶酪。
你不能同时把面包和肉喂给工作站 1;装配线强制一次只接受一个参数的严格流程。
为什么库里使用柯里化?
因为柯里化可以轻松创建专用的构造器:
// Create a builder specifically for secure sites
const secureUrl = formatUrl('https');
// Create a builder specifically for OUR site
const myBlogUrl = secureUrl('myblog.com');
// Generating links is clean:
const post1 = myBlogUrl('post-1');
const post2 = myBlogUrl('post-2');
柯里化的 formatUrl 让你可以在任意工作站停下来,生成可复用的部分函数,而无需编写新的定义。
常见陷阱:混合模式
// Not truly curried (breaks the single‑argument chain)
const add = a => (b, c) => a + b + c; // returns a function taking TWO args
// Truly curried (maintains single‑argument chain)
const curriedAdd = a => b => c => a + b + c; // each returns a function taking ONE arg
第一个例子是混合式的;真正的柯里化在整个链中保持“一函数一参数”的模式。
Margaret 的速查表
何时使用部分应用:
- 编写标准的 JavaScript。
- 为特定用例专门化函数(例如
createMultiplier)。 - 需要灵活性(例如同时固定第一个 和 第三个参数,而保持第二个参数开放)。
何时使用柯里化:
- 使用函数式库(例如 Ramda、Lodash/fp)。
- 需要最大程度的可组合性(通过管道将数据传递)。
- 想自动生成大量专用版本的函数。
在日常的 JavaScript 开发中,部分应用出现得更频繁,而真正的柯里化则相对少见——但理解两者的区别有助于你在不同场景下选用合适的工具。