SICP第三章学习笔记

第三章 模块化、对象和状态(Modularity, Objects, and State)
    当我们需要模拟真实物理系统的程序时,我们可以采用基于被模拟的结构去设计程序的结构,这样,在需要针对系统中的新对象或者新活动扩充对应的计算模型时,能够不必对程序做全面的修改,而只需加入与这些对象或者动作相对应的新的符号对象。也就是说,只需在系统里的一些小局部工作。组织大型程序的方式会受到我们对于被模拟系统的认识的支配。本章将讲两种组织策略:1、(基于对象的途径)将大型系统看成一大批对象,它们的行为可能随着时间的进展而不断变化。2、(对于流处理的途径)处理流过系统的信息流。


    Scheme相关语法:set!修改对象的值,begin顺序求值表示式,parallel-execute为每个参数创建一个独立的进程,该进程将应用这个参数,这些进程都并发地运行


基于对象的途径:
    在一个由许多对象组成的系统里,每个对象都可能通过交互作用,影响其他对象的状态。交互就是建立起一个对象的状态变量与其他对象的状态变量之间的联系。
    要使一个模型成为模块化的,就要求它能够分解为一批计算对象,使它们能够模拟系统里的实际对象。每一个计算对象必须有自己的一些局部状态变量,用于描述实际对象的状态。这些局部状态变量封装在计算对象里。封装反映了隐藏原理的一般性系统设计原则:通过将系统中的不同部分保护起来,使系统更模块化,更强健。

引进赋值的利益:
    从一个复杂计算过程中一部分的观点看,其他部分都像是在随着时间不断变化,它们隐藏起自己的随时间变化的内部状态。
    与所有状态都必须显式地操作和传递额外参数的方式相比,通过引进赋值将一个大系统中的某些部分封装,或者说“隐藏”到局部变量里,我们能以一种更模块化的方式构造系统。

引进赋值的代价:
   (如果一个语言支持在表达式里“同一的东西可以相互替换”,这样替换不会改变有关表达式的值,这个语言就称为是具有引用透明性。set!打破了引用透明性,使确定能否通过等价的表达式代换去简化表达式变成了一个复杂的问题。如果不修改数据对象,那么就可以将一个复合数据对象完全看作是由其片段组成的一个整体。)
   (不用任何赋值的程序设计称为函数式程序设计)
    命令式程序设计:采用赋值的程序设计。引进set!函数后,一个变量就索引着一个可以保存值的位置,而存储在那里的值是可以改变的。
    导致计算模型的复杂性(使用环境模型而不是代换模型)。
    带有赋值的程序将强迫人们去考虑赋值的相对顺序,以保证每个语句所用的是被修改变量的正确版本。
    并发执行的进程存在的问题

求值的环境模型:
    变量必须以某种方式指定一个“位置”,相应的值可以存储在那里。这种位置将维持在称为环境的结构中。
    一个环境就是框架的一个序列。每个框架是包含着一些约束的一个表格(可能为空),这些约束将一些变量名字关联于对应的值(在一个框架里,任何变量至多只能有一个约束)。每个框架还包含一个指针,指向这一框架的外围环境。
    环境对于求值特别重要,因为它确定了表达式求值的上下文。
    假设存在着一个全局环境,它只包含着一个框架(没有外围环境),这个环境里包含着所有关联于基本过程的符号的值,例如符号+在这被约束到相应的基本加法过程。
求值规则:
    对组合表达式求值:1)求值这一组合式的各个子表达式;2)将运算符子表达式的值应用于运算对象子表达式的值。
    (由于求值规则并没有规定各个子表达式的求值顺序,所以不要写依赖于特定顺序的程序。因为如果一个复杂的编译器去做程序的优化,它完全可能改变其中各个子表达式的求值顺序。)
    一个过程总是一个对偶,由一些代码和一个指向环境的指针组成。过程只能通过一种方式创建,那就是通过求值一个lambda表达式。这样产生出的过程的代码来自这一lambda表达式的正文,其环境就是求值这个lambda表达式,产生出这个过程时的那个环境。(define过程定义的语法形式是lambda的语法糖衣)
    在环境中过程对象是一个序对,其代码部分描述的是一个带有形式参数的过程和其过程体。环境部分是一个指向求值这个lambda表达式的环境的指针。整个过程的结果就是将这个新的约束加入框架里。
    在将一个过程应用于一组实际参数时,将会建立起一个新环境,其中包含了将所有形式参数约束于对应的实际参数的框架,该框架的外围环境就是所用的那个过程的环境。
    过程应用的环境模型规则:
    1)将一个过程对象应用于一集实际参数,将构造出一个新框架,其中将过程的形式参数约束到调用时的实际参数,而后在构造起的这一新环境的上下文中求值过程体。这个新框架的外围环境就是作为被应用的那个过程对象的一部分的环境。
    2)相对于一个给定环境求值一个lambda表达式,将创建一个过程对象,这个过程对象是一个序对,由lambda表达式的正文和一个指向环境的指针组成,这一指针指向的就是创建这个过程对象时的环境。

    用define定义一个符号,就是在当前环境框架里建立一个约束,并赋予这个符号指定的值。
    set!首先在环境中确定有关变量的约束位置,而后在修改这个约束,使之表示这个新值,如果该变量在环境中没有约束,set!将报告一个错误。

    将框架看作局部状态的展台。

    以局部过程定义作为程序模块化的有用技术中的两个关键性质:
    1)局部过程的名字不会与包容它们的过程之外的名字互相干扰,这是因为这些局部过程名都是在该过程运行时创建的框架里面约束的,而不是在全局环境里约束的。2)局部过程只需将包含着它们的过程的形参作为自由变量,就可以访问该过程的实际参数。这是因为对于局部过程体的求值所在的环境是外围过程求值所在的环境的下属。


    运用数据抽象的数据结构不但包含选择函数和构造函数,还应包含改变函数,这种操作能够修改有关的数据对象。
    定义了改变函数的数据对象称为变动数据对象。
        变动的表结构,队列的表示,表格的表示,数字电路的模拟器,约束的传播

并发
    赋值语句的执行描绘出有关值变化的一些时刻,对一个表达式的求值结果不但依赖于该表达式本身,还依赖于求值发生在这些时刻之前还是之后。采用具有局部状态的计算对象建立模型,就会迫使我们去直面时间问题。
    几个进程有可能共享同一个状态变量。多个进程有可能同时试图去操作这种共享的状态。
    对并发的限制方式:
    1)修改任意共享状态变量的两个操作都不允许同时发生。
    2)保证并发系统产生出的结果与各个进程按照某种方式顺序运行产生出的结果完全一样。
           并没有要求各个进程实际上顺序地进行。一个并发程序完全可以产生多于一个“正确的”结果。
    3)每个进程都反复将自己的值更新为自己的原值和相邻进程的值的平均值。无论有关操作按什么顺序执行,都能收敛到正确的解。
    控制并发的机制:
        串行化组:使我们可能限制并行进程之间的交错情况,以保证程序具有正确的行为方式。
        串行化使进程可以并发地执行,但是其中也有一些过程不能并发地执行。串行化就是创建一些不同的过程集合,并且保证在每个时刻,在任何一个串行化集合里至多只有一个过程的一个执行。如果某个集合里有过程正在执行,而另一进程企图执行这个集合里的任何过程时,它就必须等待到前一过程的执行结束。
        一个串行化组以一个过程为参数,它返回的串行化过程具有与原过程一样的行为方式。对于一个给定串行化组的所有调用返回的串行化过程都属于同一个集合。
    使用多重资源的复杂性:
        如果一个操作中存在多项共享资源,则用每个资源的串行化组将整个过程串行化。
    串行化的实现:
        用一种称为互斥元的同步机制实现。一个互斥元可以被获取或者被释放。一旦某个互斥元被获取,对于这一互斥元的任何其他获取操作都必须等到该互斥元被释放之后。
        实现:每个串行化组关联着一个互斥元。给一个过程p,串行化组将返回一个过程,该过程将获取相应互斥元,而后运行p,而后释放该互斥元。
    (define (make-serializer)
      (let ((mutex (make-mutex)))
        (lambda (p)
          (define (serialized-p . arges)
            (mutex 'acquire)
            (let ((val (apply p arges)))
              (mutex 'release)
              val))
        serialized-p)))
         互斥元可以保存真或者假。在值为假时,这个互斥元可以被获取,初始化时默认为假。
    (define (make-mutex)
      (let ((cell (list false)))
        (define (the-mutex m)
          (cond ((eq? m 'acquire)
                 (if (test-and-set! cell)
                     (the-mutex 'acquire)))
                ((eq? m 'release) (clear! cell))))
      the-mutex))

    (define (clear! cell)
      (set-car! cell false))

    (define (test-and-set! cell)
      (if (car cell)
          true
          (begin (set-car! cell true)
                 false)))

        实现并发控制的核心:test-and-set!操作必须以原子操作的方式执行。

    死锁:每个进程都要无穷无尽的等待下去,等着另一个进程的活动
        避免:首先给每个资源确定一个唯一的标识编号,并且使每个进程总是首先设法进入保护具有较低标识编号的资源的过程。

    串行化要求进程在任意时刻去检查一个全局性的共享标志,比较低效。一种代替方法是屏障同步,程序员允许并发进程随意地执行,但需要建立起一些同步点,任何进程在所有进程没有到达这里之前都不能穿过它。

    不同进程之间的同步,建立起共享状态,或迫使进程之间的通信所产生的事件按照某种特定的顺序进行。
    在并发控制中,任何时间概念都必然与通信有内在的密切联系。


流处理:惰性求值,可以提供和赋值同样的模块化同时又不必使用赋值。
    流处理使我们可以模拟一些包含状态的系统却不需要利用赋值或者变动数据。缓和状态模拟中的复杂性。
    流的构造和它的使用可以交错进行,而这种交错是完全透明的。
    基本思想:只是部分地构造出流的结构,并将这样的部分送给使用流的程序。如果使用者需要访问流尚未构造出的那部分,那么这个流就自动地构造下去,但是只做出足够满足当前需要的那一部分。

    流实现基于一种称为delay的特殊形式,对于delay <exp>的求值将不对表达式<exp>求值,而是返回一个称为延时对象的对象。force过程以一个延时对象为参数,执行相应的求值工作。
    (define (cons-stream a b)
      (cons a (delay b)))
    (define (stream-car stream) (car stream))
    (define (stream-cdr stream) (force (cdr stream)))
    可以将延时求值看作一种“由需要驱动”的程序设计,其中流处理的每个阶段都仅仅活动到足够满足下一阶段需要的程度。
    delay和force的实现:
    delay通过将表达式作为一个过程的体来包装一个表达式。(delay <exp>)相当于(lambda () <exp>)
    force就是简单地调用由delay产生的那种无参过程。
    (define (force delayed-object)
      (delayed-object))
    若需要多次地迫使同一个延时对象求值,则设法采用一种构造延时对象的方法,使它们第一次被迫求值之后能保存起求出的值。随后再次遇到被迫求值时,这些对象就可以直接返回自己保存的值,而不必重复计算。
    由于我们只计算出有关流中必须访问的那部分,所以我们可以实现无穷流。

定义流:1)通过描述“生成”过程的方式。2)利用延时求值隐式地定义流。如(define ones (cons-stream 1 ones))定义为1的一个无穷流

    带有延时求值的流可能成为一种功能强大的工具,能提供局部状态和赋值的许多效益。

流计算模式的使用
    系统地将迭代操作方式表示为流过程
    序列的无穷流
    将流作为信号,用流去模拟包含反馈循环的信号处理系统
流和延时求值
    对于带有循环的系统的流模拟,除了cons-stream所提供的“隐藏的”delay之外,可能还需要直接使用delay,即构造一个要求延时参数的过程。
    为了避免同时需要常规的过程和要求延时参数的过程,可以让所有过程都使用延时参数。可以采用一种求值模型,其中所有过成参数都自动延时,只有在需要时才强迫参数求值。这样就把语言转到了采用规范序的方式
    把延时包含到过程调用中将会对我们设计依赖于时间顺序的程序的能力造成极大损害。被动性和延时求值不能很好的结合。



时间的函数式程序设计观点:
    引进赋值和变动对象,就是为了提供一种机制,以便能模块化地构造出程序,去模拟具有状态的系统。我们构造了包含内部状态变量的计算对象,用赋值去修改这些变量。我们利用对应计算对象的时序行为去模拟现实世界中的各种对象的时序行为。
    用流去模拟一个变化的量,即用流表示某个变量的内部状态的顺序状态的时间史。这里流将时间显式地表示出来,因此松开了被模拟的世界里的时间与求值过程中事件发生的顺序之间的紧密联系。但是,当需要进行多个流的归并时仍受到“真实时间”的限制。





伪随机选择:
    采用将x更新为ax+b(mod m)的规则,其中a、b和m都是适当选出的整数。
蒙特卡罗模拟技术:
    蒙特卡罗方法包括从一个大集合里随机选择实验样本,并在对这些试验结果的统计估计的基础上做出判断。
厄拉多筛选法:
    构造出素数的无穷流
    从整数2开始,为了得到其余的素数,就需要从其余的整数中过滤掉2的所有倍数。这样就留下一个从3开始的流,而3也就是下一个素数。现在再从这个流的后面部分过滤掉所有3的倍数,这样就留下一个以5开头的流,而5又是下一个素数。我们可以这样继续下去构造出素数的无穷流。
Henderson图
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
"SICP中文版"是指计算机科学经典教材《Structure and Interpretation of Computer Programs》(计算机程序的构造和解释)的中文翻译PDF版。这本教材由麻省理工学院的Harold Abelson和Gerald Jay Sussman等人编写,是计算机科学领域中一本重要的教材。 "SICP中文版PDF"提供了更方便的学习方式。无论是学生、程序员还是计算机科学爱好者,都可以在任何时候通过电子设备访问和学习这本教材。使用PDF格式的好处是可以在不同的平台上都能打开和阅读,而不受限于特定的操作系统或设备。 通过"SICP中文版PDF",读者可以学习计算机科学的基本原理和概念,如过程、数据抽象、递归、高阶函数、并发等。这本教材以Scheme语言为示例,帮助读者理解计算机程序的结构、设计和解释。通过逐步的案例和练习,读者可以锻炼解决问题和编写高质量代码的能力。 "SICP中文版PDF"也提供了沟通和讨论的平台。读者可以通过在线社群或论坛,与其他人分享学习心得、解答疑问和参与讨论。这为读者提供了一个学习交流的机会,促进了学习者之间的互动和共同成长。 总之,"SICP中文版PDF"是一本经典的计算机科学教材的中文翻译版本,使得更多的读者可以方便地学习和掌握其中的知识。无论是对于计算机科学专业的学生还是对计算机科学感兴趣的人,这本教材都是一本很好的参考书,并提供了丰富的实例和练习,让读者深入理解计算机程序的核心概念和设计原则。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值