函数式编程如何才能在我们的常规软件开发中占据一席之地

qbear

除非你生活中与世隔绝的深山老林里,否则你应该知道,在众多的所谓顶级编程高手(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)的编程语言来拉近人类语言和计算机语言之间的距离。但它糟糕的语法使得它极不适合常规目的的编程开发。

不要这么着急的判断潮流——特别对于那些不想有太多风险的项目。

[英文原文:Functional programming: A step backward ]
分享这篇文章:

11 Responses to 函数式编程如何才能在我们的常规软件开发中占据一席之地

  1. ljf10000 says:

    半夜肚子疼,爬起来就看到这篇胡扯文章。
    习惯了C的人当然看java顺眼,习惯了lisp的人当然看haskell顺眼,不过是习惯使然。反过来,我习惯了C看basic也想吐,习惯了java有几个看fortran顺眼的?

  2. redraiment says:

    只懂英文的人乍眼能看懂中文?这两种好歹还都是人类语言!

  3. cyler123 says:

    不得不说,两段代码的排版非常虾。如果是这种缩进,能编译通过吗?
    另外:我还是觉得纯函数式语言不怎么样,还是在命令式语言中融合一些函数式语言的特性比较好~

  4. xwsoul says:

    看clojure 的代码真的很痛苦 尤其有编程经验的…转换思维模式真的很痛苦

    • tom says:

      clojure 代码没看过,我是学C的,后来学java,工作时写lotus script,业余看看lisp,不觉得多难理解

      但是看大机上的JCL就杯具了,木有运行环境,单看语法让我抓墙
      书上说还要考虑当前列的问题,过了XX列,代码就算违规,或是另一个意思

  5. bojoy says:

    我记得有一种语言叫做”white space”有兴趣的人可以查查.不要看了.因为啥都看不到,全是white space.它是一种只用空白字符(空格,TAB和回车)编程的语言,而其它可见字符统统为注释

  6. luobo25 says:

    如果用快速排序之类的例子,情况就会颠倒过来

  7. tom says:

    lotus script就是函数式+命令式复合成的,代码…不说了,有些易读,有些不知所云(要看代码作者的水准)

    那些不易读的代码均是抛弃所有的规范,用a,b,c…..z或者一堆拼音首字母做变量名,不管代码多长(哪怕超过2000行)均不拆分函数也不写注释的

    所以,问题在人,人有效地组织代码,合理划分函数,仔细定义变量名和函数参数表,即使是函数式代码也是可读,易懂的,反过来操作,用java或.net或任何一种高级语言也没人能看懂

    再说面向对象也不是银弹,抛开无法搞定所有需求这个问题,用了面向对象依然会有糟糕的代码出现,那”数十年的进步使得编程语言越来越容易被人类阅读和理解,函数式编程的语法是在把时钟指针往后拨”这根本就是一个伪命题,因为代码是否易懂并不是语言决定的

  8. Null says:

    按照大多数人的阅读习惯,一打开就是满眼的广告.内容质量下降就不提了,连排版都越做越糟.真是.

  9. 只看不说吧呀呀呀呀 says:

    学数学的人,理解函数式编程非常容易,如学Java 语言的人学Scala 很容易,然后学其它的函数式编程

  10. dongguangming 对这篇文章的反应是赞一个

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.