向热爱计算机科学的你推荐SICP

《计算机程序的构造和解释》改变你的编程思维
服务器君一共花费了220.783 ms进行了5次数据库查询,努力地为您提供了这个页面。
试试阅读模式?希望听取您的建议

先谈谈关于《计算机程序的构造和解释》(后面简称为SICP)的几个八卦。

  • 本书曾经是MIT本科第一门课的教材。前两年被Python取代,在geek中引发了轩然大波。有兴趣可以Google一下[sicp mit python]。
  • 本书在Amazon上的评分严重两极分化,五星(>90)和一星(>50)为主,彻底反正态分布。
  • 本书在Amazon上排名最高的书评出自Peter Norvig,当然是强烈推荐,顺便狠狠地鄙视了给一颗星的同学们;第二篇出自Paul Graham,还是强烈推荐。
  • 本书别名紫书(The Purple Book),巫师书(The Wizard Book),或者干脆The Book。

这是一本什么样的书?

前言说,这是一本给MIT学生的入门级(entry-level)计算机科学教材。作者的出发点有两条: 

  • 语言首先是写给人看的,只是恰巧(incidentally)能够运行。这当然是个修辞,格外强调代码之可读。
  • 语言的语法,漂亮的算法,数学的分析,这些统统都不重要。最打紧的是如何控制复杂度(The techniques used to control the intellectual complexity of large software systems)。

在本书成书的年代(1984),以上言论即使不算正邪不两立,也够的上离经叛道了。

通俗的说,这本书教你如何用最基本的构造和原则,解决复杂和多样的问题。用摄影打比方,这本书不比较尼康和佳能,不介绍繁杂的机型和参数,不介绍后期处理的技巧。这本书只讨论光线、色彩和构图,以及如何在不同场景拿捏这些基本原则组合出美妙的照片。

《计算机程序的构造和解释》(更多推荐……

这本书适合初学者吗?

不好说。Amazon上的一颗星评价大多鄙视本书已经过时或者太过高深。我个人看法,它很适合一部分初学者,但是需要满足几个条件: 

  • 热爱计算机科学 
  • 有时间和耐心 
  • 受过(高中水平)数学和抽象思维的训练 

所以,如果只是想领一份程序员的薪水,这本书完全可以略过。并不是说这本书有多么不实用,只是计算机科学与写代码并不是一码事。

这本书广而不深,讨论到了非常多重要的思想,有些甚至冷不丁出现在注释里(比如Y算子)。内容安排很照顾初学者,循循善诱;语言直白简单;代码大多简明自然。

至于习题,个人认为只要认真思考,大部分都不是很难,需要耐心多于智力。有时间不妨多做几道。

这本书适合有经验的程序员吗?

还行。如此庖丁解牛般的讲解,其他书中不多见;内容简单,思想却不过时。另外,国内绝大部分程序员都从命令式语言入门,不妨接触一些函数式思想,开开眼界。如果时间不多,至少看看前两章,学习一些解决复杂问题和编写优雅代码的技巧。

为什么我们要学习这本书?因为这本书告诉我们如何抽象。为什么我们要学习如何抽象?因为抽象是我们控制软件复杂性的重要手段。软件是人类有史以来最复杂的系统。其一、软件系统本身规模庞大,参与人手众多,难以管理;其二、环境和需求不断变化,且错误难以避免。

人类无法驾驭过于复杂的事物,于是只能寻找方法简化软件系统:把系统分为许多子部分,人们开发一个部分的时候,系统其他部分都是一种抽象,无需了解其细节。

本书讨论的就是系统的组织和设计,有哪些方法可以帮助我们控制软件的复杂度。

Scheme好学吗?

其实Scheme是一门异常简单的语言。直来直去,除了括号多,基本没有旁门左道(比如指针)和撕心裂肺的语言构造(比如模版、多重继承、Monad)。再者,这本书的内容和具体的语言基本没有关系,思想才是重头戏。如果实在有顾虑,推荐先读两本薄薄的小册子:The Little Schemer和The Seasoned Schemer。

这本书到底讲什么?

本书按照内容可以分为三个部分:过程抽象(第一章);数据抽象(第二、三章)和语言抽象(第四、五章)。

过程抽象部分比较简单,先介绍了Scheme的基本语法,让读者初步领略函数式编程的风采。对于有一定编程基础(相信国内极少有人入门就读这个)的读者来说,会有耳目一新的感觉,原来递归和迭代可以有另一种表现形式,但并不难理解。习题也比较简单,不会用掉太多的时间。过程抽象的概念也很简单,就是编程语言中的函数,目的是封装计算过程的细节。关于何时应该用过程抽象的原则是:一切可以定义为过程的计算片段都应该定义为过程。

数据抽象是我认为的本书的核心,也是最值得我们仔细研读的部分。关于数据抽象最直接的理解就是面向对象编程,如C++,而Java和C#则是更彻底的数据抽象。把一组过程抽象(类的方法)集中考虑,并加入内部状态(类的变量),就是一个数据抽象。每个数据抽象都应该把自己的内部对象状态和对象的实现隐藏起来,对外通过一组接口进行消息传递。这样听起来好像本书与一般的面向对象书没有区别,但实际上,这些都是我自己的总结,书里面不会把这些概念直接罗列出来,而是通过一个个巧妙的例子,让读者一步步深入,感叹原来A还可以这样抽象,原来B还可以这样封装。个人认为如果时间有限,读完前三章已经可以领会本书大部分思想了,后两章可以不读。

语言抽象是指自己发明一门语言,以解决某一特定应用领域的问题。在这一领域中,自己发明的语言会比其他通用语言更方便。定义了新语言的语法后,就要自己去实现该语言的编译器或解释器,可以通过现有的语言去构造。这一部分包含了许多编译方面的知识,但又与编译原理中的构造方法有不少区别,自己看书很容易看得云里雾里,听老师讲课才好一些。大部分习题很难做,一部分习题非常难。

第一章讨论程序设计的最基本原则:原语(primitive expressions)、组合(means of composition)和抽象(means of abstraction),以及如何利用这些基本原则化解复杂度。重点是过程抽象和高阶过程(high-order procedures)。本章的例题十分精彩,抽象和组合的过程十分清晰。有关递归和迭代的讨论也非常耐读。

第二章讨论数据抽象,即利用基本数据构造复杂结构。Scheme里的基本构造能力只有cons,但由此可以组合出所有实用的结构。图像语言、符号运算、集合表示、哈夫曼编码和复数系统都是经典实用的例子。顺带还介绍了data-directed方法,与面向对象中的封装有异曲同工之妙。

即使没有太多时间,我觉得前两章也值得值得细读。尤其是例子。

第三章主要讨论了状态(local state)和环境(environment model),可变数据结构(mutable data),以及状态和时间的交互(concurrency和laziness)。前两章用到语言是Scheme的一个没有副作用的子集,从这一章开始涉及解释器的核心机制,尤其是状态的管理,及其优缺点。

第四章用Scheme实现了一个简单的Scheme解释器。重点是讨论语言的解释过程,以及如何针对问题(领域)创造和修改语言,从中可见DSL(Domain Specific Language)的思想。后三节各自讨论一个工程中不常见但高效解决特定问题的语言变种及其实现。

第五章介绍将Scheme编译为现实中的寄存器机器模型(register machine)。重点不是编译技巧(Scheme压根不需要文法分析),而是基本构造(条件、过程,等等)对应于寄存器模型的实现。略带讨论了最简单的垃圾回收。

后三章较深,最好略有一点语言、编译和体系结构的基础,或者多些耐心。

语言会影响思维

如果要问现代数学最重要的概念是什么,那毫无疑问就是函数了,或者更确切地说,是映射。泛函这个词,或许对非数学系的同学来说有些陌生,但如果写成英语 functional, 看起来就眼熟多了。狭隘一点地说,泛函就是以函数为参数,返回值是数值的一类函数。看到这里相信不少同学都发现了,这就是在很多语言中被称为高阶函数(high-order function)的那个东西。泛函在数学中是如此普遍的概念,现代数学几乎无处不会用到。数学家们很自然地在集合上添加运算,构造空间;从一个空间映射到另一个空间,创造泛函。对泛函做变换,构造泛函的泛函等等。

为什么我要在这里提到数学和泛函?因为在我看来, lisp 是一门以表达数学为己任的语言。正如 SICP 中希望表达的一种观点:语言会影响思维。如果数学推理过程中最频繁应用到的泛函,在计算机语言中却没有对应的表达,换言之数学思维不能很自然地表述为计算机语言的话,那么计算机对于数学研究的意义就显得很可疑了,毕竟那时候的计算机可不是用来玩大菠萝3的。所以这里就有了两拨人,务实的一拨人开发出了 fortran ,力主解决数值计算;务虚的一拨人则创造了 lisp ,试图一举解决符号计算的难题。在 John McCarthy 所作的 history of lisp 中这样写到: 

Then mathematical neatness became a goal and led to pruning some features from the core of the language.(保证数学上的简洁性成为我们的目标,并因此拒绝了将一些特性加入到语言核心中。) 

This was partly motivated by esthetic reasons and partly by the belief that it would be easier to devise techniques for proving programs correct if the semantics were compact and without exceptions.(这部分是基于美学上的考虑,部分是因为我们相信,紧凑而没有特例的语法才更有可能设计出一种从数学上证明程序正确的方法。) 

之所以讲了这么多关于数学和历史的东西,是因为我觉得在看这本书前,最重要的是理解: lisp 是什么。而我又一直相信理解一样事物最好的办法就是理解其历史。(顺带说一句,以上历史都是在看过书以后才找的,所以也是我的血泪教训……)如上所示, lisp 是一门为了表达数学推导过程而诞生的语言,所以不可避免地使得 SICP 前两章的例子几乎全是数学问题。代码只是其形,而其神是纯粹的数学。所以这里似乎就陷入了一种两难的境地:如果执意于写代码的话,那看起来做的都是形而下的工作;而如果只思考问题的数学原理的话,那姑且不说是舍本逐末,至少也是偏离主题了。在看完 SICP 以后我始终怀着这种疑问而不解——看的时候是不会有这种感觉的,因为注意力全部纠结于书中的题目了——不过在写这篇书评时又翻了一下第一章,似乎明白了。

小节1.1写到: 

一个强有力的程序设计语言,不仅是一种指挥计算机执行任务的方式,它还应该成为一种框架,使我们能够在其中组织自己有关计算过程的思想。每一种强有力的语言都为此提供了三种机制:基本表达形式,组合的方法,抽象的方法。

所以我认为 SICP 这本书最主要的目的,就是“教你用 lisp 的语言,来组织,来抽象,来表达想法”。从这个意义上来说, SICP 和一本 Learning Python ,或者一本 C Programming Language 并没有太多的区别,依然讲授的是“用特定的语言求解特定的问题”。不过略有不同的是, lisp 太特殊了,导致从 c 转向 python 或许不需要太多的思维转换,但从 c 转向 lisp 却需要对思维习惯大改造一番,这我想就是 SICP 地位如此之高的原因吧。我也同意,学习 SICP 确实很锻炼思维,以及培养一种更加高度的抽象习惯。其实在看 SICP 的过程中,很多时候我都会感慨,“如果我不是数学系的,这一段到底会怎么理解呢”。一个典型例子就是习题2.6,初看我也一头雾水,后来才意识到 zero 是 f->id 的泛函,正是零映射, one 是 f->f 的泛函,正是恒同映射,也就是函数空间的1。如果没有学过泛函分析的我来看这道题目,估计只能好不容易推导出规律后,感慨于 Church 计数的“巧妙”了。所以从好的层面来看, SICP 至少能够带来泛函的直观感受,因此我才说 SICP 是一本写给CS人的泛函数。但是从坏的层面上说,数学抽象毕竟是象牙塔里的产物,当好不容易抽象出一个优雅的模型却发现手头的语言难以表达或者效率上有种种顾虑的时候,还是很郁闷的吧。

前面貌似说了 SICP 的不少坏话,其实只是想拉低一下 SICP 的评价,至少使得后人不至于期待过高。SICP 是一本好书,至少是一本有趣的书,这点我是非常赞同的。就冲着她那创意的封面图和作者头像,每章开篇都会引用一段(非常利于装逼)的名言,以及用半页的篇幅讲述和主题完全无关的 MIT 第一任校长的生平,想不有趣都难啊。不过我还是世俗一下,列一下自己看过这本书以后比较"现实"的收获: 

  1. 对于构造递归式的训练。相信做过的都深有体会…… 
  2. 列表处理流程,也就是 map-filter-reduce 。
  3. 流处理这一节让我终于明白了 generator 的意义。
  4. 从另一个角度看程序和程序设计中的问题 
  5. 函数式程序设计 
  6. 多种多样的程序组织方式 
  7. 丰富多彩的编程模式 
  8. 对一些基础问题的理解 
  9. ……

关于习题

最后说一下习题,习题的重要性想来大家都很清楚。

本书共有5章,每章都有近100道习题。这本书可以说是时间黑洞。每章分为4-5节,每节有几个小节,全书有一百小节(即X.X.X)左右。我以小节为单位进行了估算,包括完成习题,每小节大约需要一个小时。当然不同小节难度不同,有的耗时长些,有的短些。于是读完本书并做完大部分习题需要上百个小时。再加上听课或看视频教程的时间则会更长。所以我觉得恐怕只有在校学生才有时间和精力来完成这本书的学习。

不过对于 SICP 来说,我觉得习题未必都要写成代码,在纸上写出思路和关键代码也未为不可。因为 scheme 的编码效率实在不高(就是 scheme 逼得我给 vim 装上 surround 插件……),而习题重要的还是整个抽象的过程。另外就是,要找个好一点的解释器,我下了 MIT 的 scheme 解释器发现各种操作太不人性了…… 

为什么推荐SICP?

向大家推荐 SICP,不知道有多少人看了,也不知道有多少人明白了,更不知道有多少人惊叹了。或者你根本不屑一顾,或者你看见 Lisp 那层层括号心生畏惧,又或者你了了一瞥,觉得没什么精彩之处。那我真的很失望。

我为什么要推荐SICP,而且为什么如此执着?这本不算厚的书带给我的观念,是从未有过的,是关乎于软件本质的。曾几何时,我觉得我看到了计算机编程书中没有的哲学观,但这一次我的梦破灭了,那些已经被写进书里差不多快 30 年了。

我现在就来谈谈我的心得,以再次向你展现这本书的魔力。

第一章作为基础,作者并没有象后续章节写太多的软件思想,主要还是介绍 Scheme 语言,所以草草看去,没什么精辟之处。不过在第一章中,作者用了大量的篇幅来探讨数学问题,因为他想向你揭示程序设计中的核心哲学:抽象。而数学无疑是最好的例子。

了解数学史的人,应该知道整个数学史,就是一个不断抽象的历史。古希腊人将字母引入计算,使数学不再只是算术,而且具有表达抽象规则的能力。近代数学对函数和微积分的探求中,用 f(x) 替代了多项式表达式,函数更一般了,然后 n 维空间、复分析、映射、泛函,抽象代数、群论,等等等等,直到集合论,摧毁了数学的基石,使数学界再次陷入沉思。

构造程序的方法也是抽象。从最简单的元素开始,基本元素(自演算表达式,包括数字,字符串和布尔值),然后定义基本过程(基本运算符,四则运算和布尔运算),进一步,自定义标识符(如同代数),再自定义过程(函数),再将过程作为值参与运算(高阶过程)。一步步的抽象,形成了整个程序的结构。而我们编程,无非就是从现实世界抽象出模型,再将模型不断的提炼抽象,属性、方法、类、继承、层次、框架。

编程就是一个不断抽象的过程。我再次把作者在第一章末写下的结论抄在这里,作为最后的注脚。

“作为编程者,我们应该对这类可能性保持高度敏感,设法从中设别出程序中的基本抽象,基于它们去进一步构造,并推广它们以创建威力更强大的抽象。当然,这并不是说总应该采用尽可能抽象的方式去写程序,程序设计专家们知道如何根据工作中的情况,去选择合适的抽象层次。但是,能基于这种抽象去思考确实是最重要的,只有这样才可能在新的上下文中去应用它们。高阶过程的重要性,就在于我们能显式地用程序设计语言的要素去描述这些抽象,使我们能像操作其他计算元素一样去操作它们。” 

本文地址:http://www.nowamagic.net/librarys/veda/detail/1905,欢迎访问原出处。

不打个分吗?

转载随意,但请带上本文地址:

http://www.nowamagic.net/librarys/veda/detail/1905

如果你认为这篇文章值得更多人阅读,欢迎使用下面的分享功能。
小提示:您可以按快捷键 Ctrl + D,或点此 加入收藏

大家都在看

阅读一百本计算机著作吧,少年

很多人觉得自己技术进步很慢,学习效率低,我觉得一个重要原因是看的书少了。多少是多呢?起码得看3、4、5、6米吧。给个具体的数量,那就100本书吧。很多人知识结构不好而且不系统,因为在特定领域有一个足够量的知识量+足够良好的知识结构,系统化以后就足以应对大量未曾遇到过的问题。

奉劝自学者:构建特定领域的知识结构体系的路径中再也没有比学习该专业的专业课程更好的了。如果我的知识结构体系足以囊括面试官的大部分甚至吞并他的知识结构体系的话,读到他言语中的一个词我们就已经知道他要表达什么,我们可以让他坐“上位”毕竟他是面试官,但是在知识结构体系以及心理上我们就居高临下。

所以,阅读一百本计算机著作吧,少年!

《UNIX环境高级编程(第2版)》 史蒂文斯 (作者), 拉戈 (作者), 尤晋元 (译者), 张亚英 (译者), 戚正伟 (译者)

《UNIX环境高级编程(第2版)》是被誉为UNIX编程“圣经”的Advanced Programming in the UNIX Environment一书的更新版。在本书第1版出版后的十几年中,UNIX行业已经有了巨大的变化,特别是影响UNIX编程接口的有关标准变化很大。本书在保持了前一版的风格的基础上,根据最新的标准对内容进行了修订和增补,反映了最新的技术发展。书中除了介绍UNIX文件和目录、标准I/O库、系统数据文件和信息、进程环境、进程控制、进程关系、信号、线程、线程控制、守护进程、各种I/O、进程间通信、网络IPC、伪终端等方面的内容,还在此基础上介绍了多个应用示例,包括如何创建数据库函数库以及如何与网络打印机通信等。

更多计算机宝库...