组合才是编程的本质

组合才是编程的本质——函数式编程通过组合小的映射来构建复杂映射,比面向对象的类拆分更贴近计算的核心。

程序就是映射

程序的本质是什么?一头输入,一头输出,中间是从输入到输出的映射。

这个映射可以用一个函数表达:f(input) = output。但真正的问题在于,这个 f 极其复杂。你不可能把整个程序写成一个函数——它太大、太难理解、太难维护。所以你需要拆分它。

怎么拆?这是编程范式的分水岭。

拆分的两条路

面向对象的解法是将处理职责拆分到不同的类,然后组合和复用这些类来构建程序。听上去合理,但问题来了:怎么拆?怎么给这些细小部分定个类名?

没有标准答案。这是面向对象系统混乱的根源。为了缓解这种混乱,软件工程教科书从业务角度出发教人拆分,UML 成了必修课。然而,后来的实践者抛弃了 UML,声称要追求敏捷。

结果呢?先定义数据库表,再定义对应的类,写上一堆 getter 和 setter,配上 mapper 注解,然后说自己在做面向对象。这种做法的本质不是面向对象,是面向数据库。

函数式编程 的做法不同。它不纠结「这个职责该归哪个类」,而是问:这个映射可以拆成哪些小映射?

每个小映射通常以计算本身命名。toUpperCase 这个函数,名字就明确表达了它的职责:将输入字符串中的英文字符转为大写。它接受一个字符串,返回一个字符串,中间只做一件事。函数式编程通过组合这些细小的映射来构建复杂的映射。先做 A,再做 B,然后做 C——每一步都清晰、可测试、可复用。

类比一下:面向对象像是先造抽屉、柜子、架子,再决定什么东西放哪里;函数式编程像是流水线,每个工位完成一道工序,产品从一端进来,另一端出去。

组合的细节

Lambda 演算 只用函数定义、函数应用和变量引用三种元素,就能表达所有可计算的问题。函数式编程的组合方式直接对应这个数学根基。但实践中,有两个常见的疑问。

composition-is-the-essence-of-programming-fig01.png450

关于 Monad:很多人以为需要范畴论才能理解它。其实不需要。Monad 之所以存在,是因为输入和输出有时不那么「干净」。一个函数可能收到空值,可能抛出异常,可能需要异步返回。

Maybe 处理空值,Either 处理错误——这些结构就是给输出加了「修饰词」,让你在组合小映射时能优雅地处理不确定性。仅此而已。

关于纯度:计算机程序的最初输入和最终输出几乎不可能完全纯净——读写文件、网络请求、用户交互,副作用在计算机世界中真实存在。真正的复杂性不在输入输出是否纯粹,而在于构建整个映射体的逻辑组合

回到映射

程序的本质是映射。函数式编程 比面向对象更接近这个本质,不是因为它更「纯粹」,而是因为它把拆分和组合的粒度压到了最小——函数。粒度越小,组合方式越多,表达力越强。

这是 Lambda 演算 教给我们的:三个元素,无限组合。