跳到主要内容

柯里化与偏应用

柯里化(Currying)和偏应用(Partial Application)是函数式编程中处理函数参数的技术。

什么是柯里化?

柯里化是将一个接受多个参数的函数转换为一系列接受单个参数的函数的过程。

// 普通函数
const add = (a, b, c) => a + b + c;
add(1, 2, 3); // 6

// 柯里化函数
const addCurried = a => b => c => a + b + c;
addCurried(1)(2)(3); // 6

// 可以分步调用
const add1 = addCurried(1);
const add1And2 = add1(2);
add1And2(3); // 6

柯里化实现

function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}

// 使用
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);

curriedAdd(1, 2, 3); // 6
curriedAdd(1)(2)(3); // 6
curriedAdd(1, 2)(3); // 6
curriedAdd(1)(2, 3); // 6

什么是偏应用?

偏应用是固定一个函数的一些参数,返回一个接受剩余参数的函数。

const add = (a, b, c) => a + b + c;

// 偏应用
const add1 = add.bind(null, 1);
add1(2, 3); // 6

// 或使用自定义函数
function partial(fn, ...args) {
return fn.bind(null, ...args);
}

const add1And2 = partial(add, 1, 2);
add1And2(3); // 6

柯里化 vs 偏应用

特性柯里化偏应用
参数数量每次一个可以多个
调用方式f(a)(b)(c)f(a, b)(c)
转换方式转换函数定义基于现有函数

实际应用

配置预设

const fetchWithBaseUrl = baseUrl => endpoint => 
fetch(`${baseUrl}${endpoint}`);

const apiFetch = fetchWithBaseUrl('https://api.example.com');
const fetchUsers = apiFetch('/users');
const fetchPosts = apiFetch('/posts');

事件处理

const handleChange = fieldName => event => {
setFormData(prev => ({
...prev,
[fieldName]: event.target.value
}));
};

<input onChange={handleChange('username')} />
<input onChange={handleChange('email')} />

数据处理

const filterBy = key => value => arr => 
arr.filter(item => item[key] === value);

const filterByType = filterBy('type');
const filterByActive = filterBy('active');

const users = [
{ type: 'admin', active: true },
{ type: 'user', active: false }
];

filterByType('admin')(users);

Ramda 中的柯里化

const R = require('ramda');

// Ramda 函数自动柯里化
const add = R.add;
const add1 = add(1);
add1(5); // 6

const prop = R.prop;
const getName = prop('name');
getName({ name: 'John' }); // 'John'

// 组合使用
const getUserNames = R.pipe(
R.map(getName),
R.join(', ')
);

最佳实践

  1. 参数顺序:将最常变化的参数放在最后
  2. 适度使用:不是所有函数都需要柯里化
  3. 配合组合:柯里化与函数组合配合使用效果最佳