第三章关于程序的模块化、对象和状态
局部状态
前面介绍了过程抽象和数据抽象,通过这两种抽象已经可以完成较强的计算功能。并且程序的组织结构也显得非常的优雅、易扩展;抽象背后的实现也可以随时修改,只要保证抽象接口不变。有一种强有力的设计原则就是依据模拟的真实世界的对象,去设计程序的结构。也就是说,程序的计算对象与待模拟的真实物理对象有对应关系。
前面说的过程定义,有输入参数和返回值,它们的重要特点是只要参数相同,返回值总是固定的。例如加法操作add,有表达式(add 1 2),它总是返回3,无论什么时候,在什么情况下上面的表达式的值都是固定的。但是有时候,甚至是绝大多数的时候,真实物理世界的对象是“有状态”的。对象的操作过程会改变这个状态,并且会影响这个操作的下一次调用的返回情况。例如一个人作为对象,他有吃饭(eat)的动作。在吃了一碗饭之后,他就感觉有点饱了,虽然他可能还以再吃,但是并不能一直吃,因为“饱的状态”是会积累的。这就是说这个函数(eat)有side effect。这个副作用是通过对象的中间状态保持的。
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (make-monitor sqrt)
(let ((count 0))
(lambda(fun)
(cond ((eq? fun 'how-many-call?) count)
((eq? fun 'reset)
(set! count 0) )
((number? fun)
(begin (set! count (+ count 1))
(sqrt fun)))
(else "no requre demanded!")))))
> (define s (make-monitor sqrt))
> (s 1)
1
> (s 100)
10
> (s 'how-many-call?)
2
> (s 'reset)
> (s 'how-many-call?)
0
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
> (define (make-acc acc)
(lambda(x)
(begin (set! acc (+ acc x))
acc)))
> (define A (make-acc 5))
> (A 1)
6
> (A 1)
7
> (A 1)
8
上面举了两个例子,说明含有状态的过程,以及如何改变这个状态。第一个例子利用count,保存sqrt被调用的次数,而且还可以显示或者置零该计数器。如果所带的参数不是'reset和how-many-call?,而是数字,那么就求这个数的平方根,并把它作为整个过程的返回值。第二例子比较简单,acc是累加器,将参数累加到acc中,并作为整个过程的值。
在命令式语言中,如C、java,局部变量的赋值顺序将改变过程的执行结果,使得学习者不断考虑是变量的赋值顺序,以保障每个语句使用的是变量的正确版本。如果运行并发执行的语言,这个复杂性将大大增加。
同一与变化
(define peter-acc (make-account 100))
(define palu-acc peter acc)
这里peter-acc和paul-acc虽然名称不同,但是它们都指向同一个对象,修改它们中的任何一个,另一个都可以看见。