SICP学习笔记及题解---构造过程抽象(一)

有段时间没看这本书了.

而且在做笔记的时候产生了一些疑问,觉得这样照着书做笔记没什么意义.于是乎,改变了一下做法.改成先提出疑问,记下重点,然后结合实际案例学习相关东西,最后附上题解,

ok,下面就是第一次的笔记.(依旧是旧套路的)

本节内容

  • l  讨论基本的Scheme语法规则
  • l  过程的定义
  • l  代换模型
  • l  条件表达式和谓词
  • l  过程抽象
  • l  与C语言比较

程序设计的基本元素

所有的高级的语言都会在把简单的认知组合起来形成复杂认识的方法上有独到之处.而且每个强有力的语言都为此提供了三种机制:

  • l  基本表达形式:用于表示语言所关心的最简单的个体
  • l  组合的方法:从简单的东西出发构造出复杂的元素
  • l  抽象的方法:为复合对象命名,并将它们当做单元操作

而在程序设计中我们需要处理的两类要素:数据和过程.

Scheme基本语法规则

是一个交互式语言,类似于Python的解释器运行方式,其解释器运行时反复执行一个”读入---求值---打印”的循环过程,即是递归调用在Scheme中非常常见.每次循环:

  • ? 读入一个完整地表达式
  • ? 对表示求值
  • ? 返回求出的值

与C语言相比,C语言是一个编译型语言,他要求程序要有完整的结构,表达式和语句不是程序,不能够被编译运行,而且编写好的程序需经过编译后运行;从结构上看,,C语言也有描述基本运算的表达式,有描述基本动作的语句,语句上有各种组合(控制流),而它的函数式语言的抽象机制.

C语言还严格的区分了数据和操作数据的过程(代码),而在Scheme里面,数据和过程可以自然的转化:数据可以作为被执行的代码,代码可以被当做被处理的数据.

演示Scheme的基本语法:

1.表达式

2.复合表达式的求值:

要求求值的通常是组合式,解释器的工作方式是:

  1. 求值该组合式的各个子表达式
  2. 将最左子表达式的值(运算符的值,应该是一个过程)作用于相应的实际参数(由其他子表达式求出的值)

例:(* (+ 2 (* 4 6)) (+ 3 5 7))

组合式求值要求先求值子表达式。因此求值过程是递归的求值过程可以用树表示,先取得终端(叶)结点的值后向上累积,最终在树根得到整个表达式的值树具有递归结构,递归处理很自然

3.变量

4.复合过程

过程定义的基本形式是:

求平方过程的定义:

(define (<name>
 <formalparameters> )  (body) )

过程名     形式参数        做什么(如何求值)

代换模型

预定义基本过程(操作)和特殊形式是构造程序的基本构件,即是可以根据需要,通过定义过程扩大了这一构件集,我们通过定义一个过程将该过程的计算细节抽象起来,使用时只需要像使用运算符一样使用该过程即可,上例而言,从使用上完全看不出 square 是基本操作还是用户定义过程.

复合过程的使用方式和威力与基本操作一样,是很好的语言特征,而过程定义是分解和控制程序复杂性的最重要技术之一

求值这种定义表达式,将相应计算过程关联于名字,组合式和复合过程确定的计算过程是(代换模型)

a)       求出各参数表达式(子表达式)的值

b)       找到要调用的过程的定义(根据第一个子表达式的求值结果)

c)       用求出的实际参数代换过程体里的形式参数

d)       求值过程体

代换模型给出了过程定义和过程应用的一种语义,很多 Scheme 过程的行为可以用这个模型描述,后面会看到,更复杂的过程需要用扩充的语义模型.

注意:

  • ?  代换模型只是为了帮助直观理解过程应用的行为
  • ?  它并没有反映解释器的实际工作过程
  • ?  实际解释器的情况后面讨论,基于环境实现
  • ?  代换模型最简单,容易理解,但不足以解释所有的实际程序
  • ?  其局限性是不能解释带有可变数据的程序
  • ?  后面将介绍更精细的模型

关于应用序求值和正则序求值

对于复合过程运算,解释器先求值子表达式(运算符和各运算对象),而后把得到的运算应用于运算对象(实际参数).这一做法合理,但合理的做法不唯一.另一方式是先不求值运算对象,推迟到需要时再求值。

前一方式(先求值参数后应用运算符)称为应用序求值,后一方式(完全展开之后归约)称为正则序求值。Scheme 采用应用序求值.可以避免一些重复计, 其他的作用后面讨论.

Scheme的条件表达式和谓词

Scheme 有条件表达式。绝对值函数可定义为:

条件表达式的一般形式:

绝对值函数还可定义为:

else 表示永远成立的条件,只应放在最后.

简化的条件表达式形式:

(  if <predicate>   <consequent>   <alternative>  )

cond 和 if都是特殊形式,有特殊的求值规则

逻辑组合运算符 and和 or也是特殊形式,采用特殊求值方式

(and<e1> ... <en>)

逐个求值 e,直到某个 e 求出假,或最后一个 e 求值完成。以最后求值的那个子表达式的值作为值

(or<e1> ... <en>)

逐个求值 e,直到某个 e 求出真,或最后的 e 求值完成。以最后求值的那个子表达式的值作为值

(not<e>) 如果 e的值不是真,就得真,否则得假

谓词是指返回真或假的过程,也指那些能够求出真或假的表达式.各种关系运算符是基本谓词,可以用 and、or、not 组合出各种复杂逻辑条件,可以用过程定义谓词.

题解

练习1.1

直接把表达式敲进编译器就会得出结果.

练习1.2

将中缀表达式改为前缀表达式:

(/ (+ 5 4 (- 2 (- 3(+ 6 (/ 4 5)))))

(* 3 (- 6 2) (- 2 7)))

练习1.3

这道题我采用的解决方法是:找出三个数中最小的,然后用三个数的和减去之即可.

首先是找出三个数中最大的代码:

(define (biggest x y z)
    (define (bigger m n)
      (if (< n m)
          m
          n))
    ( bigger x (bigger y z)))

然后是题目answer:

(define (sum-of-bigger x y z)
    (define (smallest x y z)
    (define (smaller m n)
      (if (< n m)
          n
          m))
      (smaller x (smaller y z)))
    (- (+ x y z) (smallest x y z)))

结果如下:

练习1.4

这个无解.

练习1.5

解答这道题的关键在于了解到应用序和正则序之间的区别(书本的 1.1.5 小节有详细的说明)。

首先,可以确定的是,无论解释器使用的是什么求值方式,调用 (p) 总是进入一个无限循环(infinite loop),因为函数 p 会不断调用自身:

(define (p) (p))

具体到解释器中,执行 (p) 调用会让解释器陷入停滞,最后只能强制将解释器进程杀掉:

1 ]=> (p)
^Z
[1]+  已停止               mit-scheme
$ killall mit-scheme

在应用序中,所有被传入的实际参数都会立即被求值,因此,在使用应用序的解释器里执行 (test 0 (p)) 时,实际参数 0
(p) 都会被求值,而对 (p) 的求值将使解释器进入无限循环,因此,如果一个解释器在运行 Ben 的测试时陷入停滞,那么这个解释器使用的是应用序求值模式。

另一方面,在正则序中,传入的实际参数只有在有需要时才会被求值,因此,在使用正则序的解释器里运行 (test 0 (p)) 时, 0
(p) 都不会立即被求值,当解释进行到 if 语句时,形式参数 x 的实际参数(也即是 0)会被求值(求值结果也是为
0 ),然后和另一个 0 进行对比((= x 0)),因为对比的值为真(#t),所以
if
返回 0 作为值表达式的值,而这个值又作为 test 函数的值被返回。

因为在正则序求值中,调用 (p) 从始到终都没有被执行,所以也就不会产生无限循环,因此,如果一个解释器在运行 Ben 的测试时顺利返回
0
,那么这个解释器使用的是正则序求值模式。

Note

另一个需要说明的地方是『形式参数』和『实际参数』两个名词。

对于一个函数来说,它接受的参数的局部名被称为形式参数。

而调用函数时传入的表达式,被称为实际参数。

比如说,对于函数 (define (square x) (* x x)) 来说, x 就是形式参数,当进行调用
(square 2)
时, 2 就是形式参数 x 的实际参数。

当人们只说『参数』而不说明它是『形式参数』还是『实际参数』时,他们一般指的是『形式参数』,但是具体还是要看上下文来决定。

时间: 2024-10-12 11:45:26

SICP学习笔记及题解---构造过程抽象(一)的相关文章

SICP学习笔记及题解—构造过程抽象(三)

主要内容 高阶过程:以过程为参数和/或返回值的过程 lambda 表达式 let 表达式 用过程作为解决问题的通用方法 求函数的 0 点 求函数的不动点 返回过程值 过程是语言里的一等公民 (first-class object) 1.3.1高阶过程 过程是抽象,一个过程描述了一种对数据的复合操作,如求立方过程:(define (cube x) (* x x x)) 换个方式,也可以总直接写组合式:(* 3 3 3), (* x x x), 不定义过程,总基于系统操作描述,不能提高描述的层次,

SICP学习笔记及题解---构造过程抽象(二)

主要内容: 表达式,值,define 过程的内部定义和块结构(上述示例已经解释) 分析过程(静态,描述)产生的计算进程(动态,行为) 计算进程的类型 线性递归 线性迭代 树形递归 计算的代价 第一部分: 表达式,值,define 1.总结表达式的一些概念 变量 如果一个变量没定义,对它求值是错误,求值中断,如果变量有定义,求值得到它当时的关联值 内部过程 对内部过程名求值得到某种特殊信息.如(不同系统可能不同) 组合过程: 对自己定义的过程名求值也得到特殊信息. 特殊形式的名字不能求值 例如,对

SICP 第一章 构造过程抽象 1

Chapter 1 Building Abstractions with Procedures .title { text-align: center } .todo { font-family: monospace; color: red } .done { color: green } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal

swift 笔记 (十四) —— 构造过程

构造过程 为了生成类.结构体.枚举等的实例,而做的准备过程,叫做构造过程. 为了这个过程,我们通常会定义一个方法来完成,这个方法叫做构造器.当然它的逆过程,叫做析构器,用于在实例被释放前做一些清理工作以及一此自定义化的处理. 为存储型属性设置初始值 类和结构体在生成实例那一刻,必须为所有的属性赋以特定的初始值. 要么在定义存储型属性的时候直接给个初始值,否则就必须在构造器里面指定一个初始值. 上面说的这两种情况,都不会触发存储型属性的监听者行为(property observer). struc

oracle学习笔记 SQL语句执行过程剖析讲课

oracle学习笔记 SQL语句执行过程剖析讲课 这节课通过讲述一条SQL语句进入数据库 和其在数据库中的整个的执行过程 把数据库里面的体系结构串一下. 让大家再进一步了解oracle数据库里面的各个进程.存储结构以及内存结构的关联关系. 首先来讲整个体系中有客户端.实例和数据库 数据库里有三类文件 控制文件ctl.数据文件dbf.日志文件log 实例中SGA有六大池子 第一大内存区shared pool即共享池 第二大内存区buffer cache 第三块是redo log 我们主要讲上面的三

计算机程序的构造和解释笔录(1):构造过程抽象

Q: SICP是讲软件工程么? A:部分,但并非全部.主要是模块化和黑盒抽象,计算机中两大主要基本思想.SICP关心的是:"当系统复杂度爆炸时(或者在此之前),我们如何通过有效的方法和手段去控制系统的复杂度?" Q: SICP是讲编译原理么? A: 部分,另外,如同书名说描述的那样,SICP中的"编译"都是"解释",这种解释的行为,无外乎就是用一种机器的计算行为去模拟另一种机器. 一个计算机语言并不仅仅是让计算机去执行操作的一种方式,而是一种表达

openstack学习笔记一 虚拟机启动过程代码跟踪

本文主要通过对虚拟机创建过程的代码跟踪.观察虚拟机启动任务状态的变化,来透彻理解openstack各组件之间的作用过程. 当从horizon界面发送一个创建虚拟机请求,horizon api 将会依据前端给定的数据信息.调用novaclient 生成一个创建虚拟机的http post 请求来创建vm服务. >/usr/lib/python2.6/site-packages/horizon/api/nova.py(334)server_create() > /usr/lib/python2.6/

学习笔记:Maven构造版本号的方法解决浏览器缓存问题

需要解决的问题 在做WEB系统开发时,为了提高性能会利用浏览器的缓存功能,其实即使不显式的申明缓存,现代的浏览器都会对静态文件(js.css.图片之类)缓存.但也正因为这个问题导致一个问题,就是资源的缓存逻辑有时出现问题后服务器的最新版本文件无法更新客户端的缓存. 这个问题会给用户产生许多的困扰,当然首先是测试人员会很头痛,一些看起来没有修复的bug为什么开发要说做好了?这种时候我会无奈的说:ctrl+f5刷新一下.但这毕竟不是解决问题的方法. 思路与方法考虑 思路 之前没有着手处理过这样的问题

hadoop学习笔记--hadoop读写文件过程

读取文件: 下图是HDFS读取文件的流程: 这里是详细解释: 1.当客户端开始读取一个文件时,首先客户端从NameNode取得这个文件的前几个block的DataNode信息.(步骤1,2) 2.开始调用read(),read()方法里,首先去读取第一次从NameNode取得的几个Block,当读取完成后,再去NameNode拿下一批Block的DataNode信息.(步骤3,4,5) 3.  调用Close方法完成读取.(步骤6) 当读取一个Block时如果出错了怎么办呢.客户端会去另一个最佳