函数式编程是一个倒退
英文原文:Functional programming: A step backward
除非你生活中与世隔绝的深山老林里,否则你应该知道,在众多的所谓顶级编程高手(alpha geeks)中,函数式编程是十分盛行的。也许你已经使用了某种函数式编程语言。如果你是在使用很传统的编程语言,例如 Java 或 C#,你应该知道了,这些语言很快就将引入一些函数式编程特征。就在这美丽的新世界即将来到之际,就在我们摩拳擦掌打算大干一番之前,我想,现在应该是我们暂停一下、反省一下函数式编程在我们的日常应用开发中是否合适的好时机。
什么是函数式编程?简单的回答:一切都是数学函数。函数式编程语言里也可以有对象,但通常这些对象都是恒定不变的 —— 要么是函数参数,要什么是函数返回值。函数式编程语言里没有 for/next 循环,因为这些逻辑意味着有状态的改变。相替代的是,这种循环逻辑在函数式编程语言里是通过递归、把函数当成参数传递的方式实现的。
为什么要使用函数式编程
拥护者说函数式编程能开发出更高效的软件,而反对者说反之亦然。我感觉双方的观点都有问题。我可以轻松的证明函数式编程能使你更难写出针对编译器优化的代码,或者相较于传统语言的代码,JIT 编译器对于函数式代码会编译出更慢的程序。命令式编程语言(imperative programming languages)语法跟底层的计算机硬件指令间有着很相似的对应关系,但函数式编程语言却没有这种特征。结果就是,编译器处理函数式编程语言时更费力。
然而,优秀的编译器能把函数式编程中的闭包、tail 调用、或 lambda 表达式转换成跟传统语言中 loop 循环或其它表达式等效的代码。这需要多做一些工作。如果你在寻找一本厚达 1600 页的关于这方面的好书,我推荐你《Optimizing Compilers for Modern Architectures: A Dependence-based Approach》和《Advanced Compiler Design and Implementation》。或者你也可以使用 GCC 或任何具有多阶段编译功能、能生成汇编代码的编译器自己去证明这一点。
对于为什么要使用函数式编程,这有一个更好的论据,现代的应用程序都会牵涉到多核计算机上的并行运算功能,程序状态就成了一个问题。所有的命令式语言,包括面向对象语言,在涉及多线程时,都会遇到共享对象的状态修改问题。这就是死锁、堆栈跟踪、低级处理器缓存命中率低等问题的根源。如果对象没有状态,这些问题就不存在了。
在很多地方使用函数式编程或函数式编程语言都是非常适合的,甚至是最好的选择。对于纯函数计算,函数式编程明显的比命令式编程更合适。但对于商业软件或其它普通应用软件,你不能不说这正好要颠倒过来。就像 Martin Fowler 著名的阐述,“傻子都能写出计算机可读懂的代码。优秀的程序员写出的是人能读懂的代码。”而函数式编程写出的代码就是让人一眼望去不可读。
几段代码就能让你知道我说的是什么意思。来自 Erlang 语言的代码例子:
-module (listsort).
-export ([by_length/1]).
by_length (Lists) ->
qsort (Lists, fun (A,B) -> A < B end).
qsort ([], _)-> [];
qsort ([Pivot|Rest], Smaller) ->
qsort ([X || X <- Rest, Smaller (X,Pivot)], Smaller)
++ [Pivot] ++
qsort ([Y || Y <- Rest, not (Smaller (Y, Pivot))], Smaller).
这个是 Haskell 语言的:
-- file: ch05/Prettify.hs
pretty width x = best 0 [x]
where best col (d:ds) =
case d of
Empty -> best col ds
Char c -> c : best (col + 1) ds
Text s -> s ++ best (col + length s) ds
Line -> '\n' : best 0 ds
a `Concat` b -> best col (a:b:ds)
a `Union` b -> nicest col (best col (a:ds))
(best col (b:ds))
best _ _ = ""
nicest col a b | (width - least) `fits` a = a
| otherwise = b
where least = min width col
人 vs 机器
一个不怎么样的程序员一般都能从一段命令式的代码中很快的看出其基本的功用 —— 甚至这是一种他从未见过的语言。然而虽然你也能从一段函数式代码里分析出它的功用,但你绝对不可能简单几眼就能看出来。不像命令式代码,函数式代码并不体现出简单的语言结构。它展现的都是数学结构。
我们的编程经历了从纸带打孔到汇编到宏汇编到C语言(高级宏汇编)再到抽象出了很多老实机器上复制运算的高等编程语言。每一步都使我们越来越接近《星际迷航4》里的场景:遇到麻烦的 Scott 对他的鼠标说出指令(“Hello computer“)。数十年的进步使得编程语言越来越容易被人类阅读和理解,函数式编程的语法是在把时钟指针往后拨。
函数式编程能解决并行运算的状态问题,但付出的代价是失去人类可读性。函数式编程也许完全可以用于任何环境开发,它甚至可以通过定义面向领域(domain-specific)的编程语言来拉近人类语言和计算机语言之间的距离。但它糟糕的语法使得它极不适合常规目的的编程开发。
不要这么着急的判断潮流 —— 特别对于那些不想有太多风险的项目。