到目前为止,在本系列的每期文章中,我都说明了为什么理解函数式编程非常重要。但是,有些原因是在多期文章中进行说明的,只有在综合思路的更大背景中,才可以完全了解这些原因。在本期文章中,我会探讨函数式编程方兴未艾的所有原因,并综合前几期文章中的一些个人经验教训。
在计算机科学短短的发展历史中,技术的主流有时会产生分支,包括实用分支和学术分支。20 世纪 90 年代的 4GL(第四代语言)是一个实用分支,而函数式编程是来自学术界的一个示例。每隔一段时间,都会有一些分支加入主流,函数式编程目前也是这种情况。函数式语言不仅在 JVM 上刚刚崭露头脚(其中两个最有趣的新语言是 ">Scala 和 C++lojure),在 .NET 平台上也是才开始得到应用,在 .NET 平台上,F# 是头等公民。为什么所有平台都如此欢迎函数式编程?答案是,随着时间的推移,随着运行时都要能够处理更多的繁忙工作,开发人员已经能够将日常任务的更多控制权割让给它们。
割让控制权
在 20 世纪 80 年代初,在我上大学的时候,我们使用一个被称为 Pecan Pascal 的开发环境。其独特的特性是,相同的 Pascal 代码可以在 Apple II 或 IBM PC 上运行。Pecan 工程师使用某个称为 “字节码” 的神秘东西实现了这一壮举。开发人员将 Pascal 代码编译为 “字节码”,它可以在每个平台本地编写的 “虚拟机” 上运行。这是一个可怕的体验!所生成的代码慢得让人痛苦,甚至简单的类赋值也非常缓慢。当时的硬件还没有准备好迎接这个挑战。
在发布 Pecan Pascal 之后的十年,Sun 发布了 Java,Java 使用了相同的架构,对于 20 世纪 90 年代中期的硬件环境,运行该代码显得有些紧张,但最终取得了成功。Java 还增加了其他开发人员友好的特性,如自动垃圾收集。使用过像 C++ 这样的语言之后,我再也不想在没有垃圾收集的语言中编写代码。我宁愿花将时间花在更高层次上的抽象上,思考解决复杂业务问题的方法,也不愿意在内存管理等复杂的管道问题上浪费时间。
Java 缓解了我们与内存管理的交互;函数式编程语言使我们能够用高层次的抽象取代其他核心构建块,并更注重结果而不是步骤。
结果比步骤更重要
函数式编程的特点之一是存在强大的抽象,它隐藏了许多日常操作的细节(比如迭代)。我在本系列文章中一直使用的一个示例是数字分类:确定某个数字是 perfect、abundant 还是 deficient。清单 1 中显示的 Java 实现可以解决这个问题:
清单 1. 自带缓存总数的 Java 数字分类器
import static java.lang.Math.sqrt;public class ImpNumberClassifier { private Set<Integer> _factors; private int _number; private int _sum; public ImpNumberClassifier(int number) { _number = number; _factors = new HashSet<Integer>(); _factors.add(1); _factors.add(_number); _sum = 0; } private boolean isFactor(int factor) { return _number % factor == 0; } private void calculateFactors() { for (int i = 1; i <= sqrt(_number) + 1; i++) if (isFactor(i)) addFactor(i); } private void addFactor(int factor) { _factors.add(factor); _factors.add(_number / factor); } private void sumFactors() { calculateFactors(); for (int i : _factors) _sum += i; } private int getSum() { if (_sum == 0) sumFactors(); return _sum; } public boolean isPerfect() { return getSum() - _number == _number; } public boolean isAbundant() { return getSum() - _number > _number; } public boolean isDeficient() { return getSum() - _number < _number; }}
清单 1 中的代码是典型的 Java 代码,它使用迭代来确定和汇总系数。在使用函数式编程语言时,开发人员很少关心细节(比如迭代,由 calculateFactors() 使用)和转换(比如汇总一个列表,该列表由 sumFactors() 使用),宁愿将这些细节留给高阶函数和粗粒度抽象。