这个博客仅仅记录了学了一定阶段写下的看法和总结,如果有错误的地方请指正,大家一起学习交流,毕竟巫书还是比较抽象的。。。。。
先安利一个不会的习题我就去看答案的地方,里面也给出了测试代码的环境。http://sicp.readthedocs.io/en/latest/index.html
第一章 构造过程抽象
第1章主要是介绍了lisp基本的语法和怎样一步一步把一些过程抽象出来,主要是以一些常见的数学问题做原型。
书中提出基本的表达形式、组合的方法、抽象的方法这三种机制是一个强有力的程序设计语言必须提供的,第一章就是围绕着这三种机制展开的。
1.1
在Lisp的方言scheme里,表达式以前缀形式展示,例如(+ 1 2 3)就是1+2+3,直接键入这样一个简单的表达式就可以马上得到答案6。如果把表达式进行嵌套表示,就得到了组合式,而求解一个组合式,文中提到分为两个过程:
1.求值该组合式的各个子表达式
2.将作为最左子表达式的值的那个过程应用于相应的实际参数。
有点抽象,其实就是递归求解。想象一下3+4*(5+2),要得到结果,就需要得到’+’两边的结果,接着需要求出4*(5+2)的值,要得到它的值,又需要的得到(5+2)的值。这个过程可以用二叉树来表示,文中提到叫树形积累。
一门编程语言,总不可能写的代码都是确定的数吧,用变量代替数字,以及用函数抽象一个功能,这些都是应该有的。scheme引入了define(定义)这样的表达方式,可以用于数据和过程,类似其他高级语言的定义一个变量或者函数。利用define就可以完成一个过程的抽象,那么问题是对一个过程进行相应的计算,采用的是先把这个过程完全展开再计算还是先计算再应用。第一种方式称为正则序求值,第二种成为应用序求值(实际应用这种方式,因为可以避免重复计算)。
接下来文中给出了条件判断的语法,cond和if,if很容易理解,因为其它语言基本都有。cond可以这样理解,它给出了多个条件-值,这样的序列,直到条件为真时,才停止cond这一块的执行。
接下来主要提出的关于局部性的一些问题,比如定义了一个过程名sqrt
,那在这个程序的其它地方,就不允许再定义名为sqrt
的过程或名字了。并且我们完成一个过程假如是多个过程构成的,那其他人是没必要知道这里面的其它过程而只需要知道这个完整过程是怎么使用的就行了,而程序的其它地方也应该能使用这些小过程的过程和变量名,所以引入了会结构和内部定义的概念,语法表示就是在define定义里面包含其它的define。
1.2
这里介绍了递归计算过程和迭代计算过程。所谓递归计算,就是在计算的过程中要不断保存本次递归的变量值等数据然后不断地深入求解,最后再一层一层返回,利用了虚拟内存中的栈空间来实现。迭代是指可以用固定数目的状态变量描述的计算过程。虽然递归明确易懂,但是递归的次数是有限的。在lisp语言中,递归和迭代很容易弄混,这是因为在其它语言中,描述迭代通常是用for、while等循环方式,而lisp都是递归过程。。。。书中的解释是说一个过程是递归的时候,描述的是语法形式上的事实(调用了过程本身)。而在一个计算过程中,进行计算的过程方式是不同于语法形式上的递归的。所以有可能一个递归过程进行的是迭代计算。lisp中把执行迭代计算的这种递归称为尾递归,效率和迭代差不多高。
树形递归的含义就是每一层有不止一个分支,而是多个,这样递归就形成了一棵树,成为树形递归。书中给出的用递归计算Fibonacci数列,就属于树形递归,虽然容易理解,但是有太多次多余的计算。我们可以避免这些多余的计算来达到高效,具体的方法可以利用每次计算的值,如果已经有了结果,那直接使用就行。而相应的迭代计算,虽然高效,但是较难理解。
接下来提到的增长阶,其实就是一种表达程序执行效率的方式。没什么好说的。
关于求幂问题,普通的做法是一步一步地乘,而书中给出了一个公式,这个方法采用了二分的思想,效率很高,是一种快速幂算法。而关于最大公约数,用到了辗转相除法,都是一些数论中的知识。素数检测给的第一种方法效率不是很高,其实有一种更好的方法叫素数筛法,网上可以查的到,是基于这个算法的基础之上再进行优化的算法。费马小定理是跟概率相关的一个检测素数的效率很高的算法,虽然书中提到有些数可能可以骗过这样的检测,但是貌似几率很小。
1.3
第一章最后一个部分用高阶函数做抽象,之前的抽象是把数据作为基本的构件去抽象,而用高阶函数做抽象就是把过程作为参数以及把过程抽象成具有一般性的方法和过程作为返回值。
过程作为参数可以理解为当有几个问题,它们的基本功能都是一样的,但是具体实现的一些细节不同,就可以把基本功能抽象成一个整体的框架,而具体的实现细节抽象成各自不同的方法。比如书中的例子求和方法,求和都知道是每个元素相加得到的结果,但是不同的求和,它的元素的计算方式可能不同,就可以把求和的公共部分抽象出来,而不同的部分由不同的过程分别抽象,再作为参数传递到公共过程去。
接下来提到了用lambda
来构造一个匿名的过程,这是因为比如一个过程里面需要其它的过程,如果没有匿名过程的存在,那我们就需要分别给这些小过程都要命名,这其实是不必要的。所以引入lambda
就可以完成这样的要求,而不需要再去定义那些小过程。而用let
创建局部变量就比较有意思了,比如我在let
外部定义了x = 3
,接着在let
中定义了x = 4
接着又定义了(y (+ x 10))
,结果y
的值为13,而不是14,这点跟其它语言有点不同,因为都是在同一个语句块里面定义的,x
所使用的值应该是局部变量x
的值才对。在lisp中,可以理解为let
中的每一个<var>-<exp>
对都是独立的,数据互不干扰。做了个例子事实证明的确是这样:
(define x 2)
(let ((x 3)
(y (+ x 3))
(z (+ y 1))
(+ x y z))
运行了会报错,提示y没有定义,这就说明了在计算z的时候,y的值由于外部并未定义,所以导致了报错。
而过程作为一般性的方法,其实跟上面提到的具有公共部分的过程有些类似,它和特定的函数无关,而是提取了许多问题的共有的特性出来抽象。书中的例子通过区间折半寻找方程的根就是一种,求解f(x)=0,不管这个f(x)到底是什么只要它连续,作为这样的方程,都具有如果f(a)<0<f(b)
,那么根就一定在a和b之间,这样就可以不断地缩小范围,最后求得解。函数的不动点也是一个例子,不管这个函数是什么,怎么计算,只要满足f(x)=x,那么x就是它的不动点,求解的方式也不管函数是什么,反复应用,f(x),f(f(x)),f(f(f(x))))。。。。就行。
过程作为返回值,顾名思义,就是返回值不是一个值,而是一个过程。功能和过程作为参数类似。
差不多第一章的内容就这么多。感叹下lisp这种函数式编程语言,它的描述能力确实比其它语言要强,语法方面很简洁优美,很适合描述解决一个问题的思想本身。