函数式编程实战:这次怎么落地的

先复现,再谈优化。

从 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/) 转载或引用必须申明原指尖魔法屋来源及源地址!