裘宗燕
北京大学数学学院信息科学系教授。长期从事计算机软件与理论、程序设计语言和符号计算方面的研究与教学工作。已出版多部著作和译著,包括《程序设计语言基础》(译著,北京大学出版社,1990)、《Mathematics数学软件系统的应用与程序设计》(编著,北京大学出版社,1994)、《C++程序设计语言(特别版)》(译著:机械工业出版社,2002)、《C++语言的设计和演化》(译著,机械工业出版社,2002)、《程序设计语言——概念和结构》(合译,机械工业出版社,2002)、《从问题到程序——程序设计与C语言引论》(编著,机械工业出版社,2005年第1版,2011年第2版)等。
本书主要介绍计算的核心思想,采用的方法是为计算建立一系列概念模型。主要内容包括:构造函数抽象,构造数据抽象,模块化、对象和状态,元语言抽象,寄存器机器里的计算等。采用JavaScript作为实例分析,但并不拘泥于对语言的解释,而是通过这种语言来阐述程序设计思想。第1章介绍了计算过程以及函数在程序设计中扮演的角色。第2章在第1章的基础上提供了将数据对象组合起来形成复合数据,进而构造抽象的方法。第3章介绍了一些帮助我们模块化构造大型系统的策略。第4章通过元语言抽象探究如何在一些语言的基础上开发新语言的技术。第5章从寄存器机器的角度出发,通过设计寄存器机器,开发一些机制,实现重要的程序设计结构,同时给出一种描述寄存器机器设计的语言。本书揭示计算机程序设计思想的实质是改变了人们的思考方式:从命令式的观点去研究知识的结构。因此,本书所阐述的设计思想不仅适用于计算机程序设计,而且适用于所有工程设计。
Harold Abelson,Gerald Jay Sussman,Martin Henz,Tobias Wrigstad,with Julie Sussman:Structure and Interpretation of Computer Programs:JavaScript Edition(ISBN 978-0-262-54323-1).
Original English language edition copyright © 2022 Massachusetts Institute of Technology.
Simplified Chinese Translation Copyright © 2024 by China Machine Press.
Simplified Chinese translation rights arranged with MIT Press through Bardon-Chinese Media Agency.
No part of this book may be reproduced or transmitted in any form or by any means,electronic or mechanical,including photocopying,recording or any information storage and retrieval system,without permission,in writing,from the publisher.
All rights reserved.
本书中文简体字版由MIT Press通过Bardon-Chinese Media Agency授权机械工业出版社在中国大陆地区(不包括香港、澳门特别行政区及台湾地区)独家出版发行。未经出版者书面许可,不得以任何方式抄袭、复制或节录本书中的任何部分。
“我认为特别重要的,就是在计算机科学领域享受计算的乐趣。这一学科起步时充溢着乐趣。当然,付钱的客户常常觉得受骗,一段时间后,我们开始严肃看待他们的抱怨。我们开始觉得,自己真像是要为成功地、无误和完美地使用这些机器负起责任。我并不认为我们可以做到这些。我认为我们的责任就是去拓展这个领域,设定新方向,并享受自己领地的乐趣。乐趣无处不来,它们出自发现和证明一个定理,写出一段程序,或者破解一段编码等。无论乐趣从何而来或者因何出现,我希望计算机科学领域绝不要丢掉其趣味意识。最重要的是,我希望我们不要变成传道士。你知道的有关计算的东西,其他人也都能学到。绝不要认为成功计算的钥匙只掌握在你的手里。你能掌握的——也是我认为并希望的——不过是智慧:那种能使你看到的这种机器比你第一次站在它面前时更强大,能够做得更多的能力。”
——Alan J.Perlis(1922年4月1日—1990年2月7日)
《计算机程序的构造和解释》(
Structure and Interpretation of Computer Programs
,简记为SICP)是MIT的基础课教材,出版后引起计算机教育界的广泛关注,对推动全世界大学计算机科学技术教育的发展和成熟产生了很大影响。机械工业出版社把SICP(第2版)引进中国,由我翻译后于2004年出版,至今已近20年了
。令人感兴趣的是,SICP至今仍然受到国内关心计算机科学技术的人们,特别是计算机专业的优秀学生和青年计算机工作者的关注。我偶尔还会收到讨论这本书的邮件,出版社也在不断重印。
与许多计算机科学领域的入门教材不同,SICP的最主要关注点并不在基础语言中各种编程结构的形式和意义,也没有深入讨论巧妙或深刻的算法。与众不同地,一方面,SICP注目于帮助读者理解基于计算的观点看世界、看问题的重要性,掌握相关的基本概念和观点,建立基于计算思考问题的习惯,也就是今天人们常说的计算思维。另一方面,SICP也深入讨论了通过计算的方式处理和解决问题时必须掌握的主要技术与方法,最重要的就是分解问题和组织计算,以及建立和使用抽象的各种技术与方法。
SICP的章节目录清晰地反映了作者的基本想法:第1、2两章分别讨论函数(或过程)抽象和数据抽象的作用,它们的建立和使用;第3章讨论抽象数据对象本身的状态和变化,相关的模块化的问题及其在计算实践中的重要性;第4章讨论元语言抽象,也就是设计和实现面向应用的新语言的问题;第5章可以看作前面讨论的应用,而应用的对象问题就是JavaScript语言在寄存器机器上的实现。这里的寄存器机器是现代计算机的抽象模型,这里的讨论也说明了抽象的高级语言如何落地。
读者现在拿在手里的这本书是SICP的一个改编本。与SICP的不同之处,就在于这个改编本用更多计算机工作者熟悉的JavaScript语言作为讨论的工具,而没有用原SICP里使用的Scheme语言。因此,这里程序实例的形式更接近各种常规的编程语言,可能更容易被更多读者接受。本书的内容是原SICP的翻版,作者编写本书的基本目标是尽可能完整准确地反映原书的宗旨和精神,同时又使这些能被更多的人理解和重视。
由于本书的根源和作者的意图,本书的基本内容和结构都来自SICP,许多一般性的讨论直接来自原书,但也有许多地方针对JavaScript做了一些调整和修改。本书比较好地反映了SICP的思想,是一本非常好的学习计算机科学技术的读物,值得每一个关心计算机领域,并有心在这个领域中深入学习和努力工作的人士阅读学习。
正如作者所言,这本书并不想作为JavaScript的入门教科书。书中对JavaScript语言的介绍远非完整,读者不应该希冀通过阅读本书学习JavaScript编程。但另一方面,由于本书的宗旨和内容,对它的学习一定会有助于读者学习JavaScript(一般而言,学习任何常见的编程语言,如Java、Python或C)。如果读者学过JavaScript(或其他编程语言),阅读这本书能帮助你更好地理解程序设计和一般的软件开发,从而有可能在这些领域中做得更出色、更高效、更得心应手。如果本书是你学习计算机科学技术的第一本书(或者学的第一门课),这段学习经历能为你今后的学习建立一个坚实的基础,帮助你更顺利地度过这段专业学习。无论如何,认真地阅读这本书,都是一件非常值得做的事情。
对于本书的学习,必须和相应的实际编程、用计算机解决问题的实践相结合。只读不做,当然不可能真正领悟计算机科学技术的真谛。另一方面,只是抄录、运行和试验书中给出代码,也不能得到其中的真传。作为这本书的真正有心的读者,你必须亲自一次次地经历使用计算机(通过编程)解决问题的实践过程。本书的作者已经为读者提供了学习所需的许多材料和资源,希望读者好好利用。
最后,非常感谢机械工业出版社引进这本很有意思,也很值得阅读的著作。在专业领域中的一大批人都扑向人工智能、机器学习等热门话题的今天,基础的计算机科学技术知识和能力仍然不会过时。在这里付出的努力终将会被证明是值得的。
裘宗燕
2023年5月,于北京
在学生时代,遇到妙趣横生的Alan Perlis时我总是非常兴奋,并和他有几次交谈。他和我对两种截然不同的程序设计语言——Lisp和APL——都非常喜爱和珍重。跟上他的步伐很不容易,何况他还在开辟卓越的新路。我想重新检视他在给这本书原序中的一个论断(我建议你在读完本序言后再重读他的序,它就印在后面):用100个函数在一种数据结构上操作,优于用10个函数在10种数据结构上操作。这句话真对吗?
为了认真回答这个问题,我们首先要问,这一种数据结构是“普适的”吗?它能方便地取代那10种更特殊的数据结构的角色吗?
对于这些,我们还可以问,我们真的需要这100个函数吗?是否存在一个单一的“普适”函数,它能取代所有其他函数的角色?
对最后这个问题,有一个令人惊异的回答:是!只需要不多的技巧就能构造出一个函数,该函数接受(1)一种数据结构,它可以看作其他函数的描述,以及(2)一系列的参数,使该函数的行为恰如前面的那些函数作用于这些给定的参数。同时,只需要不多的技巧就能设计出一种数据结构,使其足以描述我们需要的所有计算。一种这样的数据结构(用于表示表达式和语句的带标签表,加上记录名字与值的关联的环境)和一个这样的普适函数(apply)将在本书的第4章介绍。也就是说,我们可能只需要一个函数和一种数据结构。
这些在理论上都是对的,但在实践中,我们发现区分不同事物确实很有帮助。作为人,在需要构造计算的描述时,应该把我们代码的结构组织好,使我们更好地理解它们。我相信Perlis在这里不是想讨论计算能力的问题,而是要讨论人的能力及其限度。
人的头脑似乎在为事物命名方面做得很好。我们有强大的关联记忆,给我们一个名字,我们可以很快想起与之关联的某些事物。这可能就是我们会发现使用lambda演算很方便,而使用组合演算就不太容易的原因。对大多数人而言,给他们解释Lisp表达式(lambda(x)(lambda(y)(+x y)))或JavaScript表达式x=> y=> x+y都比较容易,而解释下面的组合表达式就难得多了:
((S((S(K S))((S((S(K S))((S(K K))(K+))))((S(K K))I))))(K I))
即使将其写成结构与之对应的5行Lisp代码,事情也不会变得更容易。
所以,虽然从原则上说,我们可以只用一个普适函数,但却更应该把代码模块化,并为其中的各种片段命名。然后在这个普适函数里用名字来说这些函数的描述,而不是简单地把这些函数的描述直接塞进代码里。
我在1998年的演讲“生长出一种语言”里曾经说过,一个好程序员“并不只是写程序,好程序员要构造有用的词汇表”。随着设计和定义出程序中越来越多的部分,我们要给这些部分命名,这样做的结果就是生长出了一个日益丰富的、能用于描述其他部分的语言。
然而我们也发现,与区分不同的数据结构相比,为它们命名更不容易。
嵌套的表可以看作一种普适数据结构(值得说一下,很多时髦的、使用广泛的数据结构,例如HTML、XML和JSON,也都是各种括号括起的嵌套表示形式,只不过比Lisp的简单括号形式更精致一点)。还有许多函数在范围广泛的许多不同情景下都很有用,例如确定一个表的长度,把一个函数应用于一个表的每个元素并返回结果的表等。因此,当我思考某项特殊的计算时,就经常对自己说,“这个以两个东西为元素的表,我期望它表示一个人的姓和名;那个以两个东西为元素的表,我期望它表示一个复数的实部和虚部;另一个以两个东西为元素的表,我期望它表示一个分数的分子和分母”,如此等等。换句话说,我对它们做了区分——因此,明确地表示这些数据结构之间的区分,也可能非常有用。一种作用就是可以防止一些错误,例如无意中错误地把复数当作分数使用。(再次强调,这一注释也是有关人的能力及其限度。)
在写本书的第1版时,那是在大约40年前,许多数据组织方式已经成为相对的标准,特别是“面向对象”技术。还有很多程序设计语言(包括JavaScript)支持一些特殊数据结构,例如对象和字符串、堆和映射,还有许多内置的或者库支持的数据机制。但是,在这样做的同时,许多语言放弃了对更一般、更普适的描述机制的支持。以Java为例,开始时它不支持函数作为一等元素,新近才把这种功能结合进来,这极大地提高了表达能力。
类似地,APL原来也不支持函数作为一等元素,而且它原本只支持一种数据结构——各种维数的数组。以数组作为普适数据结构非常不方便,因为数组不能以其他数组作为元素。APL的新近版本也支持了匿名的函数值和嵌套的数组,这些扩充奇迹般地增强了APL的表达能力。(APL的原初设计确实包含了两个非常好的特点:它有一集容易理解的函数,它们都应用于唯一的一种数据结构,这些函数还被特别选择了一套名字。我这里不是说那些奇怪的符号或希腊字母,而是APL程序员在使用函数时所用的词汇,例如shape、reshape、compress、expand和laminate等。这些都是名字而不是符号,它们说明了函数的功能。Ken Iversion在为操作数组的函数取短小易记而且生动的名字方面确有些高招。)
至于JavaScript,与Java类似,最初设计时心里想的就是对象和方法,但一开始就纳入了一等函数,用它的对象定义普适数据结构也没有任何困难。由于这些情况,JavaScript与Lisp的距离不像你想象的那么远。因此,正如这一版《计算机程序的构造和解释》展示的,它可以作为表达相关核心思想的另一个框架。SICP并不是讨论某种程序设计语言的,它展示的是有关程序组织的一些强大且具有普适性的思想,因此应该适用于任何程序设计语言。
Lisp和JavaScript有哪些共性?把一项计算(代码加一些相关数据结构)抽象为一个可用于在将来执行的函数的能力;针对一些参数调用函数的能力;划定一些不同情况(条件执行)的能力;一种方便的普适数据结构;对数据的完全自动化的存储管理(这种功能初看起来无足轻重,好像什么都没说,直到你认识到许多广泛使用的程序设计语言都缺少了这种功能);很大一集操控普适数据结构的非常有用的函数;以及使用普适数据结构去表示各种更特殊的数据结构的一套标准策略。
因此,真理很可能位于Perlis雄辩地设置的两个极端之间。甜蜜点可能更像是某种情况,例如针对一种普适数据结构(例如表)完成各种操作的40个足够普适的有用函数,再加上10组每组各6个函数,分别用于操控该普适数据结构的10种特殊视角。
当你阅读这本书时,请不要只关心各种程序设计语言的结构及其使用,还应该关注赋予各个函数、变量和数据结构的 名字 。这些名字并不都简短并生动,如Iverson为其APL语言选的名字。但它们也经过精心地、系统化地选择,可以加深你对整个程序结构的理解。
基本操作、组合方法、功能抽象、命名,以及为了做出各种区分而使用的普适数据结构的各种常用定制方法,这些都是一个好的程序设计语言的基本构造要素。从这些出发,再加上想象和基于经验的良好的工程评判能力,就能做好其他事情。
Guy L.Steele Jr.
马萨诸塞州列克星敦,2021