各位同学,大家好!
欢迎来到今天的“React 函数式编程实战:柯里化(Currying)大乱斗”讲座。我是你们的讲师,一个既喜欢写优雅代码,又喜欢在深夜吐槽 React 事件处理器的资深前端工程师。
今天我们不聊那些花里胡哨的框架新特性,比如“服务端组件”或者“RSC 的未来”,我们聊点硬核的、能让你在代码审查时让面试官眼前一亮的东西——柯里化。
你可能听过这个词,觉得它是数学系的遗物,或者是 Curry 大师在实验室里搞出来的什么奇怪实验。但实际上,柯里化是 JavaScript(尤其是 React)世界里的一把瑞士军刀。它能让你的回调函数从“一次性用品”变成“可重复利用的精密仪器”。
准备好了吗?系好安全带,我们开始。
第一章:柯里化不是魔法,是闭包的魔法
首先,我们要打破对柯里化的神秘感。柯里化(Currying)听起来很高大上,其实就是把一个多参数的函数,拆解成一系列单参数的函数。
数学上,如果你有 add(x, y),柯里化之后就是 add(x)(y)。
但在 JavaScript 里,这不仅仅是语法糖,它是闭包的完美应用场景。
举个简单的例子:
// 普通函数
function add(a, b) {
return a + b;
}
// 柯里化函数
function curriedAdd(a) {
return function(b) {
return a + b;
};
}
console.log(add(2, 3)); // 5
console.log(curriedAdd(2)(3)); // 5
看,这就是把一个函数像俄罗斯套娃一样包起来。第一个 add 返回了一个新的函数,这个新函数记住了 a 是多少,然后等待 b 的到来。这就像是给变量 a 上了一个锁,只有当你提供 b 的时候,锁才会打开,结果才会出来。
在 React 里,我们经常遇到这样的场景:我们需要传递给子组件一个 onClick 事件处理器。这个处理器可能需要很多参数,比如 ID、用户名、价格、折扣率等等。
如果不使用柯里化,你的代码可能长这样:
// 糟糕的代码:爆炸的回调
<button onClick={() => {
buyItem(item.id, item.name, item.price, item.discount, item.isMember);
}}>
购买
</button>
或者,你需要写一个通用的 handleClick,然后传参进去,但这通常会导致 useCallback 依赖数组变得极其庞大,或者传递 event 对象时手忙脚乱。
柯里化登场,它能帮你把这种“一次性”的回调,变成一个可以配置的“半成品”。
第二章:场景实战——配置化事件处理器
假设我们正在开发一个电商后台,有一个订单列表。我们需要对每一行数据执行操作,比如“编辑”、“删除”或者“发货”。
如果我们不使用柯里化,每次都要在 JSX 里写一堆 onClick={() => handleAction('edit', item.id)}。这不仅丑陋,而且如果逻辑复杂,这行代码会变成“意大利面”。
让我们用柯里化重构一下。
核心思想: 我们创建一个“工厂函数”,它接受一些固定参数(比如 API 地址、请求方法),返回一个专门处理特定事件的函数。
// 1. 定义一个通用的 API 请求工厂(柯里化)
const createApiRequest = (baseUrl) => {
return (endpoint) => {
return (method = 'GET') => {
return (data) => {
console.log(`Sending ${method} request to ${baseUrl}${endpoint} with data:`, data);
// 这里是真实的 fetch 或 axios 逻辑
};
};
};
};
// 2. 为不同的模块创建特定的请求器
const userApi = createApiRequest('https://api.example.com/users');
const orderApi = createApiRequest('https://api.example.com/orders');
// 3. 定义具体的事件处理器
const handleEdit = userApi('/123/edit')('PUT');
const handleDelete = userApi('/456/delete')('DELETE');
const handleStatus = orderApi('/status')('PATCH');
// 4. 在 React 组件中使用
function UserList() {
const user = { id: 123, name: 'Alice' };
return (
<div>
<button onClick={() => handleEdit(user)}>
编辑用户
</button>
<button onClick={() => handleDelete(user)}>
删除用户
</button>
</div>
);
}
看!这就是柯里化的魅力。我们在定义阶段就“固定”了 baseUrl 和 endpoint,剩下的只需要在组件里传 data(用户对象)。代码变得非常干净,逻辑复用性极高。你不需要在每次点击时都重新计算 API 路径,React 的 useCallback 甚至可以优化这些柯里化后的函数。
第三章:防抖与节流——柯里化的经典战场
如果你在 React 社区混过一段时间,你一定对“防抖”和“节流”这两个词耳熟能详。特别是在处理 window.resize、input 输入框或者滚动事件时。
通常我们会写一个通用的 debounce 工具函数,然后把它传进去。但是,如果我们能利用柯里化,把“防抖函数”本身也做成一个柯里化的工厂,那岂不是更爽?
为什么要柯里化防抖?
因为防抖函数通常需要两个参数:func(要防抖的函数)和 delay(延迟时间)。在 React 中,delay 往往是组件的 props 或者状态,它可能会变化。
我们来写一个高阶的、柯里化的防抖 Hook:
import { useRef, useCallback } from 'react';
// 基础防抖函数逻辑
function debounce(func, wait) {
let timeout;
return function(...args) {
const context = this;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
}
// 柯里化工厂:创建一个防抖 Hook
const useDebouncedCallback = (callback, delay) => {
// 使用 useRef 来保存最新的 callback,防止闭包陷阱
const callbackRef = useRef(callback);
// 每次渲染更新 ref
callbackRef.current = callback;
// 返回一个 memoized 的函数
return useCallback((...args) => {
// 这里我们再次利用柯里化的思路,或者直接调用基础防抖逻辑
// 关键在于,我们利用 useRef 确保每次调用的是最新的 callback
debounce((...args) => callbackRef.current(...args), delay)(...args);
}, [delay]); // 依赖 delay,如果 delay 变了,useCallback 会重新生成防抖函数
};
等等,上面的代码有点绕。让我们换个更直观的柯里化例子,比如动态延迟。
假设我们要做一个搜索框,根据输入内容的长度自动调整搜索的延迟时间。输入少,延迟短(秒搜);输入多,延迟长(防抖)。
// 一个通用的防抖函数
const debounce = (fn, delay) => {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
};
// 柯里化应用:根据输入长度动态计算延迟
const createSmartDebounce = (fn) => {
return (...args) => {
const input = args[0]; // 假设第一个参数是输入值
const delay = input.length > 10 ? 1000 : 300; // 长文本延迟1秒,短文本延迟300ms
// 这里的 debounce 是一个闭包,它“捕获”了当前的 delay
// 但因为我们每次调用 createSmartDebounce 都会生成新的 debounce 实例
// 所以 delay 的变化会被“烘焙”进每次调用的防抖器中
return debounce(fn, delay)(...args);
};
};
// React 组件
function SearchComponent() {
const handleSearch = (query) => {
console.log('Searching for:', query);
// 这里可以对接 API
};
const smartSearch = createSmartDebounce(handleSearch);
return (
<input
type="text"
onChange={(e) => smartSearch(e.target.value)}
placeholder="输入点什么试试..."
/>
);
}
在这里,柯里化帮助我们封装了“逻辑判断(根据长度决定延迟)”和“执行逻辑(防抖执行)”之间的桥梁。它让 handleSearch 变得非常纯粹,只负责“搜索”,至于什么时候搜索、搜多快,那是 createSmartDebounce 的事。
第四章:组件组合与属性注入
React 的核心理念之一是“组合优于继承”。柯里化在组件属性注入(Props Injection)方面有着天然的优势。
想象一下,我们有一个通用的 Modal 组件。它需要处理 onOpen、onClose、onConfirm 等事件。
如果每个使用 Modal 的地方都要写一堆 onClick 回调,那简直是噩梦。我们可以利用柯里化,创建一个“属性注入器”。
// 基础 Modal 组件
const Modal = ({ isOpen, title, children, onConfirm, onCancel }) => {
if (!isOpen) return null;
return (
<div className="modal-overlay">
<div className="modal-content">
<h2>{title}</h2>
<div className="body">{children}</div>
<div className="footer">
<button onClick={onCancel}>取消</button>
<button onClick={onConfirm}>确认</button>
</div>
</div>
</div>
);
};
// 柯里化属性注入器
// 这个函数接收 Modal 的配置,返回一个“增强版”的 Modal
const createEnhancedModal = (modalConfig) => {
return (Component) => {
return (props) => {
// 在这里,我们可以利用柯里化逻辑,预设一些 props
const enhancedProps = {
...props,
onConfirm: () => {
console.log('Default confirm logic');
if (props.onConfirm) props.onConfirm();
},
onCancel: () => {
console.log('Default cancel logic');
if (props.onCancel) props.onCancel();
}
};
// 这里的 Component 可能是 Modal,也可能是其他组件
return <Component {...enhancedProps} />;
};
};
};
// 使用示例
// 我们创建一个“带确认按钮的 Modal”
const ConfirmModal = createEnhancedModal({ title: '确认操作' })(Modal);
// 使用
function DeleteButton({ itemId }) {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button onClick={() => setIsOpen(true)}>删除</button>
<ConfirmModal
isOpen={isOpen}
title="确定要删除吗?"
onConfirm={() => {
deleteItem(itemId);
setIsOpen(false);
}}
/>
</>
);
}
等等,上面的代码有点过于复杂了,而且 createEnhancedModal 返回了一个 HOC(高阶组件),这虽然也是柯里化的一种形式(柯里化常用于 HOC),但在 React 中,HOC 已经有点过时了。
让我们换一个更“函数式”的例子:高阶组件工厂。
假设我们要给所有的按钮组件添加“点击波纹效果”或者“日志记录”。
// 核心逻辑:给组件添加日志
const withLogging = (WrappedComponent, componentName) => {
return (props) => {
return (
<WrappedComponent
{...props}
onClick={(e) => {
console.log(`[${componentName}] Clicked at ${e.clientX}, ${e.clientY}`);
// 也可以选择不调用原始 onClick,或者先调用原始 onClick 再记录
if (props.onClick) props.onClick(e);
}}
/>
);
};
};
// 柯里化调用:我们可以先固定组件名,再应用 HOC
const LogButton = withLogging('LogButton');
// 或者更灵活一点,允许自定义日志函数
const withLogger = (loggerFn) => {
return (WrappedComponent) => {
return (props) => {
return (
<WrappedComponent
{...props}
onClick={(e) => {
loggerFn(e);
if (props.onClick) props.onClick(e);
}}
/>
);
};
};
};
// 使用
const SmartButton = withLogger((e) => {
console.log('Smart Log:', e.target.innerText);
})(Button);
在这里,withLogger 就是一个柯里化函数。它接受 loggerFn(参数1),返回一个 HOC(参数2),这个 HOC 接受 WrappedComponent(参数3),最终返回一个组件。
这种模式在处理复杂的业务逻辑时非常有效。你可以把它想象成给函数套上了一件一件的“外衣”。外衣1负责打日志,外衣2负责防抖,外衣3负责错误捕获。每一层都只关心自己的事,通过柯里化层层传递,最终得到一个功能完备的组件。
第五章:深入闭包陷阱——柯里化的双刃剑
同学们,这里我要敲黑板了。柯里化虽然好用,但它有个大杀器——闭包。
在 React 中,我们经常在组件内部定义函数。如果这些函数被柯里化了,并且被 useCallback 或者事件监听器捕获,那么它们会“记住”它们被创建时的环境。
这在某些情况下是好事(保持状态),在某些情况下是坏事(数据过时)。
场景: 我们有一个“计数器组件”,我们想利用柯里化来创建“加1”、“减1”和“重置”的函数,并把它们传给子组件。
function Counter() {
const [count, setCount] = useState(0);
// 这是一个柯里化函数:它接收一个操作符,返回一个处理函数
const createOperator = (operator) => {
return () => {
setCount(prev => {
if (operator === 'add') return prev + 1;
if (operator === 'sub') return prev - 1;
return 0;
});
};
};
const handleAdd = createOperator('add');
const handleSub = createOperator('sub');
return (
<div>
<h1>Count: {count}</h1>
<button onClick={handleAdd}>+1</button>
<button onClick={handleSub}>-1</button>
{/* 把这些函数传给一个只显示 count 的子组件 */}
<DisplayCount count={count} />
</div>
);
}
// 子组件
function DisplayCount({ count }) {
console.log('DisplayCount rendered, count:', count);
return <div>Current Count: {count}</div>;
}
在这个例子中,handleAdd 和 handleSub 是稳定的(只要 createOperator 不变)。它们通过闭包捕获了 setCount 和 operator。这很好,因为 React 不会因为 handleAdd 的引用变化而重新渲染子组件(假设子组件只是渲染)。
但是,如果我们改变一下逻辑呢?
function AdvancedCounter() {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
// 问题来了:如果 step 是动态的,这个柯里化逻辑还能用吗?
// 如果我们想基于 step 来加减,我们不能在 createOperator 里直接写死 step
// 因为我们每次 render 都会重新创建 createOperator
}
这就引出了柯里化在 React 中最大的坑:依赖追踪。
当你写一个柯里化函数时,你必须非常清楚:哪些变量是在闭包内部捕获的?哪些变量应该作为参数传递进来?
解决方案: 柯里化函数应该接收它需要的所有外部变量作为参数,而不是去“偷看”组件的状态。
// 改进版:柯里化函数显式接收依赖
const createOperator = (operator, step) => {
return () => {
setCount(prev => {
// 这里我们用到了 step,但 step 是作为参数传进来的,所以它是稳定的
// 只要 step 不变,这个函数的闭包就是“干净”的
if (operator === 'add') return prev + step;
return prev - step;
});
};
};
function AdvancedCounter() {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
// 每次 step 变化,handleAdd 都会重新生成,因为它依赖 step
// 这在 React 18 的并发模式下可能会导致一些意想不到的执行顺序
const handleAdd = createOperator('add', step);
return (
<div>
<button onClick={() => setStep(s => s + 1)}>Step +1</button>
<button onClick={handleAdd}>Add Step</button>
<div>{count}</div>
</div>
);
}
看,这里我们利用柯里化将 step 作为参数注入。这比直接在 handleAdd 里写 step 要好,因为 step 是可控的。但是,这也意味着 handleAdd 的引用会随着 step 的变化而变化。
如何优化? 我们需要结合 useCallback。
const handleAdd = useCallback(() => {
setCount(prev => prev + step);
}, [step]); // 依赖 step
// 但是!如果 step 是一个对象或者复杂对象,[step] 会失效。
// 这时候,柯里化可能反而成了累赘,不如直接写函数体。
所以,柯里化不是万能药。如果你的依赖项变化极其频繁,或者依赖项是对象引用,直接在 useCallback 里写逻辑可能比柯里化更直观。
第六章:实战演练——构建一个“表单验证器”
为了展示柯里化在处理复杂逻辑时的威力,我们来搞个大项目:一个动态表单验证器。
我们需要验证:必填、邮箱格式、长度限制、自定义正则。
如果不用柯里化,你可能需要写一堆 if-else 或者复杂的 switch。
如果我们用柯里化,我们可以把每个验证规则变成一个“单参数函数”,然后像乐高积木一样把它们组合起来。
// 1. 定义基础验证规则(柯里化)
const isRequired = (value) => {
return {
isValid: value !== null && value !== undefined && value !== '',
error: 'This field is required'
};
};
const isEmail = (value) => {
const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/;
return {
isValid: emailRegex.test(value),
error: 'Invalid email format'
};
};
const minLength = (length) => {
return (value) => {
return {
isValid: value.length >= length,
error: `Minimum length is ${length}`
};
};
};
// 2. 组合器:将多个验证器串联起来
const composeValidators = (...validators) => {
return (value) => {
// 找到第一个失败的验证器,并返回它的错误信息
// 如果都通过了,返回 { isValid: true, error: null }
return validators.reduce((acc, validator) => {
return acc.isValid ? validator(value) : acc;
}, { isValid: true, error: null });
};
};
// 3. React 组件使用
function UserForm() {
const [formData, setFormData] = useState({
username: '',
email: ''
});
const [errors, setErrors] = useState({});
// 定义验证逻辑
const validate = composeValidators(
isRequired,
minLength(3)
);
const handleInputChange = (e) => {
const { name, value } = e.target;
// 这里我们直接使用柯里化后的验证器
const result = validate(value);
setFormData(prev => ({
...prev,
[name]: value
}));
setErrors(prev => ({
...prev,
[name]: result.error
}));
};
const handleSubmit = (e) => {
e.preventDefault();
if (errors.username || errors.email) {
alert('Please fix errors');
return;
}
alert('Submitted!');
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Username</label>
<input
name="username"
value={formData.username}
onChange={handleInputChange}
/>
{errors.username && <span style={{ color: 'red' }}>{errors.username}</span>}
</div>
<div>
<label>Email</label>
<input
name="email"
value={formData.email}
onChange={handleInputChange}
/>
{errors.email && <span style={{ color: 'red' }}>{errors.email}</span>}
</div>
<button type="submit">Submit</button>
</form>
);
}
看这个代码!多么优雅!
isRequired 和 minLength(3) 是两个独立的函数。
composeValidators 是一个高阶函数,它接收任意数量的验证器。
在组件中,我们组合它们,然后调用 validate(value)。
这完全符合函数式编程的范式。而且,如果你想添加一个新的验证规则,比如“必须包含数字”,你只需要写一个 containsNumber 函数,然后加到 composeValidators 的参数里就行了。不需要去修改 validate 函数内部的逻辑。
这就是柯里化和组合函数的力量。它让你的代码具有了组合性和可扩展性。
第七章:性能优化与记忆化
在 React 中,性能优化是永恒的话题。柯里化结合 useCallback 和 useMemo,可以帮我们减少不必要的渲染。
场景: 我们有一个父组件,它渲染了一个列表。列表中的每一项都有一个“操作”按钮。点击按钮会触发一个全局的上下文操作。
const Context = React.createContext();
function App() {
const globalAction = (id) => console.log(`Action on ${id}`);
return (
<Context.Provider value={globalAction}>
<ItemList />
</Context.Provider>
);
}
function ItemList() {
const items = [1, 2, 3, 4, 5];
return (
<ul>
{items.map(item => (
<li key={item}>
{/* 如果不使用柯里化,我们可能需要在这里写一个匿名函数 */}
{/* 柯里化在这里的作用是:我们可以把处理逻辑提取到外部 */}
<Item item={item} />
</li>
))}
</ul>
);
}
function Item({ item }) {
// 获取上下文
const globalAction = useContext(Context);
// 使用柯里化来处理事件
// 这样我们可以把函数的创建逻辑放在组件外部,或者在组件内部用 useCallback 包裹
const handleAction = useCallback((id) => {
globalAction(id);
}, [globalAction]); // 依赖 globalAction
return (
<div>
<span>Item {item}</span>
<button onClick={() => handleAction(item)}>Do Something</button>
</div>
);
}
在这个例子中,handleAction 是一个柯里化后的函数(虽然这里只有一个参数,但逻辑是一样的)。通过把它提取出来或者用 useCallback 包裹,我们确保了只要 globalAction 不变,handleAction 的引用就不变。这防止了 Item 组件的 onClick 属性在每次渲染时都生成一个新的函数引用,从而导致 Item 组件不必要的重渲染。
第八章:进阶技巧——参数对象化与工厂模式
有时候,柯里化会让代码变得难以阅读(过度嵌套的括号)。比如 a(b)(c)(d)。
这时候,我们可以引入一个中间层:参数对象化。
我们可以写一个 withConfig 工具函数,它接收一个配置对象,然后将其拆解为柯里化的参数。
// 工具函数:将配置对象柯里化
const curryObject = (fn, config) => {
return (...args) => {
// 假设 config 包含一些默认值
const { defaultValue, transform } = config;
// 应用配置
const processedArgs = args.map(arg => transform ? transform(arg) : arg);
// 调用原函数
return fn(...processedArgs);
};
};
// 模拟 React 的 useState
const useState = (initialValue) => {
let state = initialValue;
const listeners = [];
return {
getState: () => state,
setState: (newValue) => {
state = newValue;
listeners.forEach(listener => listener(state));
},
subscribe: (listener) => {
listeners.push(listener);
}
};
};
// 使用柯里化工厂来包装 setState
const createComponentState = (initial) => {
return curryObject(
(value) => {
// 这里我们模拟 setState 的闭包逻辑
// 实际上这只是一个演示
console.log('State updated to:', value);
},
{
defaultValue: initial,
transform: (v) => v.toUpperCase() // 强制转大写
}
);
};
const myState = createComponentState('hello');
myState('world'); // 会输出 'State updated to: WORLD'
这个例子虽然有点抽象,但它展示了如何利用柯里化来封装“配置逻辑”和“执行逻辑”。
在实际 React 开发中,这通常用于自定义 Hooks 的工厂。
第九章:终极挑战——实现一个轻量级的状态管理中间件
最后,我们来挑战一个稍微复杂一点的东西。假设我们要实现一个类似 Redux 的中间件机制,但是是用纯函数和柯里化实现的。
Redux 的中间件其实就是接收一个 dispatch 函数,返回一个 dispatch 函数。
让我们用柯里化来写一个 Logger 中间件。
// 定义中间件工厂
const applyMiddleware = (...middlewares) => {
return (store) => {
// 初始 dispatch
const dispatch = store.dispatch;
// 初始 state
const getState = store.getState;
// 柯里化链:中间件接收 dispatch 和 getState,返回新的 dispatch
const chain = middlewares.map(middleware =>
middleware(dispatch, getState)
);
// 重写 dispatch 函数
const enhancedDispatch = (...args) => {
// 1. 执行所有前置中间件
let index = 0;
const next = (action) => chain[index++](action);
// 2. 调用原始 dispatch (实际上是 next)
return next(...args);
};
return {
...store,
dispatch: enhancedDispatch
};
};
};
// 定义一个 Logger 中间件
const loggerMiddleware = (dispatch, getState) => {
return (action) => {
console.log('Logging action:', action);
dispatch(action);
console.log('New state:', getState());
};
};
// 定义一个 Throttle 中间件 (简单的节流)
const throttleMiddleware = (delay) => {
return (dispatch, getState) => {
let lastTime = 0;
return (action) => {
const now = Date.now();
if (now - lastTime >= delay) {
dispatch(action);
lastTime = now;
}
};
};
};
// 模拟简单的 Store
const createStore = (reducer, initialState) => {
let state = initialState;
const listeners = [];
return {
getState: () => state,
dispatch: (action) => {
state = reducer(state, action);
listeners.forEach(listener => listener());
},
subscribe: (listener) => {
listeners.push(listener);
}
};
};
// 使用
const reducer = (state = 0, action) => {
if (action.type === 'INCREMENT') return state + 1;
return state;
};
const store = createStore(reducer, 0);
// 应用中间件
const enhancedStore = applyMiddleware(loggerMiddleware, throttleMiddleware(1000))(store);
// 测试
enhancedStore.dispatch({ type: 'INCREMENT' }); // 会打印日志,且节流生效
enhancedStore.dispatch({ type: 'INCREMENT' }); // 会被节流拦截
enhancedStore.dispatch({ type: 'INCREMENT' }); // 1秒后执行
看!这就是柯里化在框架设计层面的应用。applyMiddleware 本身就是一个柯里化函数(虽然这里用了展开运算符,但逻辑核心是链式调用)。它接收中间件列表,返回一个 Store Enhancer。每个中间件都遵循“接收 dispatch,返回 dispatch”的柯里化模式。
总结与反思
好了,同学们,我们聊了很多。从简单的 add(2)(3) 到复杂的 Redux 中间件,柯里化贯穿始终。
柯里化的核心价值在于:
- 参数固定化: 你可以把常用的参数先传进去,剩下的参数留给运行时。这极大地提高了代码的复用性。
- 组合性: 它让函数可以像积木一样组合。
compose,pipe等工具函数都是基于柯里化思想构建的。 - 延迟执行: 除非你调用最后一个函数,否则结果不会计算。这在处理异步操作和事件监听时非常有用。
但是,不要滥用!
如果你发现你的函数变成了 a(b)(c)(d)(e)(f)(g),看起来像一堆乱码,那可能就是过度柯里化了。JavaScript 的最佳实践往往是在“可读性”和“灵活性”之间寻找平衡。
在 React 中,柯里化最适合用在:
- 创建配置化的 API 请求层。
- 实现防抖、节流等高阶工具函数。
- 构建复杂的验证逻辑链。
- 设计高阶组件(HOC)或中间件系统。
最后,我想说,编程是一门艺术。柯里化就是这门艺术里的一支画笔。它能让你的代码从一团乱麻变成一幅精美的画作。但前提是,你得先学会怎么握住这支笔。
希望今天的讲座能让你对柯里化有一个全新的认识。下次当你看到 fn(a)(b)(c) 时,别再觉得它奇怪了,你应该会会心一笑,心想:“嘿,这小子,玩得挺花啊!”
下课!