函数式编程实战:这次怎么落地的
先复现,再谈优化。
从 OOP 到 FP 的转变经历。
基本概念
纯函数
// 纯函数:相同输入总是得到相同输出,没有副作用
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;
// 非纯函数:有副作用
let counter = 0;
const increment = () => {
counter += 1;
return counter;
};
// 转换为纯函数
const incrementPure = (counter) => counter + 1;
不可变性
// 可变操作(避免)
const user = { name: 'Alice', age: 30 };
user.age = 31; // 修改了原始对象
// 不可变操作(推荐)
const user = { name: 'Alice', age: 30 };
const updatedUser = { ...user, age: 31 };
// 数组操作
const numbers = [1, 2, 3];
// 可变操作
numbers.push(4); // 修改了原始数组
// 不可变操作
const newNumbers = [...numbers, 4];
const newNumbers2 = numbers.concat([4]);
高阶函数
// 函数作为参数
const map = (fn, array) => array.map(fn);
const filter = (fn, array) => array.filter(fn);
const reduce = (fn, initial, array) => array.reduce(fn, initial);
// 使用
const doubled = map(x => x * 2, [1, 2, 3]);
const evens = filter(x => x % 2 === 0, [1, 2, 3, 4]);
const sum = reduce((acc, x) => acc + x, 0, [1, 2, 3]);
组合与管道
函数组合
// 函数组合:从右到左执行
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
// 管道:从左到右执行
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);
// 使用
const add = x => x + 1;
const multiply = x => x * 2;
const square = x => x * x;
const composed = compose(square, multiply, add);
console.log(composed(1)); // 9: 1 -> 2 -> 4 -> 16 -> 9
const piped = pipe(add, multiply, square);
console.log(piped(1)); // 4: 1 -> 2 -> 4 -> 4
柯里化
// 柯里化:将多参数函数转换为单参数函数链
const curry = (fn) => {
const arity = fn.length;
const curried = (...args) =>
args.length >= arity
? fn(...args)
: (...more) => curried(...args, ...more);
return curried;
};
// 使用
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
偏函数应用
// 偏函数:固定部分参数
const partial = (fn, ...presetArgs) => (...laterArgs) => fn(...presetArgs, ...laterArgs);
// 使用
const multiply = (a, b) => a * b;
const double = partial(multiply, 2);
const triple = partial(multiply, 3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
实用函数工具
Map/Filter/Reduce
// Map:转换数组
const users = [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
{ name: 'Charlie', age: 35 }
];
const names = users.map(user => user.name);
console.log(names); // ['Alice', 'Bob', 'Charlie']
// Filter:过滤数组
const adults = users.filter(user => user.age >= 30);
console.log(adults); // [{ name: 'Alice', age: 30 }, { name: 'Charlie', age: 35 }]
// Reduce:聚合数组
const totalAge = users.reduce((sum, user) => sum + user.age, 0);
console.log(totalAge); // 90
Find/Every/Some
// Find:查找元素
const alice = users.find(user => user.name === 'Alice');
console.log(alice); // { name: 'Alice', age: 30 }
// Every:所有元素都满足条件
const allAdults = users.every(user => user.age >= 18);
console.log(allAdults); // true
// Some:至少一个元素满足条件
const hasSenior = users.some(user => user.age >= 65);
console.log(hasSenior); // false
Sort/GroupBy
// Sort:排序
const sortedUsers = [...users].sort((a, b) => a.age - b.age);
console.log(sortedUsers);
// GroupBy:分组
const groupBy = (fn, array) =>
array.reduce((acc, item) => {
const key = fn(item);
if (!acc[key]) acc[key] = [];
acc[key].push(item);
return acc;
}, {});
const groupedByAge = groupBy(
user => user.age >= 30 ? 'adult' : 'young',
users
);
console.log(groupedByAge); // { adult: [...], young: [...] }
处理副作用
Maybe Monad
// Maybe Monad:处理可能为空的值
const Maybe = {
of: (value) => (value === null || value === undefined ? Nothing : Just(value)),
};
const Just = (value) => ({
isNothing: false,
map: (fn) => Maybe.of(fn(value)),
chain: (fn) => fn(value),
getOrElse: (defaultValue) => value,
});
const Nothing = {
isNothing: true,
map: () => Nothing,
chain: () => Nothing,
getOrElse: (defaultValue) => defaultValue,
};
// 使用
const findUser = (id) => {
const user = users.find(user => user.id === id);
return Maybe.of(user);
};
const getUserName = (id) =>
findUser(id)
.map(user => user.name)
.getOrElse('Unknown');
console.log(getUserName(1)); // 'Alice'
console.log(getUserName(999)); // 'Unknown'
Either Monad
// Either Monad:处理可能出错的操作
const Either = {
of: (value) => Right(value),
};
const Right = (value) => ({
isLeft: false,
map: (fn) => Right(fn(value)),
chain: (fn) => fn(value),
fold: (_, fn) => fn(value),
});
const Left = (value) => ({
isLeft: true,
map: () => Left(value),
chain: () => Left(value),
fold: (fn, _) => fn(value),
});
// 使用
const divide = (a, b) =>
b === 0 ? Left('Cannot divide by zero') : Right(a / b);
const result = divide(10, 2).fold(
error => `Error: ${error}`,
value => `Result: ${value}`
);
console.log(result); // 'Result: 5'
Promise Monad
// Promise:处理异步操作
const fetchUser = (id) =>
fetch(`/api/users/${id}`)
.then(response => response.json())
.then(data => Right(data))
.catch(error => Left(error));
// 组合异步操作
const getUserOrders = (userId) =>
fetchUser(userId)
.chain(user => fetch(`/api/users/${user.id}/orders`))
.then(response => response.json())
.then(data => Right(data))
.catch(error => Left(error));
// 使用
getUserOrders(1)
.fold(
error => console.error(error),
orders => console.log(orders)
);
实战案例
数据处理管道
// 构建数据处理管道
const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);
// 数据转换
const processData = pipe(
// 1. 过滤有效数据
data => data.filter(item => item.valid),
// 2. 转换数据
data => data.map(item => ({
id: item.id,
name: item.name.toUpperCase(),
value: parseFloat(item.value)
})),
// 3. 排序
data => [...data].sort((a, b) => b.value - a.value),
// 4. 限制数量
data => data.slice(0, 10)
);
// 使用
const rawData = [
{ id: 1, name: 'Alice', value: '10.5', valid: true },
{ id: 2, name: 'Bob', value: '5.3', valid: true },
{ id: 3, name: 'Charlie', value: 'invalid', valid: false }
];
const processedData = processData(rawData);
console.log(processedData);
状态管理
// 简单的状态管理器
const createStore = (initialState) => {
let state = initialState;
const listeners = [];
return {
getState: () => state,
dispatch: (action) => {
state = reducer(state, action);
listeners.forEach(listener => listener(state));
},
subscribe: (listener) => {
listeners.push(listener);
return () => {
const index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
}
};
};
// Reducer
const reducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [...state.todos, action.payload]
};
case 'REMOVE_TODO':
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload)
};
default:
return state;
}
};
// 使用
const store = createStore({
todos: []
});
store.subscribe((state) => {
console.log('State updated:', state);
});
store.dispatch({
type: 'ADD_TODO',
payload: { id: 1, text: 'Learn FP' }
});
事件处理
// 事件处理器
const createEventHandler = () => {
const handlers = [];
return {
on: (eventType, handler) => {
handlers.push({ eventType, handler });
},
emit: (eventType, data) => {
handlers
.filter(h => h.eventType === eventType)
.forEach(h => h.handler(data));
}
};
};
// 使用
const emitter = createEventHandler();
emitter.on('click', (data) => {
console.log('Clicked:', data);
});
emitter.on('click', (data) => {
console.log('Log click:', data);
});
emitter.emit('click', { x: 100, y: 200 });
踩过的坑
坑一:过度使用函数组合
为了函数式而函数式,代码难以理解。
解决:平衡可读性和函数式特性。
// 过度使用:难以理解
const result = pipe(
map(compose(toUpper, prop('name'))),
filter(compose(lt(18), prop('age'))),
sortBy(prop('age'))
)(users);
// 更好的方式:有意义的函数名
const toUpperCaseNames = users => users.map(user => ({ ...user, name: user.name.toUpperCase() }));
const filterAdults = users => users.filter(user => user.age >= 18);
const sortByAge = users => [...users].sort((a, b) => a.age - b.age);
const result = sortByAge(filterAdults(toUpperCaseNames(users)));
坑二:性能问题
频繁创建数组和对象,导致性能下降。
解决:合理使用不可变性,必要时优化。
// 性能优化:使用变异操作处理大数据
const processLargeArray = (array) => {
const result = array.map(item => processItem(item));
return result;
};
// 更好的方式:使用 for 循环处理大数据
const processLargeArrayOptimized = (array) => {
const result = new Array(array.length);
for (let i = 0; i < array.length; i++) {
result[i] = processItem(array[i]);
}
return result;
};
坑三:学习曲线陡峭
函数式编程概念多,学习成本高。
解决:逐步引入,从简单概念开始。
// 从简单的函数式概念开始
// 1. 纯函数
const add = (a, b) => a + b;
// 2. 不可变性
const update = (state, updates) => ({ ...state, ...updates });
// 3. 高阶函数
const map = (fn, array) => array.map(fn);
// 4. 组合
const compose = (f, g) => x => f(g(x));
// 逐步引入更复杂的概念
写在最后
函数式编程这东西,不只是技术,是思维方式。
优势:
- 代码更简洁
- 更容易测试
- 更容易推理
- 更容易并行
挑战:
- 学习曲线陡峭
- 性能问题
- 资源消耗
选择之前先评估:
- 项目需求
- 团队能力
- 性能要求
不是所有场景都适合函数式编程,有时候命令式编程更简单。
这次函数式编程学习花了两个月,从理论到实践。学习完成后,代码质量提升明显,测试覆盖率从 60% 提升到 90%。
版权声明: 本文首发于 指尖魔法屋-函数式编程实战:这次怎么落地的(https://blog.thinkmoon.cn/post/83-functional-programming-practice-guide/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。