1.1 程序设计的基本元素 =========================== 编程语言的三种机制:基本表达形式、组合方法、抽象方法。 在程序设计中要处理两类要素:过程和数据,实际上两者并不严格区分,用于构造过程的规则同样适用于操作各种数据。 1.1.1 表达式 --------------- 前缀表达式-将运算符放在所有运算对象的左边, 完全适用于带有任意个实参的过程, 可以直接扩充, 允许出现组合式嵌套的情况。 读入-求值-打印循环 .. code-block:: scheme (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6)) 1.1.2 命名环境 ------------------ 使用 ``define`` 进行变量命名。 为存储值与变量名的关联, 解析器必须维护某种存储能力, 此即为环境。 .. code-block:: scheme (define pi 3.14159) (define radius 10) (define circumference (* 2 pi radius)) 1.1.3 组合式的求值 -------------------- 组合式的求值是一个递归的过程: 1) 求值该组合式的各个子表达式。 2) 将作为最左边表达式(运算符)的值的那个过程应用于相应的实际参数, 而这些参数即为其它子表达式(运算符)的值。 ``define`` 作为一个求值规则的特殊形式, 并不是将实际参数作用于 ``define``, 而是为第一个参数(变量)关联第二个参数(值)。 1.1.4 复合过程 -------------------- .. code-block:: scheme (define (square x) (* x x)) (define (sum-of-squares x y) (+ (square x) (square y))) 1.1.5 过程应用的代换模型 -------------------------------- 正则序求值-完全展开而后规约。 应用序求值-先求值参数而后应用。 应用序和正则序的不同,Lisp采用应用序以避免对表达式的重复求值 1.1.6 条件表达式和谓词 ---------------------------- .. code-block:: scheme ;; (cond ( ) ;; ( ) ;; ... ;; ( )) (define (abs x) (cond ((> x 0) x) ((= x 0) 0) ((< x 0) (- x)))) (define (abs x) (cond ((< x 0) (- x) (else x)))) ;; (if ) (define (abs x) (if (< x 0) (- x) x)) *练习 1.1* -------------- :: > 10 10 > (+ 5 3 4) 12 > (- 9 1) 8 > (/ 6 2) 3 > (+ (* 2 4) (- 4 6)) 6 > (define a 3) a > (define b (+ a 1)) b > (+ a b (* a b)) 19 > (= a b) #f > (if (and (> b a) (< b (* a b))) b a) 4 > (cond ((= a 4) 6) ((= b 4) (+ 6 7 a)) (else 25)) 16 > (+ 2 (if (> b a) b a)) 6 > (* (cond ((> a b) a) ((< a b) b) (else -1)) (+ a 1)) 16 *练习 1.2* -------------- :: > (/ (+ 5 4 (- 2 (- 3 (+ 6 (/ 4 5))))) (* 3 (- 6 2) (- 2 7))) -37/150 *练习 1.3* -------------- 中文版翻译有误, 原书此题目为 Define a procedure that takes three numbers as arguments and returns the sum of the squares of the two larger numbers. 所以应该是: 返回其中较大两个数的平方和 .. literalinclude:: code/ex-1.3.scm :language: scheme *练习 1.4* ------------ 给出的函数实现的功能是: 接受两个参数, 返回第一个参数与第二个参数绝对值的和 .. code-block:: scheme (define (a-plus-abs-b a b) ((if (> b 0) + -) a b)) *练习 1.5* ------------- .. code-block:: scheme (define (p) (p)) (define (test x y) (if (= x 0) 0 y)) 对于应用序, "先对参数求值然后应用", 则对 ``p`` 的调用将会陷入死循环; 对于正则序, "完全展开而后进行规约", 则对 ``test`` 展开时对 ``if`` 语句判断为真后直接返回 0, 从而避免了对 ``p`` 的调用。 1.1.7 实例: 采用牛顿法求平方根 ------------------------------- .. literalinclude:: code/ch1-1.1.7.scm :language: scheme *练习 1.6* ------------- 对于自定义的 ``new-if``, 根据 **Lisp** 的应用序求值原则, 不论 ``predicate`` 真假, 两个参数 ``then-clause`` 、 ``else-clause`` 都会被求值, 这样将会导致陷入对 ``sqrt-iter`` 的递归调用, 进而因递归调用的栈深度超过阀值而导致程序崩溃。 *练习 1.7* ------------- 在被求值的数很小或很大时,因为变化程度很小会导致程序进入死循环 修改后的程序如下: .. literalinclude:: code/ex-1.7.scm :language: scheme *练习 1.7* ------------- 仿照实例中的实现以及上一题目中的改进之处 .. literalinclude:: code/ex-1.8.scm :language: scheme 1.1.8 过程作为黑箱抽象 ------------------------------- 一个问题可以分解成若干个子问题, 而这些子问题因为可以清楚标明自身的工作, 因此可以构成定义其它过程的模块。 一个过程的定义应该能隐藏具体实现的细节, 使得调用者不必自己去实现这些过程, 而是作为一个黑箱接受它。 过程的形式参数作为约束变量, 作用域为定义过程的过程体。 使用内部定义和块结构使子过程局部化, 避免与过程外的环境发生冲突。 .. code-block:: scheme (define (average x y) (/ (+ x y) 2)) (define (sqrt x) (define (good-enough? guess x) (< (abs (- (squre guess) x)) 0.0001)) (define (improve guess x) (average guess (/ x guess))) (define (sqrt-iter guess x) (if (good-enough? guess x) guess (sqrt-iter (improve guess x) x))) (sqrt-iter 1.0 x))