SICP1

前言与背景介绍

SICP是著作Structure and Interpreatation of Computer Programs的简称,同时是北美一众CS强校(例如加州伯克利,滑铁卢大学等)的CS第一门专业课Functional Programming(下简称FP)的教材。笔者抱着兴趣与学习计算机技术与一窥人类计算机教育最前沿的目的,在有一定计算机基础的情况下接触了这门课(学习资源为SICP、加州大学伯克利分校于YouTube上上传的网课CS61A、以及该课程任课教授在互联网上完全公开的教案、教学顺序表),只是初探就受益匪浅,完完全全刷新了本人对计算机及编程教育的认知,相比国内的计算机教育,北美的计算机教育显然显得更为成熟,而且对待有无计算机基础的同学并无差别,这是我们非常值得学习的。
从内容讨论这门课程,课程的中心在于编程 而不是像通常我所熟知的国内计算机教育顺序中心在于 编程语言。相比较于国内一上来就用C语言作为学生的“开山语言”,FP则使用了不用关心内存分配,不用担心抬头引用的高级语言Scheme,引用课程中教授的原话并做翻译:“我们学习这门课是让你们对计算机有一个大的框架,让你们知道编程到底是什么,剩下的问题例如内存分配,那是你们在学会编程之后的细节问题。”关联我当时学习计算机的过程,在学习C和C++时,我们更关注的好像确实不是编程本身,而是C++的各种已经封装好的工具,就像知道继承多态的好像就要比只知道函数的同学要上一个档次,知道STL的同学就要比知识到达继承多态的同学要牛逼一些。
本课程的目的在于使用最简单的工具(if-else函数与condition函数)将我们日后所熟知的大部分功能函数以及数据结构实现,扪心自问一下,自己是否真的学会的“递归”这一重要方法,如果只有一个if-else函数,你能否写出while函数?能否完成线性表?树?图?作为自以为是的学生,我本人需要反思,同时我们的计算机教育也需要反省并承认自己的不足及落后了。这一系列文章相当于是在学习该门课程中的各种总结,以及有些在书中课里资料里都找不到的问题答案,我也会从stack overflow中翻译整理下来,为技术打破语言壁垒尽一份力。
我在写相关系列文章时会尽量将一些重点名词用原文代入,因为本人计算机水平过浅,随意翻译可能会导致信息偏差。同样本人计算机水平十分有限,有错误之处在所难免,还请见谅。
附CS61A课程网站:https://inst.eecs.berkeley.edu//~cs61a/su10/index.html课程教案、讨论内容、作业包括课本都在该网站上免费向全世界提供。
特别致谢CS61A任课教授、http://teachyourselfcs.com网站所有、以及在stack overflow、CSDN、知乎等平台为scheme这门冷门计算机语言答疑解惑,为学习SICP的学生们指路的前辈。

Scheme安装与开始步骤(2021年)

由于网上较多流传的网课版本为2010年的CS61A录像,课堂上教授使用的编辑器与编译器在十年后的今天有了更优选择,请自行搜索并于官网下载DrRackethttps://racket-lang.org/,下载并使用exe安装完成后请于安装处打开文件夹,双击打开,看到所示页面点击package manager,在这里我们有两种选择,都同样的可以满足本课程的全部所需,见下图点击下载即可。进入DrRacket后你会发现界面分为两个部分,上面的部分是我们需要输入代码的区域,而下半部分则是输出反馈。
请记住,在之后的每一次编程中我们都需要在编程开始时选择语言,即在第一行输入# lang simply-scheme或者# lang sicp(基于个人安装),想要执行程序点击右上角Run即可。注意,当我们选择语言并且至少成功执行了一次后就会发现在我们的输出区域出现如下字样:若无,则有可能出现编译错误或者自己在写程序时忘记添加语言选择。

scheme基础语法

基础运算以及编写规范

scheme的基础语法以operator(操作符) 与 operands(受操作对象)组成,在scheme中将二者联系起来的符号是括号(paretheses)和空格,我们以最简单的加法为例

# lang simply-scheme
(+ 1 2)

以此为例,+是我们的operator,而1、2则是operands,scheme提供前置算符运算,即所要进行的操作规则前置,要进行该项操作的对象之间用空格隔开,并与相应操作规则用同一个括号括起来。不光是加法,所有的基本运算以及之后我们要学习到的一些定义运算都符合这个规则,如果说在scheme语法中需要注意的事项,那么我认为只有一个:括号。浏览stack overflow,绝大多数初学者在编译出现问题时都是忘记在某一个不显眼的地方忘记了括号。scheme为nesting(嵌套)的运算提供了比较简便的输入方法例如:

# lang simply-scheme
(* (+ 1 2) (+ 3 4) (+ 5 6))

但这么些会出现一些问题,就像我们之前提到的括号是scheme中最重要的元素,写成一行会让代码可读性很差,为此我们规定一些代码编写规则,在嵌套运算时,长元素之间用换行分割,而运算归属(或者是先后)则用缩进来表示,以上式为例

# lang simply-scheme
(* (+ 1 2)
   (+ 3 4)
   (+ 5 6)
)

式中并列的三个元素代表它们地位相等,在缩进中都在*之后,代表他们都是该操作符的元素。在scheme中(在大多数情况下都代表着某一个操作的开始,在写代码时请不断自我审问,我要开始某一个操作指令了吗?我加括号了吗?
编程中还有一个重要的内容,comment,scheme的comment符号为;在此之后的所有内容计算机不会编译。
引用书中原话,在编程的过程中我们只处理两种东西,procedure和data,从计算机的角度出发二者在计算机内的存储处理方式其实都是数据,只不过一个是我们要操作的对象,另一个则是我们进行操作的方法。接下来我们看一种特别的操作方式。

define

我们可以用该命令对某个数据附值,例如:

# lang simply-scheme
(define a 2)

意为将2的值赋予名称a,之后我们可以使用a来代替2进行一系列代数运算,在输入端执行改代码后,在输入端输入(其中>为输入命令,无该符号则是电脑输出结果)

>a
2
>(+ a 1)
3

然而define的强大之处不止于此,我们看下面的一个例子

# lang simply-scheme
(define (square x) (* x x))

运行之后在输出端输入

>(square 10)
100
>(squre 3)
9

根据我们的输入代码以及相关的测试结果,我们不难得到,我们使用definesquare定义为了函数,而函数表达式为随函数后跟的参数的平方,在此先给出define在定义函数时的用法

(define (函数名称 参数formal_parameter) (函数主体body));参数可以不为一,函数主体中为相应的表达式

相应的我们在调用已经定义的参数时遵守相应规则,(函数名称 参数)。值得一提的是,当我们在写函数主体时可以不拘束于基本的运算,函数主体也可以包含函数,还是用书上的原例子,接续上面的函数继续,(注意以上样例公式中为了表达简便并没有按照代码编写规范,在如下实例中我们运用代码表达规范对define进行规范表达,请一定养成这样的习惯。)

(define (sum_square x y) 
        (+ 
            (square x) 
            (square y)
        )
)

此例中针对一个函数我们有两个参数,且在函数主体中调用了之前存在的函数进行运算。
我们借由CS61A中教案的例子,加深对define命令的认识
思考

(define x 3)

(define (x) 3)

有什么差别?根据我们上面的所学知识,(define x 3)的目的是将数值3赋予x,而(define (x) 3)则完全不一样,它的意思是创造一个函数函数的名称为x,而这个函数就是3。我们分别运行以上两个函数,然后在输入端输入以下指令

>(x)

第一个函数报错,第二个函数正常得出结果。

>x+3

第一个函数正常运行,而第二个函数报错。这是因为我们最后得到的两个x他们的数据结构是不同的!!!请谨记这一点。