老赵书托(2):计算机程序的构造与解释

我要推荐的第一本书便是大名鼎鼎的《Structure and Interpretation of Computer Programs》,在国内可以买到中译版,即机械工业出版社的《计算机程序的构造与解释》。

抽象

sicp

豪不夸张地说,这是一本影响了好几代程序员的书。自从上世纪80年代MIT开始使用这本书作为教材开始,它使用Lisp语言——直到前两年才被Python取代,但是使用哪本教材不得而知,由这个侧面也可见SICP这本书的影响力有多么深远。在技术日新月异的计算机行业,有多少教材可以经得起20年的考验?

至于为什么要推荐这本书,还是要从这本书在讲什么东西谈起。您觉得,对于一个程序员来说,他最需要培养哪些能力?需要了解哪些知识?如果要我回答,我会说,一个合格的程序员需要一定要对计算机算法与数据结构有较为踏实的了解(这点在以前的文章中也重复了很多次)。至于“操作系统”、“计算机网络”、“编译原理”等课程是否重要?我不知道。这些课程都被广泛接受,所以它们肯定是有用的,但是如果您追问我它们的具体作用,我无法清晰明确地告诉您答案(例如,“编译原理”对普通程序员有什么作用?)——所以我不知道。当然,以后我还是会推荐一些这方面的书籍(因为“需要与否”其实都是个“尺度”和“方向”问题),到那时我们再继续谈论这方面的话题。

不过我可以肯定的是,一个合格的程序员(无论前台/后台,系统/应用),必须要有一定的分析问题解决问题的能力——或者说,抽象的能力。抽象是使用程序解决问题的必备手段之一。例如:

  • 您是否可以把一个多级的系统分类,理解为一颗树,然后用树或图的方式来处理它?
  • 如果让您解八皇后问题,或者走一个简单的迷宫,基本上不太会难倒你,但是您可以把自己的思路使用程序表现出来吗?
  • 领域驱动设计的一个重要部分,便是将真实世界中的“领域”提炼成模型,再使用计算机语言实现出来。

需要抽象能力的情况,数不胜数。而SICP这本书,其目标便是培养您的抽象能力,自然还有使用基本的手段进行组合来解决问题的能力。这点正如书中1.1节The Element of Programming中所述:

Thus, when we describe a language, we should pay particular attention to the means that the language provides for combining simple ideas to form more complex ideas. Every powerful language has three mechanisms for accomplishing this:

  • primitive expressions, which represent the simplest entities the language is concerned with,
  • means of combination, by which compound elements are built from simpler ones, and
  • means of abstraction, by which compound elements can be named and manipulated as units.

函数式编程

全书使用Lisp进行教学,这是一门函数式编程语言。有人说,函数式编程语言适合在实验室里把玩,不适合开发大型工程——我觉得这还是一个怎么看的问题。这里谈一个我的亲身经历:我在大学里也有课程是讲述LISP语言,但当时的感觉只是“一种比较新奇的语言”,至于它有什么用,它有什么帮助我根本一概不知。然而,经过了“工程”的磨练和实践之后,我反而慢慢体会到函数式编程的优势来。在我看来,函数式编程对于实际工程上的影响,一个主要的方面在于它可以使用更小粒度的抽象单元。对于面向对象编程来说,其抽象的最小单元为“类”和“实例”。试想如果您的程序想要展开“交互”,无论如何都必须从一个“实例”和“类”上面发起。而对于函数式编程来说,它最小粒度的抽象为“函数”。例如,您可以把一个方法作为另一个方法的参数或返回值(所谓高阶函数),而一段逻辑的实现完全可以通过“小方法”的组合来进行。不要小看这种抽象级别的改变,它会大大影响系统API的设计。

这里举一个示例。一个小问题作为示例:“求出a到b之间所有整数之和”。这很容易,您可以会这么做。

static int Sum(int a, int b)
{ int sum = 0; for (int i = a; i <= b; i++) sum += i; return sum;
}

那么,“求出a到b之间所有整数的平方之和”或“绝对值之和”呢?当然,您可以再写两个方法。但是,从函数式编程角度来说,这完全是一个可以复用的逻辑:

static int Sum(Func<int, int> f, int a, int b)
{ int sum = 0; for (int i = a; i <= b; i++) sum += f(i); return sum;
}

您可以将一个函数(在.NET里用委托表示)作为参数传入Sum方法,在调用时只需传入f的实现即可:

int i = Sum(x => x * x, 1, 3); // 14 int j = Sum(x => Math.Abs(x), -3, 3); // 12

甚至于,我们可以将其“部分应用(partial application)”1。简单说来,“部分应用”是将函数的部分参数固定,以得到一个新的函数:

static Func<int, int, int> SumCurry(Func<int, int> f)
{ return (a, b) => Sum(f, a, b);
}

这样我们就可以使用SumCurry来获得新的函数了2

var sumOfSquare = SumCurry(x => x * x); // int i = sumOfSquare(1, 3); var sumOfCube = SumCurry(x => x * x * x); // int j = sumOfCube(1, 3);

如果您理解了上面的代码,其实您已经对函数式编程更细致的抽象能力有所体会了。如果是面向对象编程,您需要怎么做呢?首先,您可能需要定义一个抽象类SumCalculator,其中有一个抽象方法为F。我们要复用算法,就必须构造SumCalculator的子类,提供F的具体实现。哪种做法简单,哪种做法繁琐,一目了然。

目前函数式编程几乎已经成了高级语言的必备特性了,如C#,F#,甚至颇有代替Java语言之势的Scala中也包含了相当的函数式编程能力。事实上,我认为出现这种趋势的一个重要原因,便在于人们之前对面向对象语言的抽象能力寄予过高期望,而这种期望的破灭(或者说“冷静”)使得许多人的注意力又回到了更容易“组合”和“复用”的函数式编程理念上。而且,其实人们从来没有放弃过对小粒度的事物的热爱。例如很多人喜欢C语言的原因,便是因为它没有庞大的架构,可以通过各种方法的组装来编写程序。而Unix编程艺术之一,便是大量小程序的组合复用。

您对函数式编程的重要性还有所怀疑吗?如果您觉得上面的例子还有些“玩具”感觉的话,您还可以参考我之前实现的CacheHelper,AsyncTaskDispatcher(上,下)或者微软的并行库——还有Matthew Poswysocki发起的“反对for行动”。您不妨思考一下,如果没有函数式编程特性,又该如何实现这些功能呢?

自然,函数式编程的优点远不止这一个。例如函数式编程中“无副作用”的纯函数,对于目前愈发热烈的并行环境也有重大意义。这些就要靠您来自行挖掘了3


转载自老赵的博客
http://blog.zhaojie.me/2009/07/recommended-reading-2-sicp.html#comment_lkmSMbIA07M00001有兴趣的可以多关注关注这位大牛的博客

你可能感兴趣的:(SICP,lisp,书单,老赵,函数编程)