1.1程序设计的基本元素
1.1.1表达式
1. 组合式
(+ 3 4)
>7
在以括号为边界,把左侧的运算符所刻画的过程应用于右侧所有元素(结果会自动打印出)
此表达式为组合式
2.允许组合式镶嵌
(+ (* 3 5) (- 10 6))
>19
3. 美观格式
过多的组合式镶嵌会导致难以阅读代码,所以通过格式来更好的显示表达式的结构
(+ (* 3
(+ (* 2 4)
(+ 3 5)))
(+ (- 10 7)
6))
>57
各个运算对象垂直对齐
1.1.2命名和环境
1.命名
变量:通过名字去使用计算对象的方式。名字标识符成为变量
Scheme中用define(定义)给事物命名:
(define size 3)
解释器将3与size相关联,就可以使用这个名字去引用值3
2.环境(全局)
名字-值对偶的轨迹被储存起来,解释器维护着这一储存能力,这种储存被称为环境(精确的说是全局环境,因为以后将看到,在一个计算过程中完全可以涉及若干个不同的环境)
1.1.3 组合式的求值
1.组合式的规则
1)先对组合式的各个子表达式求值
2)将所得值应用于最外层的最左侧运算符刻画的过程下
思考:此过程在性质上是递归的。
2.树形积累
(* (+ 2 (* 4 6))
(+ 3 5 7))
用一棵树的形式表示:
观看上面的树形图可以发现,这种处理方式具有层次性这种计算过程成为树形积累。
3.处理基础情况的规定
1)数的值就是它们所表示的数值
2)内部运算符的值就是能完成相应操作的机器指令序列
3)其他名字的值就是环境中所关联于这个名字的对象
4.通用规则和专门规则
以上所说的规则使用于通用规则,而专门规则是一种特殊形式,每一种特殊形式都有自身的求值规则,例如:
(define x 3)
含义:把名字x与值3关联起来,并不是将define所刻画的过程应用到x和3
1.1.4 复合过程
1.过程定义
实现一个过程然后为这个过程关联一个名字,再以这个过程的名字为一个单元。我们就可以使用这个单元去构造其他的过程:
(define (square x) (* x x))
(define (sum-of-squares x y)
(+ (square x) (square y)))
只看sum-of-squares的定义,无法分辨square是(像+和*那样)直接做在解释器里呢,还是被定义为一个符合过程。
1.1.5 过程应用的代换模型
1.代换模型
在使用一个被定义的过程时,会用实际参数来代换形式参数进行求值:
(define (square x) (* x x))
(square 6)
>36
square的内部会进行一系列的代换过程。
注:代换的作用只是帮助我们领会过程调用的情况,而不是对解释器实际工作的具体描述。通常解释器不采用直接操作过程的正文,再用值去代换形参的方式。(在第3章和第4章会考察一个解释器的细节实现)
2.应用序和正则序
1)应用序:先求值参数而后应用
(sum-of-squares (+ 5 1) (* 5 2))--->(sum-of-squares 6 10)--->...--->136
- 正则序:完全展开后归约
(sum-of-squares (+ 5 1) (* 5 2))--->
(+ (square (+ 5 1)) (square (* 5 2)) )--->
(+ (* (+ 5 1) (+ 5 1)) (* (* 5 2) (* 5 2)) )--->
;;而后是归约
(+ (* 6 6) (* 10 10) )--->
(+ 36 100)--->
>136
可以看出(+ 5 1) 和(* 5 2)做了两次求值,所以应用序的求值要简单,而正则序也是特别的有价值的工具,在后面我们会学习正则序的某些内在性质。
1.1.6 条件表达式和谓词
1.cond(表示条件)
cond条件表达式的一般性形式为:
(cond (<p1> <e1>)
(<p2> <e2>)
:
(<pn> <en>))
<p1>是谓词,谓词的值只有true和false.谓词可以是else(此外)
<e1>是过程或一个值,当<p1>为ture时,就会执行这一过程
例如:
(define (abs x)
(cond ((> x 0) x)
((= x 0) 0)
(else (- x))))
2.if条件
if条件表达式只适用两种情况的形式。
一般形式:
(if <p>
<c>
<a>)
<p>是谓词
<c>当<p>为ture时执行(只能是单个表达式)
<a>当<p>为false时执行(只能是单个表达式)
例如:
(define (abs x)
(if (< x 0)
(- x)
x))
3.符合谓词
1)与:每个<e>都为ture才为ture,否则为false
(and <e1> …<en>)
2)或:至少有一个<e>为ture才为ture,否则为false
(or <e1> …<en>)
3)否:<e>为ture,则为false。<e>为false,则为ture。
(not <e>)
练习最后集中解答
1.1.7 实例:采用牛顿法求平方根
我们只知道平方根的含义是什么,但是如何利用计算机来实现求平方根的过程是很困难的。我们觉得只要知道平方根的定义我们就能做到。
平方根定义:
根号x = 什么样的y, 使得y>= 0而且y的平方=x
可以看出从定义也不能告诉我们如何求平方根,我们需要一个描述计算过程的方法,所以我们用到了牛顿法求平方根(怎么哪都有他)
代码实现:
(define (average x y)
(/ (+ x y) 2))
(define (abs1 x)
(cond ((< x 0) (- x))
(else x)))
(define (square x)
(* x x))
(define (improve guess x)
(average guess (/ x guess)))
(define (good-enough? guess x)
(< (abs1 (- (square guess) x)) 0.001))
(define (sqrt-iter guess x)
(if (good-enough? guess x)
guess
(sqrt-iter (improve guess x)
x)))
(define (sqrt1 x)
(sqrt-iter 1.0 x))
(sqrt1 0.0009)
>0.030000012746348552
1.1.8 过程作为黑箱抽象
1.过程抽象
注:因为敲代码abs这个名字和编译器内部的过程重名所以上面的名字用的是abs1,同理sqrt也是这个原因。
故:abs = abs1, sqrt = sqrt1
从上面的求平方根的过程分解图中,这一分解过程不仅仅是把一个问题分成了几个部分,而是分解中的每一个过程完成了一件可以清楚表明的工作,它作为定义其他过程的模块。比如说square这个过程,我们在定义good-enough这个过程的时候,我们知道square这个模块是求平方的就行了,而square过程的内部具体是怎么实现的被隐去不提了可以看作一个黑箱,因为这一过程在我们脑海中没有明确的实现过程只是知道黑箱的用法,所以这个黑箱中的过程是抽象的,这一黑箱中的过程被称为过程抽象。所以有些代码我们没有去写,可以从其他的程序员那里作为一个黑箱接受并使用。
2.局部名
一个过程的形式参数的作用域就是这个过程。与形式参数的名字无关,一个过程的定义引用了另一个过程,这两个过程的形式参数即使是一样的名字,但相同的名字确实不同的概念互不影响。例如:
(define (square x)
(* x x))
(define (good-enough? guess x)
(< (abs1 (- (square guess) x)) 0.000001))
square的形式参数x和good-enought?的第二个形式参数x虽然名字一样,但概念不同且互补影响。good-enought?过程中引用了square,给square的参数guess对于square来说是实参,在计算过程中可以理解为guess会代换掉square的形式参数x进行计算,square本身作为一个黑箱是独立的存在不会因为,其他过程的形式参数与自己相同而产生混淆。
一个过程的定义约束了它的所有的形式参数(不同过程参数互补影响等)我们把这个形式参数的名字称为约束变量。good-enough?中的< ,abs1, square是自由的 。约束变量也就是形式参数的作用于就是被声明的过程体中。 切记在同一作用域中不能出现相同的名字。
3.内部定义和块结构
从上面的并列声明(在同一作用域中声明的所有过程)存在一些问题随着定义的过程的名字的增多,我们所用的名字就会越来越少(同一作用域中不能出现相同的名字)我们就不能在使用abs,good-enought?等被定义过的名字。更令人苦恼的是当几个程序员构造一个庞大的程序时,当把所有程序员的程序合在一起的时候会出现大量的重名错误,也不便于代码的阅读和维护。如何解决这个问题?很简单我们只需要缩小部分过程的作用域就行了。我们采用嵌套式的定义,即把过程定义到被使用的过程声明的内部(要分清出过程的主次)例如:
(define (sqrt2 x)
(define (improve guess x)
(average guess (/ x guess)))
(define (good-enough? guess x)
(< (abs1 (- (square guess) x)) 0.000001))
(define (sqrt-iter guess x)
(if (good-enough? guess x)
guess
(sqrt-iter (improve guess x) x)))
(sqrt-iter 1.0 x))
这种嵌套的定义成为块结构
我们还可以进行简化。因为sqrt2中的约束变量x的作用域包含了good-enough?,sqrt-iter和improve的声明所以,我们可以让sqrt2中的约束变量x作为内部定义的自由变量:
(define (sqrt2 x)
(define (improve guess)
(average guess (/ x guess)))
(define (good-enough? guess)
(< (abs1 (- (square guess) x)) 0.000001))
(define (sqrt-iter guess)
(if (good-enough? guess)
guess
(sqrt-iter (improve guess)
)))
(sqrt-iter 1.0))
习题:
练习1.1
解:自己敲一敲键盘
练习1.2
解:
(/ (+ 5
4
(- 2
(- 3
(+ 6
(/ 4
5)))))
(* 3
(- 6 2)
(- 2 7)))
>-37/150
练习1.3
解:
(define (sum-max a b c)
(cond ((and (>= a c) (>= b c)) (+ a b))
((and (>= a b) (>= c b)) (+ a c))
((and (>= b a) (>= c a)) (+ b c))))
(sum-max 3 2 4)
>7
练习1.4
解:当 b>0时 返回a+b
当 b<=0时 返回a-b
练习1.5
解:
应用序:求不出结果(死循环)
正则序:结果为0
因为:
(define (p) (p))
是一个自己调用自己的一个递归过程,在运行:
(test 0 (p))
时,应用序会先把(p)算出来,而从(p)的定义可以看出这是个死循环。
但是正则序不会先计算(p)而是最后归约的时候再计算,不巧的是再还没有归约到它的时候,运算结束,所以从始至终都没与计算(p)。
1.6
解:定义的new-if函数采用的是应用序,sqrt-iter中有递归会陷入死循环,而系统的if是正则序
1.7
解:
把
(define (good-enough? guess x)
(< (abs1 (- (square guess) x)) 0.001))
改为
(define (good-enough? guess x)
(< (abs1 (- (square guess) x)) 0.000001))
即:把0.001改为0.000001其目的是增加逐渐逼近的精度。
1.8
解:
#|求立方根|#
(define (cube x) (* x x x))
(define (improve2 guess x)
(/ (+ (/ x (square guess)) (* 2 guess)) 3))
(define (good-enough2 guess x)
(< (abs (- (cube guess) x)) 0.000001))
(define (cube-iter guess x)
(if (good-enough2 guess x)
guess
(cube-iter (improve2 guess x) x)))
(define (cube-root x)
(cube-iter 1.0 x))