函数式编程不是一种语言,而是一种思维方式。即使你写的是 Java、Go 或 Python,掌握纯函数、不可变数据、高阶函数这些概念,也能显著提升代码的可读性和可测试性。本文梳理我在日常开发中最常用的几个函数式技巧。
纯函数:最容易被低估的原则
纯函数的定义很简单:相同输入永远得到相同输出,且没有副作用。听起来像废话,但实际项目中一堆 bug 都是因为函数偷偷修改了外部状态。
# 非纯函数 — 依赖并修改外部状态
total = 0
def add_to_total(x):
global total
total += x
return total
# 纯函数 — 输入决定输出
def add(a, b):
return a + b
纯函数的好处是天然可测试。你不需要 mock 一堆依赖,直接传入参数检查返回值就行。在 Go 里写表驱动测试的时候,纯函数简直是最佳搭档。
不可变数据
不可变(immutable)不是说数据不能"变",而是每次"变"都产生一个新对象。这在并发场景下特别有用——不可变对象天然线程安全。
// Rust 中变量默认不可变
let v = vec![1, 2, 3];
// v.push(4); // 编译错误!
let mut v2 = v.clone();
v2.push(4); // OK,新的 Vec
Java 里的 String 就是不可变的典范。实际项目中,我习惯把 DTO、配置对象设计成不可变的:
public record UserDTO(Long id, String name, String email) {}
// record 天然不可变,字段都是 final
Python 中可以用 tuple 代替 list,用 frozenset 代替 set,或者用 dataclasses.dataclass(frozen=True)。
高阶函数:把行为当参数传
高阶函数就是能接收函数或返回函数的函数。这个概念每种语言都有,只是写法不同。
// Go:函数作为参数
func Filter[T any](items []T, predicate func(T) bool) []T {
var result []T
for _, item := range items {
if predicate(item) {
result = append(result, item)
}
}
return result
}
// 使用
adults := Filter(users, func(u User) bool {
return u.Age >= 18
})
在 Java 里 Stream API 就是高阶函数的集中体现:
List<String> names = users.stream()
.filter(u -> u.getAge() >= 18)
.map(User::getName)
.sorted()
.collect(Collectors.toList());
map/filter/neduce 三板斧
这三个操作覆盖了 80% 的集合处理场景:
- map:一对一转换
- filter:筛选
- reduce:聚合
orders = [
{"product": "手机", "price": 3999, "quantity": 2},
{"product": "耳机", "price": 299, "quantity": 5},
{"product": "平板", "price": 2499, "quantity": 1},
]
# 计算所有订单的总金额
from functools import reduce
total = reduce(
lambda acc, o: acc + o["price"] * o["quantity"],
orders,
0
)
# total = 3999*2 + 299*5 + 2499*1 = 11992
# 筛选出单价超过 1000 的商品名
expensive = list(
map(lambda o: o["product"],
filter(lambda o: o["price"] > 1000, orders))
)
# ['手机', '平板']
Python 中更 Pythonic 的写法是列表推导式,但理解 map/filter/neduce 的思想更重要——它们在 Java Stream、Rust Iterator、Go 泛型函数中都是同样的模式。
函数组合
函数组合就是把多个小函数串成一个大函数。Unix 管道 cat file | grep pattern | sort | uniq 就是函数组合的经典体现。
from functools import reduce
def compose(*fns):
'''从右到左组合函数'''
return reduce(lambda f, g: lambda x: f(g(x)), fns)
# 处理用户输入:去空格 -> 转小写 -> 去除特殊字符
import re
strip_spaces = str.strip
to_lower = str.lower
remove_special = lambda s: re.sub(r'[^a-z0-9一-鿿]', '', s)
clean_input = compose(remove_special, to_lower, strip_spaces)
clean_input(" Hello, World! 你好 ") # "helloworld你好"
Rust 中 Iterator 的链式调用天然就是函数组合:
let result: Vec<i32> = (1..=100)
.filter(|x| x % 2 == 0) // 偶数
.map(|x| x * x) // 平方
.take_while(|x| *x < 1000) // 小于1000
.collect();
小结
函数式编程的核心不是 monad 或 functor 这些学术概念,而是几个务实的原则:
| 原则 | 好处 | 日常应用 |
|---|---|---|
| 纯函数 | 可测试、可推理 | 工具函数、计算逻辑 |
| 不可变数据 | 线程安全、减少 bug | DTO、配置、常量 |
| 高阶函数 | 代码复用、抽象 | 回调、中间件、策略模式 |
| 函数组合 | 模块化、可读性 | 数据处理管道 |
不需要全盘函数式,在合适的场景用合适的技巧就好。