回顾历史做仔细的分析与研究,总能给人意想不到的发现和惊叹。从认知的难易程度上来看,编程语言的范式可以按照如下的方式排序:最容易理解的是structured programming,一根线从上往下;再来会稍微费点功夫理解的是object-oriented(OO)programming;更困难的是functional programming,相当抽象,整个就是一数学的抽象思维。
自然地,历史的发展都是由简单到复杂,我们会下意识地认为编程语言在历史上的出现顺序也应该是:structured programming、OO、functional programming。
但翻阅历史,却会惊讶地发现它们的出现顺序竟然是反过来的!
structured programming是在1968年出现,其标志是Dijkstra(对,那个最短路径算法里出现的名字)发表他的seminal paper 'Cooperating sequential processes’。
再来是更早的1966年,Dahl和Nygaard发现function call stack frame可以被放到heap去做,这标志着OO的出现。
而似乎最难以理解的functional programming,则几乎可以追溯到1936年计算机被发明的时期。其标志是Alonzo Church推出lambda calculus。
另一件值得探讨的事情是:为什么Dijkstra要引入structured programming?虽然从现在来看structured programming是如此的直观,以至于你会问,为什么会过了那么久才引入structured programming?
在Dijkstra的时代,主流编程语言都不是structured的,到处充斥着goto所带来的跳转。为什么会遍地的goto呢?因为在计算机语言的莽荒时期,一切都是向机器看齐的。对于机器语言、汇编来讲,指令集的各种jump操作是再平常不过的事情了。既然它们都有之灵跳转,对应的编程语言怎么好意思说不支持goto呢?
所以,structured programming反而在当时是一种极端的非主流,因为它为programming给予了相当大的限制。
Dijkstra之所以要引入structured programming源自于他要把“计算机”这门学科变为科学的尝试:将数学的公理化体系引入computer science(CS)。但经过大量尝试后,他发现要为代码构建牢固的数学公理体系着实不是一件容易的事情。而其中最大的问题障碍,就是goto带来的不确定性。
于是,Dijkstra采用了数学家常用的研究方法:当需要推出一个漂亮的理论却发现前提条件不够时,就反过来先引入这个结论需要的前提假设。
于是,Dijkstra就直接把goto(至少是被滥用的goto)废除掉。没有了goto,虽然引入数学的严格证明会变得相当简单,但这样会不会限制语言的表达?又或者说,有些代码是不是没有了goto就无法写出来呢?
说来也是历史机缘,恰好在这个时候,Bohm and Jacopini在理论上证明了:所有的程序都可以被以下三种句式所替代: sequence, selection(if/then/else), iteration(for/while)。而这正好是Dijkstra所需要的,因为实现这三种语句完全用不到goto。于是,Dijkstra的为CS引入严格数学公理体系的壮举也就水到渠成:所有的由sequence、selection(if/then/else)语句构成的程序,可以由数学枚举法证明。而由iteration(for/while)语句构成的程序,则可以由数学归纳法完成。而structured programming这种被做了更多限制的编程范式也就应运而生。(很有意思的事情是,三种编程范式structured programming、OO、functional programming的引入,都是通过“限制功能”而非增加功能做出的,也即是,为了更强你需要更弱一点。更多讨论可以参考我以前的一篇文章《》。)
如果按照这样的思维框架去思考算法,比如LeetCode中的算法题目,又会有一些惊人发现。我的一个想转行做程序员的朋友曾跟我聊过这样一段刷题感受:似乎算法题目都或多或少地在使用数学归纳法。这确实是很强的洞见。回顾起来,无论是divide-and-conquere还是dynamic programmming,这两种作为算法基石的工具都运用到了数学归纳法的思维方式。前者(递归)是天然的数学归纳法处理方式,而后者则是做了空间复杂度优化的递归,同样按照数学归纳法的方式做处理。
进一步,我们可以考察一些工作中遇到的问题。刚毕业工作的小白程序员常常会疑惑这样一个事情,明明自己在学校中算法的造诣颇高,按道理说算法好不就是编程的功底好么,但为什么到了工作岗位中还是各种碰壁?
按照我们上面构建的思维框架,我们其实可以给出一种解释:因为算法部分的修炼,仅仅保证了你在structured programming这个语言范式下做事具备了很好的能力。可工作中所涉及到的软件设计,则是在OO范式和functional programming范式下进行的。领域不同,自然是新手,碰壁也是自然之事。