函数式编程才是计算的本质

#Ofilm #PLT

变量的类型实现了对变量的约束,这种约束可以防止人犯一些愚蠢的错误。早期的计算机内存制造成本是很高的,一块内存需要反复使用,所以变量就显得很必要了。

这也是为什么早期函数式语言很难发展起来,函数式语言中,变量值是不可变的,对值的操作并不是修改原来的值,而是修改新产生的值,原来的值保持不变。同样由于变量不可变,纯函数编程语言无法实现传统意义上的循环,因为 for 循环使用可变的状态作为计数器,而 while 循环或 do while 循环需要可变的状态作为循环的条件。因此在函数式语言里就只能使用递归来解决迭代问题,这使得函数式编程语言严重依赖递归。

程序的本质就是一头有输入,而另一头有输出,但关键是从输入到输出的这个映射十分复杂。面向对象的方法是将处理职责拆分到不同的类,然后组合和复用这些类来构建程序,但如何拆分和如何给这些细小部分的处理职责定个类名?没有标准答案,这也是面向对象系统混乱的根源,所以早期为了解决系统混乱,软件工程告诉我们要从业务角度去拆分,所以软件工程必修内容包括 UML,用于系统分析设计。然而,后来人许多人抛弃了 UML,声称要追求敏捷。他们先定义表再定义类,写上一大堆 getter、setter,配上一堆 mapper 注解,然后就说自己在做面向对象。即使到了 2020 年,这种现象依然普遍存在。

另一方面,函数式编程的作法就更加贴近计算与组合的本质,它将复杂的映射拆分为小的映射,这些映射通常以计算命名,比如一个 toUpperCase 函数,名字就明确地表达了它的职责:将输入的字符串中所有的英文字符转为大写。函数式编程就是通过组合这些细小的映射来构建复杂的映射,组合才是函数式编程的核心。这时可能会有人提到 Monad 或范畴论。实际上,编写函数式编程并不需要理解范畴论。Monad 的存在是因为我们的输入和输出有时是不定的或不具体的,它们需要修饰词,比如处理 null 或异常,因此有了 Maybe 和 Either。

至于纯度的讨论,如输入输出的纯粹性,这在实际编程中并不需要纠结。计算机程序的最初输入与最终输出几乎不可能完全纯净,副作用在计算机世界中是真实存在的。我们所谓的复杂性,是指构建整个大映射体的逻辑组合的复杂,而不是纠结于最初和最终的数据是否纯粹。