大二时学习SICP过程中的一些简单总结

目录

  1. 过程和代换模型
  2. 高阶函数和数据抽象
  3. 层次性数据和符号数据
  4. 模式匹配和以规则为基础的代换
  5. 泛型运算符
  6. 副作用与环境模型

过程和代换模型

《计算机程序的构造和解释》这本书的目标并不是讲解一门编程语言的语法等,它是一种方法。不是在向你陈述知识,而是在教你如何做到想要做的东西。它是一个过程,一个精神。这些引导过程的东西就是所谓的程序规则的模式。

书中用了许多的例子来诠释书名,我才疏学浅就不再举例往博客上推了,仅仅归纳一点总结而已。

如果说程序是一种法术,那么控制神奇的法术就是过程。因此我们需要一门叫做Lisp的语言来唤醒这种精神。从书中的讲解中我们发现,Lisp语言的语法并不多,要记住这些语法并不难,但要理解这些规则的含义以及如何使用这些规则,则是要上很长的时间去修炼。深入理解了这些规则便会使你成为更优秀的程序员。

在计算机科学中,这并不是要告诉人们该如何算平方根,作者也说了如果这是计算机科学的全部,那么便没什么了不起的。真正的问题是,当我们试图建立非常大的系统时,这通常是几千页长的计算机程序。

之所以能完成这种庞大的工程,是因为有一些技术,用于控制这些大型系统的复杂性。而如果控制复杂性也是这门课的重点,某种意义上来说,这也解释了什么是真正的计算机科学。

作者举了一个例子,当一名工程师正在设计一个由很多实体构成的物理系统时。担心这个系统的工程师不得不去解决容许误差和噪音的问题。而我是一个电气工程师,我可以去很容易的建立起一级或二级的放大器,你也可以设想建造一百万个放大器的级联。但是去建造这样一种东西是非常荒唐的事情,因为在建造这一百万个放大器的很长时间之前,这些组件的噪音会得到放大,以至于这整个工程没有了意义。

计算机科学负责处理理想化的组件,我们很清楚的知道我们在将这些小程序和数据块放到一起。我们不必担心误差。那意味着在建立一个庞大的程序时,这和我能建立和我能想象的没有什么不同。因为这些部分是我知道我想要的抽象的实体。

很显然工程师是非常准确的,因此相对于其他类型的工程,你可以建立约束物理系统的限制,建立噪音和近视的限制,实行建设大型软件系统的限制是我们的思想局限。所以从这一点来看,计算机科学就像是工程的一个抽象形式。用在所有的工程中的一种技术,叫做黑盒抽象。

如果说求出X的平方根可能是一个复杂的整体的一套规则,那么最终会有,比如36的平方根,得出6。而真正重要的是,如果我想计算A的平方根加上B的平方根,那么你便可以用这个规则,并且将其看作一个无需知道内容的模块。因为从我们的角度来看,黑箱内有什么并不重要,因为我们只是想要知道他们的平方根的和。这在C等各种语言中都有体现。比如给一个函数传入一个参数,我们并不需要知道这个函数的具体操作,只是需要它们的代码,当然了,这里所说的函数是别人写好的库。

书中有一个求平方根的例子,为了去做些什么,我们需要去做一个猜想,并且不断的去改进它。这里我们可以做一个黑盒,上面写着“平方根”。这里是一个过程,它本身就是关于通用的战略的总体战略第一个关键——将黑盒抽象。将原始的程序和原始的数据结合起来作出更复杂的事情,这里就是说的组合。这是通过定义程序和处理数据抽象的复合数据的技术来完成。我们要用到的是一种叫做高阶函数的东西,它的输出和输出都是自己的程序。

如果用1和3乘以2,将会得到8。但当我们考虑所谓的线性组合的总体思路时,便可以很容易的添加两件事情和别的东西相乘。例如将向量a1和向量a2,通过一些因子来扩展它们,并且得到另一个向量。我们也可以考虑有两个多项式A1和A2,可能会用2或者其他的数值来乘以这两个多项式。又或者A1和A2可能是电信号,我们可能需要组合这两个电信号并且将其放大。

这就是前面所说的通用的战略的总体战略的第二个关键——常规接口。要做到控制复杂性,就需要建立常规的接口,将约定的东西放到一起。当在谈论真正的大尺度结构时,常规接口在现世界中建模赋值的系统时非常重要。这样的系统有两个非常重要的隐喻,一个被称为OOP(面向对象编程),将我们的系统分为许多小东西,它们之间发送信息交互。另一个称为流,在那里我们就像电气工程师将拼起电气系统一样拼起大型系统。

第三个关键则是基于技术控制的复杂性来构造新的语言——也就是书中第四章所说的元语言。因为当面临一个不堪重负的复杂设计时,也许选择一个新的设计语言是控制这种复杂性的很好方式。而新设计语言的目的是突出了系统的不同方面,它可以一直某些类型的细节,而强调其他种类的详细信息。这是这本书最神奇的部分,而建立新语言的第一步就是用Lisp过程来解释Lisp自身。或许更加神秘的则是仿佛Lisp有两个巨大的车轮——应用和循环。在书中封面你将会看到,它们分别是apply和eval。在我们后面的学习过程中将学习元语言抽象,这将帮助我们构建新的语言。所谓的逻辑编程语言,就是你并不需要研究输入输出的过程,需要研究的则是它们之间的关系。

在第一章我认为非常重要的一点是lambda和define之间的关系,这在第41页中有详细描述。

1
(define (plus4 x) (+ x 4))

等价于

1
(define plus4 (lambda (x) (+ x 4)))

要时刻记住这一点,因为在后面的复杂的程序中,会有很多个define出现,并仅仅有

1
(define (plus x) (+ x 4))

也有

1
(define plus (+ 4 4))

理解以下这两段代码非常有必要。

1
2
3
4
(define A
(* 5 5))
(define (D)
(* 5 5))

在这里,

1
2
3
4
A-->25
D-->compound procedure
(D)-->25
(A)-->error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(define A
(* 5 5))
;Value: a
A
;Value: 25
(A)
;The object 25 is not applicable.
;To continue, call RESTART with an option number:
; (RESTART 2) => Specify a procedure to use in its place.
; (RESTART 1) => Return to read-eval-print level 1.
;Start debugger? (y or n): n
(define (D)
(* 5 5))
;Value: d
D
;Value 12: #[compound-procedure 12 d]
(D)
;Value: 25

程序员通过构造程序和表达式来构建一个法术,而这些法术在某种程度上直接通过一个过程来实现这一目标。为了有效的做到这一点,我们必须要明白所写的特定事物之间的关系,这些特定的咒语,还有我们试图控制的过程的行为。

在这里我们有lambda,有definitions,有conditionals,还有combinations。怎样去求值组合呢?但真正需要组合起符号和数字的时候,数字会对自身求值,而在代换模型中符号将会消失不见。它们不会出现,直至你需要它们。如何来看求值一个应用程序的规则呢,当求值一个组合的时候,它有几个部分——运算符和操作数。运算符会返回到程序,如果求值运算符,我们就会得到一个过程,比如+运算符。替代过程的形式参数提供的参数,形式参数在过程的声明中得到定义。这样我们就可以来求值一个新的实体,这个实体通过用代换模型来复制旧的实体。

即便是对于简单的加法乘法,无论你深入到怎样的细节中去,在一台机器中,你都会发现有更深入的细节。因此我们要学会的便是忽视细节,理解复杂问题的关键是知道什么不用看、什么不用计算还有什么不用考虑。书中关于如果通过代换模型来计算3和4的乘积和已经非常完美了,通过这个例子我们已经看到代换模型的规则,那就是当求值运算符时会得到一个过程,当求值操作数时,如果还没有做应用,则仅仅是得到一个参数。也就是说对于(* 3 (+ 1 4)),(+ 1 4)就相当于是乘法运算符的操作数,在对其求值的时候,如果还没有得到应用,其仅仅是乘法运算符的参数而已。而对于应用这个词,应用序和正则序则是非常重要的概念。

通过代换模型去命名这些事物以及这些表达式。Gerald Jay Sussman教授说,每一个巫师都会告诉你,如果你有命名的精神,你就有了权力。

高阶函数和数据抽象

上一篇博文对应的是书中的第一章的一二两节,我们已经大致的有了一种构造的感觉不是么。书中展示了很多有趣的句法(syntax)。现在我们要让思想进一步的抽象,写这篇博客的时候并未学完整本书,更不敢说对书中的内容有一个多深的领悟。但我一路学习过来,就感觉书中的示例越来越抽象,作者所引导我们的也是如此方向。博文也会持续更新下去,伴随着我的理解。

在这个专栏的【Scheme归纳】4 高阶函数中已经初步介绍了什么是高阶函数(Higher-order Procedures)。而在这一节中,将用高阶函数来做抽象。书中分了许多小节来逐一介绍这一主题。在“过程作为参数”中,书中用了许许多多的相似示例来概括起一个抽象的操作。lambda用和define同样的方式来创建过程,唯一的区别仅仅是不为过程提供参数。这就是函数式编程和命令式编程的最大区别了,在C++等语言中我们总是会定义许多的变量和常量,而在Scheme中一路下来几乎都是函数。函数与函数之间也不再是用变量等做参数,再用一个return来返回,它可以用过程来作为参数,甚至过程还可以作为返回值。

在第38页前后的各种求和中,这些程序几乎是相同的,没有太多的区别。它们都具有相同条件,相同的谓词和相同的结果,并且这些代换方式都是如此相似。如果现在仔细回想,我们至今为止已经学习了哪些语法,哪些组合,哪些抽象,还有没有学到的常见模式。作者说,计算机应让人快乐,而不是人让计算机快乐。所以人们会创建让自己更容易编写程序、更易于阅读的编程语言,因此就引进了一切抽象的东西。

至今为止一直谈论的过程,仅仅是在提醒我们关于这个语言的框架,我们用原始的东西来构建一个系统。这其中用到了一些组合的手段,通过它把原始的东西做成复杂的事物;用抽象技术来利用复杂的事物以及给它们命名,这就像是搭积木一般。还可以用高阶函数来实现许多常规的方法。就像是书中第45页的不动点搜寻和牛顿法。通过组合起这些抽象将获得无穷的力量。

而这一切的关键思想就是去建立一个分层次的系统。因此举例来说,当我们在写一个求平方根的程序时,程序的某一部分就是用了一个名为good-enough的函数,而在那之间则存在着抽象。作者举例说,如果我们和George一起写一个求平方根的程序,而George的工作就是写good-enough。而我们并不用去关心这个函数是如何进行的。甚至即是George在这里函数里用了Harry写的抽象过程,我们也不用去关心甚至不用知道有这样一个过程或者Harry是不是存在。因为good-enough的细节是由George来完成的。在一个大型系统中,我们许多许多级别的抽象屏障(书中第58页讲解了抽象屏障),而我们要做的就是一遍一遍的去实施程序。

关于数据方面的问题,我们首先要将源数据放在一起组成比较复杂的复合数据,当我们在一组简单数据之上建立复合数据的时候要用到抽象的方法。其次,还要建立系统的层次。书中有有理数的例子来介绍了这一点。

在Lisp上有一个操作符叫做cons,其有2个参数x和y,然后返回一个序对pair。所谓的构造就是通过cons操作,在后面的学习中将会发现,无论是构造队列,甚至是构造流都是通过cons操作。书名中的Structure为名词构造,结构,动词的构造是construct。

上篇博文在控制复杂性部分用了级联一次,第79页就介绍了模块化结构。在真实的信号处理应用中,设计者通常总是从标准化的过滤器和变化装置族中选出一些东西,通过级联的方式构造出各种系统。所谓级联,就是将2种以上的设备、信号等通过某种方式连接到一起。可能我们暂时在实际中还用不到它,但无论如何,数据抽象都是一个强大的技术。

对于数据抽象计数,我最为惊叹的便是Church计数。也即是练习2.6,通过lambda演变就可以创建出所有的自然数和加减乘除。当然了,我的理解有限,不过我相信加减乘除之外的操作也是可以实现的。这道题也有一篇博文来解答:【SICP练习】47 练习2.6

层次性数据和符号数据

在学习书中第二章的时候有个问题一直让我很困扰,那就是2.2.4节的实例,因为没法输出书中华丽的图案,只能是一对英文字母。这在几个月前浅浅的学了一会Common Lisp的时候也是如此,当时看到书中有个实例是书中输出了很赞的线条,而我只会输出一堆点。后来才知道让Lisp输出图形化界面是更高层次的工程,想想还是先把SICP搞定。

上一篇博文中我们谈到了复合数据,关于它有两个重点。第一,数据抽象,这也就意味着你可以隔离那些数据对象。第二,在Lisp中有个特殊的方式能够黏住一些东西,它就是pair,而cons、car、cdr的实现方式我们暂时不必去研究。在BASIC和Fortran中构造一个数组并不是一个闭合的操作,因为你可以用数字、字符串和其它东西来构造数组,但你不能用数组来构造数组。而当你看着组合的定义时,你应该看看这些东西在组合的定义下是否是闭合的。因为我们可以形成序对的序对,我们就可以用序对用各种方式粘起各种东西。比如我们用1、2、3、4来构成一个序对,就可以有非常多种方式,一下只是一部分,而且还仅仅是按照大小顺序来构造的。

1
2
3
4
5
(cons 1 (cons 2 (cons 3 4)))
(cons (cons 1 2) (cons 3 4))
(cons (cons 1 (cons 2 3)) 4)
(cons 1 (cons (cons 2 3) 4))
(cons (cons (cons 1 2) 3) 4)

而且在任意的序对中,都可以有car、cdr的组合来得出其中的任一元素。我们可以建立元素本身也是序对的序对,这就是表结构得以作为一种表示工具的根本基础。我们将这种能力称为cons的闭包性质(the closure property of cons)。一般说,某种组合数据对象的操作满足闭包性质,那就是说,通过它组合起数据对象得到的结果本身还可以通过同样的操作再进行组合。闭包性质是任何一种组合功能的威力的关键要素,因为它使我们能够建立起层次性的结构(hierarchical structures),这种结构由一些部分构成,而其中的各个部分又是由它们的部分构成,并且可以如此继续下去。

在2.2.4节中,我们怎样在如此短的时间内就可以用简单图片来构造出复杂的图片,这就是因为Lisp有闭包的性质。我们可以拿一张图片来得到另一张beside版的图片,而且这张图片还可以rotate和flip,或者用这所有的操作。关于这部分作者说:When I take that element P, which is the Beside or the Flip or the Rotate of something, that’s, again, a picture. The world of pictures is closed under those means of combination. So whenever I have something, I can turn right around and use that as an element is something else. So maybe better than List and segments, that just gives you an image for how fast you can built up complexity, because operations are closed.

一个复杂的系统应该通过一系列的层次构造出来,为了描述这些层次,需要使用一系列的语言。构造各个层次的方式,就是设法组合起作为这一层次中部件的各种基本元素,而这样构造出的部件又可以作为另一个层次里的基本元素。在分层设计中,每个层次上所用的语言都提供了一些基本元素、组合手段,还有对该层次中的适当细节做抽象的手段。分层设计有助于使程序更加强健,使我们更有可能在给定规范发生一些小改变时,只需对程序做少量的修改。

我们可以组合东西,可以递归过程,可以做各种事情,而且这些都是自动的。我们仅仅使用语言的句法和将句法嵌入语言,这样我们就不会失去语言最原始的能力。表最伟大的是什么,如果只是用表去做一些尖刻的问题,这便是一门糟糕的语言。而去补充你想要的语言并且将其嵌入到表中,这才是表的伟大之处。这也正是所谓实现的真正力量——去设计一门语言。我们甚至可以走得更远,用表来让常规方法像高阶函数一样完成一些事情。

这里有一个层次允许我们去创建一个原始的图片,然后我们对图片进行各种操作。这些操作所发生的事情归结起来就是Henderson创建的图形语言。而且在这之上,我们也看到了由组合构成的schemes,由schemes构成的语言。比如说这里反复地使用一个比例系数。在这个层次所拥有的,这些被提到的事物被架设到上一个层次。事实上每一个节、每一个到这一层次的分解都被设计成执行具体的任务,而在其他层次,你却拥有全方位的力量。在任何层次,它没有被设置成一个具体的任务,而是被设置成整个范围。这一设计的结果就是被设计在方法中的功能更加健壮。

如果你在函数的描述中做些改变,它更可能是由相应的变化所捕获,在这里语言也会自动在下一级别执行,因为你已经完全控制了这一个层次。所以如果不是在谈论一个具体的函数,比如说Beside,那么你便拥有全局的控制,所以如果去进行一点点的改变,更可能的便是你的方法将有能力去捕获、去适应这一改变。而这样的设计则不会是强大的,因为如果我去改变一些东西,可能会影响整个层次,进而进入树的下一层。

对于由表,亦或是由符号来构造的软件的方法,去设计过程与其说是实现一个程序,不如说是构造一门语言。如果要构造出表(a b),不能用(list a b),因为这构造出来的是a和b的值的表,而不是这两个符号本身的表。因为假如a和b都被define定义成了具体的数值,比如1和2,那么(list a b)得出来的就是(1 2)而不是(a b)了。引号是很有威力的东西,因为它使我们可以构造起一种能操作其他表达式的表达式。所以我们应该用要这样写:(list ‘a ‘b)。

符号求导是一个挺难的部分,在这样有一个很重要的思想,在后面的习题中会有很多体现,那就是按愿望思维。所谓的思想,对于不同的编程语言都是通用的,这也就是为什么SICP是学习函数式编程最好的书籍。即便对于C等语言也是如此,当需要完成一个操作时,我们写了一个函数,一个类,但这个函数可以还要调用其它的函数,这个类也可能是由其它类继承而来。Lisp也是支持面向对象编程的,只不过比较痛苦。

模式匹配和以规则为基础的代换

在书中符号数据一节中,作者写了关于微积分的演算规则的程序。这是一个很程序化的程序,我们所做的是在讲这些(数学)规则翻译成计算机语言。因为它有程序化的行为和结构,那么存在其他的方法使书写这个程序更加清晰吗?这些规则都具有左右两侧。左侧是我们想要采取的导数的表达式,有右边则是其的替代。

用可以匹配的模式,和能够代换的框架,可以得到新的表达式。所以这意味着模式是对源表达式的匹配,并且规则的应用的结果是去产生一个新的表达式,而这个表达式是通过实例化一个框架来传入的目标参数。这就是所谓的实例化。而整体范围上这就是由规则所描述的过程。

我们试图找出解决一类问题的解决方案而不是一个个例的解决方案。书中采用的是关于负数运算的例子。在这个例子中,作者很好的封装了实例化、结构控制等思想,并且是用各自的规则进行的单独封装。

所以尽管是一个复杂的程序,但是每个复杂的程序都是由大量的简单片段所构成。这里的递归模式倒是很复杂,但明智的一点便是不去想这件事。如果总是想着要做的事情的实际的模式,我们便会变得很困惑。也许这并不是一项很好的实践。这些模式很困难,但我们没必要去想它们。关键是有一个好的编程技巧和优良的设计,并且知道什么不用去想。

没有必要知道每一个过程都该如何去执行,或者也可以去尝试这些模式。无论如何,这已经演变为编程这一层次上的递归,不断的将问题分解。

作者举了一个字典的例子:
在扩展字典时,我不得不去用一个模式、一个基准、一本字典。这个模式就是一个模式变量。我想要去找出这个模式变量的名字并且看看它是否存在,如果不存在则添加一个新的,如果存在,那么这便是我要找的字。当然,这是建立在这是我所期待的字典时。(比如说,我要查一个汉字的意思,却去成语词典中查则成语词典不是我所期望的。)

所以如果打开任何程序,我们所要找的就是它们中的一些小块,并且这一切都很容易。至于什么叫做简化的表达式,在复合表达式中,所有的片段都是简化的,并且这些规则会应用于它们的结果。对于所有的简化表达式,仅仅是用了所有的规则——去简化它们。所以简化表达式的规范是任何表达式通过所有这些规则而被分解出的简化的片段。

泛型运算符

关于数据抽象,有两个很重要的思想。一是在构建一些水平的抽象障碍在这个系统中。用某些对象来代换这些单独使用的抽象屏障。二是当需要使用某种形式的数据对象时,往往可以由其他人来将其实现,而我们并不需要知道具体操作。这都是在数据抽象方面非常强大的编程方法。

但是这还不足以完成一个真正复杂的系统。在这里的问题出在“其他人”,作者常举的例子是Georges。归结来说,有很多人都工作在这个系统上,但所有的这些设计表达式很显然是不兼容的。问题是所有的整个系统却又不可能由一个人来完成,可又无法让所有的实现不能互相干扰。在以某种方式设计系统时,我们就不仅仅需要这些水平的障碍,还需要某种形式的垂直障碍,让这些实现方法分割开来。

那么该怎么做呢,一种方式是,每个人都得去改变自己的设计风格去迎合这个系统。也这是人们经常尝试却又永远无法高效工作的。(Lisp太多伟大的特性,让程序员更加有效的编程。这些理念并不新颖,Lisp几十年前就有了,但有多少其他语言能够做到呢。当然也并非其他语言一无是处,我只是和前辈们一样觉得学习SICP和Lisp能够变得更强大。)而另一种方式,则是以某种方式来进行安排,做一些垂直的抽象障碍。例如当被问到人事记录中的名字时,不论以何种可能出现的输出格式,最终都会得到一个正确的名字。这就比如当打开一个国际网站时,不论其中的过程如何,最终得到的网页都会是我们所需要的语言。同一个网站,可能有英文、简体中文、繁体中文、法文、德文等等,也许不同的程序员负责不同的语言以及不同的见面,但最终通过垂直的抽象屏障将其分离开来,所以我们并不会在一个网页中看到多种语言同时混杂在一起的界面。

更重要的是,如果要设计一个系统,以便下一次有新的部门被纳入公司时,他们不必在已经关于这个系统做好的系统中做些重大的改变,并且公司的其他部门也不必去做重大的改变来让新部门融入进来。这才是我们应该考虑的问题,而不是一股脑的去改变所有的已写好的代码和结构。也就是分布式的一个浅显示例了,3.4节的并发也是一个很重要的理念。书中第135页的类型塔很有意思,仿佛就像类的继承一般。

副作用与环境模型

虽说叫做副作用显得不太好听,但在Lisp中副作用还是非常重要的。而相对于所有状态都必须显式地操作和传递额外参数的方式,如果引进赋值和将状态隐藏在局部变量中,那么就可以用更加模块化的方式来构造系统。

正如你所知道的,不用任何赋值的程序设计称为函数式程序设计。相反,广泛采用赋值的程序设计称为命令式程序设计。在C等命令式程序设计语言中,我们往往都要仔细考虑变量赋值的顺序,尤其是在循环中,但在函数式程序设计中这类问题根本不会出现。

1
2
3
4
5
6
7
8
(define x 1)
(define (add-x y)
(set! x (1+ x))
(+ x y))
(add-x 3)
5
(add-x 3)
6

在这里我们看到同一个表达式却会导致两种不同的结果,而这一切都取决于时间。

我们常用define来定义一个为定义的变量、函数等等,而不用其来做修改的操作。set!则来完成修改这一工作。经常用到的let实际上是一个过程调用的语法糖衣。用define定义一个符号,也就是在当前环境框架里建立一个约束,并赋予这个符号指定的值。

1
2
3
4
5
6
(let ((var1 e1) (var2 e2))
e3)=>
((lambda (var1 var2)
e3)
e1
e2)

在前面的代换模型中,我们是这样求组合式的:
1)求值该组合式的各个子表达式
2)将作为最左子表达式(运算符)的值的那个过程应用于相应的实际参数,所谓实际参数也就是其他子表达式(运算对象)的值。
而在环境模型中,第二步就有所差异了:
1)求值该组合式的各个子表达式
2)将运算符子表达式的值应用于运算对象子表达式的值。

在将一个过程应用于一组实际参数时,将会建立起一个新环境,其中包含了将所有形式参数约束与对应的实际参数的框架,该框架的外围环境就是所用的那个过程的环境。

书中这部分可谓归纳的非常好,过程应用的环境模型的两条规则:
1)将一个过程对象应用于一集实际参数,将构造出一个新框架,其中将过程的形式参数约束到调用时的实际参数,而后在构造起的这一新环境的上下文中求值过程体。这个新框架的外围环境就是作为被应用的那个过程对象的一部分的环境。
2)相对于一个给定环境求值一个lambda表达式,将创建起一个过程对象,这个过程对象是一个序对,由该lambda表达式的正文和一个指向环境的指针组成,这一指针指向的就是创建这个过程对象时的环境。

现在我们已经有能力去使用独立的本地状态和对象模型,也会进行一些比较复杂的编程。我们活在物理世界中,也以物理世界的方式去思考,这正是由这些构成了整个世界。为什么物理学家、天文学家等在寻找系外生命时要以地球的指数为参照,比如说温度、大气、土壤、公转和自转等,因为有些事物是共通的。之前看到过一个老外写的书,当时对软件这个专业还没有多少认识,书也全是理论比较晦涩因此没能看多久,不过记得作者的一个比喻非常好。其认为写一个软件就像设计一座建筑,每部分有组织的组合拼凑在一起。

《计算机程序的构造和解释》虽然已经有了三十多年的历史,却依旧格外而不落伍的原因便是其是在讲解如何去构建一座建筑。它会告诉我们建筑的每个部分都有哪些功能和优缺点,这个柱子该放在哪里撑着,两层楼之间该怎样结合等等。而各自语言就像修建这个建筑的各种材料,比如钢铁和木材等。其并没有太注意去讲解语言的语法,一来这是介绍“建筑材料”的书籍需要做的,二来Lisp的语法也不太繁杂。

在我们的脑海中有对这个世界的认识以及一个不断构建中的模型,当我们在编写软件时,这些模型也即体现于计算机中。我们想要做的无非就是让这两者之间进行通信。这就带来了模块化,如果我们真的相信世界就是由这样的小片段所组成,那么我们也同样可以将这种思想应用于我们所构造的模型中,世界上的这些事物也将能够继承这种模块化设计进入我们的程序之中。


本站地址:http://nomasp.com/

欢迎交流,转载请联系本人,多谢🙏