函数组合进阶:构建优雅的数据处理管道
组合的艺术
在音乐中, 单个音符很简单, 但当它们按特定顺序组合时, 就能创造出美妙的旋律。函数组合也是如此——将简单的函数组合起来, 构建出强大而优雅的程序。
函数组合(Function Composition)是函数式编程的核心思想之一。它让我们能够:
- 将复杂问题分解为简单函数
- 通过组合构建新功能
- 创建清晰的数据转换管道
- 编写可复用、可测试的代码
基础组合模式
1. Compose 和 Pipe
在基础函数式编程中, 我们已经了解了 compose 和 pipe。让我们深入探讨它们:
javascript
// Compose: 从右到左执行 (数学函数组合的方向)
const compose =
(...fns) =>
(value) =>
fns.reduceRight((acc, fn) => fn(acc), value);
// Pipe: 从左到右执行 (更直观, Unix 管道风格)
const pipe =
(...fns) =>
(value) =>
fns.reduce((acc, fn) => fn(acc), value);
// 基础函数
const double = (x) => x * 2;
const increment = (x) => x + 1;
const square = (x) => x * x;
// 使用 compose: 从右到左
const calc1 = compose(
square, // 3. 最后平方
increment, // 2. 然后加1
double // 1. 首先翻倍
);
console.log(calc1(3)); // (3 * 2 + 1)² = 49
// 使用 pipe: 从左到右 (更易读)
const calc2 = pipe(
double, // 1. 首先翻倍
increment, // 2. 然后加1
square // 3. 最后平方
);
console.log(calc2(3)); // (3 * 2 + 1)² = 492. 多参数函数的组合
javascript
// 问题: 只有第一个函数可以接收多个参数
const add = (a, b) => a + b;
const multiply = (x) => x * 2;
const subtract = (x) => x - 1;
// ❌ 这样不行
const bad = pipe(add, multiply, subtract);
// bad(5, 3) => NaN (因为 multiply 和 subtract 只能接收一个参数)
// ✅ 柯里化解决方案
const curry = (fn) => {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
}
return (...nextArgs) => curried(...args, ...nextArgs);
};
};
const curriedAdd = curry((a, b) => a + b);
const good = pipe(
curriedAdd, // 可以分步接收参数
multiply,
subtract
);
console.log(good(5)(3)); // ((5 + 3) * 2) - 1 = 15
// 或者一开始就设计为单参数函数
const addN = (n) => (x) => x + n;
const multiplyN = (n) => (x) => x * n;
const subtractN = (n) => (x) => x - n;
const calc = pipe(addN(3), multiplyN(2), subtractN(1));
console.log(calc(5)); // ((5 + 3) * 2) - 1 = 15Point-Free 风格
Point-Free 风格是指函数定义中不显式提及数据参数:
javascript
// ❌ Pointed 风格 - 显式提及参数
const getNames = (users) => users.map((user) => user.name);
// ✅ Point-Free 风格 - 不提及参数
const prop = (key) => (obj) => obj[key];
const map = (fn) => (array) => array.map(fn);
const getNames = map(prop("name"));
// 更复杂的例子
const users = [
{ name: "John", age: 25, active: true },
{ name: "Jane", age: 30, active: false },
{ name: "Bob", age: 35, active: true },
];
// Pointed 风格
const getActiveUsersNames = (users) =>
users
.filter((user) => user.active)
.map((user) => user.name)
.map((name) => name.toUpperCase());
// Point-Free 风格
const filter = (predicate) => (array) => array.filter(predicate);
const toUpperCase = (str) => str.toUpperCase();
const getActiveUsersNamesPF = pipe(
filter(prop("active")),
map(prop("name")),
map(toUpperCase)
);
console.log(getActiveUsersNames(users)); // ['JOHN', 'BOB']
console.log(getActiveUsersNamesPF(users)); // ['JOHN', 'BOB']Point-Free 风格的优势:
- 更简洁, 减少样板代码
- 更易组合和复用
- 关注数据转换而非数据本身
- 更易测试(不需要模拟数据)
高级组合模式
1. Transducer (转换器)
Transducer 是一种高效的组合多个转换操作的方法, 避免创建中间数组:
javascript
// 传统方式 - 创建多个中间数组
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const traditional = numbers
.map((x) => x * 2) // 中间数组 1
.filter((x) => x > 10) // 中间数组 2
.map((x) => x + 1); // 最终数组
console.log(traditional); // [11, 13, 15, 17, 19, 21]
// Transducer 方式 - 单次遍历
const mapT = (fn) => (reducer) => (acc, value) => reducer(acc, fn(value));
const filterT = (predicate) => (reducer) => (acc, value) =>
predicate(value) ? reducer(acc, value) : acc;
const transduce = (transducer, reducer, initial, collection) => {
const xf = transducer(reducer);
return collection.reduce(xf, initial);
};
const transducer = pipe(
mapT((x) => x * 2),
filterT((x) => x > 10),
mapT((x) => x + 1)
);
const result = transduce(
transducer,
(acc, value) => [...acc, value],
[],
numbers
);
console.log(result); // [11, 13, 15, 17, 19, 21]2. 异步组合
处理异步操作的组合:
javascript
// 异步 pipe
const asyncPipe =
(...fns) =>
(value) =>
fns.reduce(async (acc, fn) => fn(await acc), Promise.resolve(value));
// 异步操作
const fetchUser = async (id) => {
console.log(`Fetching user ${id}...`);
await new Promise((resolve) => setTimeout(resolve, 100));
return { id, name: "John", email: "[email protected]" };
};
const enrichWithPosts = async (user) => {
console.log(`Fetching posts for ${user.name}...`);
await new Promise((resolve) => setTimeout(resolve, 100));
return {
...user,
posts: [
{ id: 1, title: "Hello World" },
{ id: 2, title: "JavaScript Tips" },
],
};
};
const enrichWithComments = async (user) => {
console.log(`Fetching comments for ${user.name}...`);
await new Promise((resolve) => setTimeout(resolve, 100));
return {
...user,
comments: [
{ id: 1, text: "Great post!" },
{ id: 2, text: "Thanks for sharing" },
],
};
};
const addTimestamp = (user) => ({
...user,
fetchedAt: new Date(),
});
// 组合异步操作
const getUserWithAllData = asyncPipe(
fetchUser,
enrichWithPosts,
enrichWithComments,
addTimestamp
);
getUserWithAllData(1).then((user) => {
console.log("完整用户数据:", user);
});3. 条件组合
根据条件选择不同的函数进行组合:
javascript
// 条件执行
const when = (predicate, fn) => (value) => predicate(value) ? fn(value) : value;
const unless = (predicate, fn) => when((value) => !predicate(value), fn);
// 使用示例
const isNegative = (x) => x < 0;
const isLarge = (x) => x > 100;
const isDecimal = (x) => !Number.isInteger(x);
const processNumber = pipe(
when(isNegative, Math.abs), // 转为正数
when(isLarge, () => 100), // 限制最大值
unless(Number.isInteger, Math.floor) // 取整
);
console.log(processNumber(-50)); // 50
console.log(processNumber(150)); // 100
console.log(processNumber(45.7)); // 45
console.log(processNumber(30)); // 30分支组合:
javascript
// 根据条件选择不同的处理流程
const branch = (predicate, trueFn, falseFn) => (value) =>
predicate(value) ? trueFn(value) : falseFn(value);
const cond =
(...pairs) =>
(value) => {
for (const [predicate, fn] of pairs) {
if (predicate(value)) {
return fn(value);
}
}
return value;
};
// 使用示例
const categorizeAge = cond(
[(age) => age < 13, () => "child"],
[(age) => age < 20, () => "teenager"],
[(age) => age < 60, () => "adult"],
[() => true, () => "senior"]
);
console.log(categorizeAge(10)); // 'child'
console.log(categorizeAge(16)); // 'teenager'
console.log(categorizeAge(35)); // 'adult'
console.log(categorizeAge(70)); // 'senior'4. 收集器组合
处理聚合操作:
javascript
// 创建收集器
const collector = {
toArray: () => (acc, value) => [...acc, value],
toSet: () => (acc, value) => acc.add(value),
toObject: (keyFn, valueFn) => (acc, value) => ({
...acc,
[keyFn(value)]: valueFn(value),
}),
groupBy: (keyFn) => (acc, value) => {
const key = keyFn(value);
return {
...acc,
[key]: [...(acc[key] || []), value],
};
},
};
// 使用
const users = [
{ id: 1, name: "John", role: "admin" },
{ id: 2, name: "Jane", role: "user" },
{ id: 3, name: "Bob", role: "admin" },
];
// 转换为对象
const usersMap = users.reduce(
collector.toObject(
(u) => u.id,
(u) => u
),
{}
);
console.log(usersMap);
// { '1': {id: 1, ...}, '2': {id: 2, ...}, '3': {id: 3, ...} }
// 按角色分组
const groupedByRole = users.reduce(
collector.groupBy((u) => u.role),
{}
);
console.log(groupedByRole);
// { admin: [{id: 1, ...}, {id: 3, ...}], user: [{id: 2, ...}] }实战场景
1. 数据验证管道
javascript
// 构建灵活的验证系统
const validate = {
required: (field) => (value) => ({
valid: value != null && value !== "",
error: value ? null : `${field} 是必填项`,
}),
minLength: (field, min) => (value) => ({
valid: value && value.length >= min,
error: value?.length >= min ? null : `${field} 至少需要 ${min} 个字符`,
}),
email: (field) => (value) => ({
valid: /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
error: /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
? null
: `${field} 格式不正确`,
}),
range: (field, min, max) => (value) => ({
valid: value >= min && value <= max,
error:
value >= min && value <= max
? null
: `${field} 必须在 ${min}-${max} 之间`,
}),
};
// 组合验证器
const composeValidators =
(...validators) =>
(value) => {
const results = validators.map((v) => v(value));
const firstError = results.find((r) => !r.valid);
return {
valid: !firstError,
error: firstError?.error || null,
errors: results.filter((r) => !r.valid).map((r) => r.error),
};
};
// 使用
const validateUsername = composeValidators(
validate.required("用户名"),
validate.minLength("用户名", 3)
);
const validateEmail = composeValidators(
validate.required("邮箱"),
validate.email("邮箱")
);
const validateAge = composeValidators(
validate.required("年龄"),
validate.range("年龄", 18, 100)
);
console.log(validateUsername("")); // { valid: false, error: '...' }
console.log(validateUsername("ab")); // { valid: false, error: '...' }
console.log(validateUsername("john")); // { valid: true, error: null }
console.log(validateEmail("invalid")); // { valid: false, error: '...' }
console.log(validateEmail("[email protected]")); // { valid: true, error: null }2. 数据转换管道
javascript
// 构建复杂的数据处理流程
const transform = {
filterBy: (key, value) => (items) =>
items.filter((item) => item[key] === value),
sortBy:
(key, order = "asc") =>
(items) =>
[...items].sort((a, b) =>
order === "asc" ? (a[key] > b[key] ? 1 : -1) : a[key] < b[key] ? 1 : -1
),
mapTo: (fn) => (items) => items.map(fn),
take: (n) => (items) => items.slice(0, n),
unique: (key) => (items) => {
const seen = new Set();
return items.filter((item) => {
const value = item[key];
if (seen.has(value)) return false;
seen.add(value);
return true;
});
},
};
// 电商订单处理
const orders = [
{
id: 1,
customer: "John",
amount: 150,
status: "pending",
date: "2025-01-01",
},
{
id: 2,
customer: "Jane",
amount: 200,
status: "completed",
date: "2025-01-02",
},
{
id: 3,
customer: "Bob",
amount: 100,
status: "pending",
date: "2025-01-03",
},
{
id: 4,
customer: "Alice",
amount: 300,
status: "completed",
date: "2025-01-04",
},
{
id: 5,
customer: "John",
amount: 250,
status: "pending",
date: "2025-01-05",
},
];
// 获取待处理订单, 按金额降序, 取前3个, 格式化输出
const processOrders = pipe(
transform.filterBy("status", "pending"),
transform.sortBy("amount", "desc"),
transform.take(3),
transform.mapTo((order) => ({
id: order.id,
summary: `订单 #${order.id} - ${order.customer}`,
amount: `$${order.amount}`,
}))
);
console.log(processOrders(orders));3. React Hooks 组合
javascript
// 组合 React Hooks 创建自定义 Hook
const composeHooks = (...hooks) => (...args) => {
return hooks.reduce((acc, hook) => {
const result = hook(...args);
return { ...acc, ...result };
}, {});
};
// 示例 Hooks
const useLocalStorage = (key, initialValue) => {
const [value, setValue] = useState(() => {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
});
const updateValue = (newValue) => {
setValue(newValue);
localStorage.setItem(key, JSON.stringify(newValue));
};
return { value, setValue: updateValue };
};
const useDebounce = (value, delay) => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return { debouncedValue };
};
// 组合使用
const useDebounced LocalStorage = composeHooks(
useLocalStorage,
(key, initialValue) => useDebounce(initialValue, 500)
);4. 中间件管道
javascript
// Express 风格的中间件组合
const composeMiddleware = (...middlewares) => {
return async (context) => {
let index = 0;
const next = async () => {
if (index >= middlewares.length) return;
const middleware = middlewares[index++];
await middleware(context, next);
};
await next();
return context;
};
};
// 中间件示例
const logger = async (ctx, next) => {
console.log(`-> ${ctx.method} ${ctx.path}`);
await next();
console.log(`<- ${ctx.method} ${ctx.path} ${ctx.status}`);
};
const auth = async (ctx, next) => {
if (!ctx.user) {
ctx.status = 401;
ctx.body = { error: "Unauthorized" };
return;
}
await next();
};
const rateLimit = async (ctx, next) => {
// 简化的限流逻辑
if (Math.random() < 0.1) {
ctx.status = 429;
ctx.body = { error: "Too many requests" };
return;
}
await next();
};
const handler = async (ctx, next) => {
ctx.status = 200;
ctx.body = { message: "Success", data: { id: 1 } };
await next();
};
// 组合中间件
const app = composeMiddleware(logger, auth, rateLimit, handler);
// 使用
app({
method: "GET",
path: "/api/users",
user: { id: 1, name: "John" },
}).then((result) => {
console.log("响应:", result);
});性能优化
1. 惰性求值
javascript
// 延迟执行直到需要结果
class LazySequence {
constructor(data) {
this.data = data;
this.operations = [];
}
map(fn) {
this.operations.push({ type: "map", fn });
return this;
}
filter(fn) {
this.operations.push({ type: "filter", fn });
return this;
}
take(n) {
this.operations.push({ type: "take", n });
return this;
}
// 只有调用 toArray 时才执行
toArray() {
let result = this.data;
for (const op of this.operations) {
if (op.type === "map") {
result = result.map(op.fn);
} else if (op.type === "filter") {
result = result.filter(op.fn);
} else if (op.type === "take") {
result = result.slice(0, op.n);
}
}
return result;
}
}
const lazy = (data) => new LazySequence(data);
// 使用
const numbers = Array.from({ length: 1000000 }, (_, i) => i);
const result = lazy(numbers)
.map((x) => x * 2)
.filter((x) => x > 100)
.take(5)
.toArray(); // 只有这时才开始执行
console.log(result);2. 函数记忆化组合
javascript
// 为组合函数添加记忆化
const memoizedCompose = (...fns) => {
const cache = new Map();
const composed = compose(...fns);
return (value) => {
const key = JSON.stringify(value);
if (cache.has(key)) {
return cache.get(key);
}
const result = composed(value);
cache.set(key, result);
return result;
};
};小结
函数组合是函数式编程的精髓, 它让我们能够:
- 分解复杂问题: 将大问题拆分为小函数
- 构建数据管道: pipe/compose 创建清晰的处理流程
- 提高可复用性: 小函数可以在不同场景组合
- 增强可测试性: 每个小函数独立测试
- 优雅的抽象: Point-Free 风格和高级模式
关键技术:
- 基础: compose、pipe
- 高级: transducer、异步组合、条件组合
- 优化: 惰性求值、记忆化
- 实践: 验证、转换、中间件