函数式编程到底值得学吗?为什么很多人尝试之后半途而废?
我自己就放弃过两次。但回头看,这两次“失败”,反而成了我编程路上的重要财富。
一、第一次邂逅:Haskell 的幻觉与崩塌
我写 Python 已经三年多了,习惯了简洁优雅的语法,也喜欢那种“写出来就能跑”的直接。
但总能在社区里听到一种声音:
函数式编程(FP)才是真正的优雅。
它纯粹、它无 bug,它能改变你写代码的方式。
于是,我挑了最“纯”的语言——Haskell。
我的预期:
- 写代码像写数学公式一样干净。
- 没有混乱的状态,没有难以追踪的 bug。
- 优雅、学术、前沿。
现实:
- “Hello World” 都写得磕磕绊绊。
- 我必须搞懂
Monad才能在屏幕上输出一句话。 - 想用循环?对不起,Haskell 里没有循环,只有递归、
map、foldr。
举个例子,在 Python 里写个问候:
def greet(name):
print(f"Hello, {name}")
而在 Haskell 里,我不得不写成:
main = do
putStrLn "Enter your name:"
name <- getLine
putStrLn ("Hello, " ++ name)
为了理解这几行,我还得先啃一堆抽象概念:monad、IO、纯函数与副作用。
结果就是——我写了两周,还没能搭出一个能跑的项目。
我感觉自己不是在学写代码,而是在读一门数学系的高等代数课。
于是,第一次放弃。
我告诉自己:“也许函数式编程不适合我。”
二、第二次尝试:Elixir 的希望与崩溃
半年后,我不甘心,又决定再给 FP 一次机会。
这次的对象是 Elixir。
为什么是它?
- 语法比 Haskell 亲民。
- 运行在 Erlang VM 上,强调并发与容错。
- 听说很多公司真的在用,尤其是电信、消息服务。
刚上手的时候,我很喜欢它:
defmodule Greet do
def hello(name) do
IO.puts("Hello, #{name}")
end
end
比 Haskell 那套 Monad 要直观多了。
而且 模式匹配 和 管道操作符 让我眼前一亮:
[1, 2, 3, 4]
|> Enum.map(&(&1 * 2))
|> Enum.filter(&(&1 > 4))
这段代码意思是:
取一个列表 → 每个元素乘以 2 → 过滤掉小于等于 4 的数。
简洁、优雅,一气呵成。
但我依然卡住了。
卡在 不可变性。
在 Python 里,我每天都写上百次:
x = 10
x = x + 1
可在 Elixir 里,这行会直接报错:
x = 10
x = x + 1 # ❌ cannot rebind x
在 Elixir 中,变量一旦绑定,就不能修改。
每次“更新”,都要返回一个新变量。
这完全颠覆了我对“程序运行”的直觉。
于是,在经过几天的思想挣扎之后,我再次选择了放弃。
三、为什么函数式编程这么难?
后来我慢慢想明白:
函数式编程不是语法上的变化,而是思维方式的转变。
-
在命令式语言里(Python、Java、Go),我们习惯“改变状态”:
设置一个变量 → 修改它 → 在循环中继续修改它。
-
在函数式语言里,代码更像数学函数:
给输入 → 返回输出,不允许偷偷改变外部世界。
你必须把“变量”看作不可变值,把“流程”看作数据转换的管道。
这对大多数程序员来说,简直就是大脑重塑。
四、放弃两次之后,我学到的三点
虽然我没有坚持用 Haskell 或 Elixir 写项目,但这两次经历让我收获巨大。
1. 纯函数的好处
- 同样的输入 → 永远得到同样的输出。
- 没有副作用 → 测试更简单。
2. 不可变性的价值
- 不会出现“变量被偷偷改掉”的 bug。
- 代码逻辑更可预测。
3. 你不必 100% 函数式
- 我现在在 Python 里大量用
map、filter、列表推导式。 - 这就是在“偷师”函数式编程,而不必被它绑死。
五、来自社区的声音:为什么很多人放弃 FP?
在 Reddit 上,关于 Elixir vs Go 的讨论很有意思。
很多程序员提到:
- Elixir/BEAM 的优势:并发和容错(OTP)、语法优雅、Phoenix/LiveView 体验好。
- Elixir 的痛点:动态类型对大项目不够友好(有人转向强类型的 Gleam)、CPU 密集型不擅长、部署比 Go 麻烦。
- Go 的优势:类型安全、性能好、生态健全、编译后单一二进制,部署简单。
- Go 的不足:并发模型虽强,但不像 OTP 那样“电池全配”。
一句话总结:
Elixir 更像是写“长期在线的高并发系统”,Go 更像是写“工具、基础设施与 CPU 密集任务”。
这也解释了为什么很多人尝试 FP 后会退缩:
它的学习曲线确实陡峭,也并非所有项目都需要它的“数学式优雅”。
六、落地思路:如何用 FP 的思想而不被劝退?
如果你也想尝试 FP,我给几个小建议:
1)别从 Haskell 开始
它太学术了,入门容易劝退。
可以考虑 Scala(对 Python 程序员更友好),或者 Elixir(语法更直观)。
进一步想要强类型 + BEAM,可以试试 Gleam。
2)混合使用,而不是极端纯粹
在 Python、Java、Go 里用纯函数、不可变思路,就能收获一部分好处:
- 避免全局变量;
- 优先返回新值而不是就地修改;
- 利用 map/filter/reduce 或者流式 API;
- 将副作用隔离到边界(I/O、网络、数据库);
- 用小函数拼装复杂行为(组合优于继承)。
3)降低环境负担:用工具把“试错成本”降到最低
很多时候,劝退点不是语言本身,而是“装环境”。
我后来用 ServBay 这类本地多语言环境管理工具,体验就顺畅许多:
- 多语言并行:同时开 Python、Go、Elixir / Phoenix 的实验项目做对比;
- 隔离依赖:各自版本与依赖互不干扰,删除重建也不污染系统;
- 一键起停:把精力放在范式学习与小实验上,而不是一天到晚修 PATH、装 VM、配编译器。
对于“想尝鲜但又怕折腾环境”的同学,这是非常现实的解法:先在熟悉语言里引入 FP 风格,再把小实验迁到 Elixir/Scala/Gleam,对比体会差异与收益。
七、从“理念”到“方法”:给想把 FP 用到生产的人
如果你已经能在日常编码中自觉地“少副作用、重不可变、用纯函数”,下一步可以考虑把 FP 落到团队工程实践里:
-
代码层面
- 约定:业务函数默认纯函数,I/O、副作用集中在接口层。
- 结构:小函数 + 组合优先,避免巨石函数;
- 数据:不可变数据结构优先(即便语言不强制,也尽量不原地修改)。
-
测试与质量
- 纯函数单测覆盖率高,Mock 难度低;
- 副作用在边界,端到端测试专注流程正确性。
-
并发与可用性
- Go 场景:goroutine + channel 的约定式并发,配合 context 超时与 errgroup;
- Elixir 场景:OTP 的 supervision tree、进程隔离与自愈,天然容错。
-
部署与运维
- Go:静态编译、单文件交付,容器化与多架构镜像更省心;
- Elixir:考虑 release、节点编排与可观测性,充分利用 OTP 的能力。
-
团队心智升级
- 通过 code review 强化“无副作用边界”的习惯;
- 为新人准备 FP 风格的代码范例与重构清单;
- 从“小而确定”的模块开始推广(比如纯函数的定价、折扣、风控规则)。
八、结语:放弃不等于失败
我两次放弃了函数式编程,但我依然感谢它。
因为它让我更清楚地认识到:
- 写代码不只是“能跑”,还要“能维护”;
- 思维方式比语法更重要;
- 即便没成为 FP 程序员,我依然因为 FP 受益匪浅。
别焦虑于“学不会” 。
哪怕只学到“写纯函数、少用副作用、尊重不可变”,你已经在向更专业的工程师靠拢。
至于要不要上手 Elixir/Haskell/Scala?不妨先在熟悉的语言里用起来,再用 ServBay 搭个本地实验场,循序渐进,择其善者而从之。
开放问题
- 你尝试过函数式编程吗?
- 你觉得它在现实项目里值不值得用?
- 你更偏爱 Go 还是 Elixir?为什么?
欢迎在评论区分享你的故事与实践。