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 }
.timestamp { color: #bebebe }
.timestamp-kwd { color: #5f9ea0 }
.right { margin-left: auto; margin-right: 0px; text-align: right }
.left { margin-left: 0px; margin-right: auto; text-align: left }
.center { margin-left: auto; margin-right: auto; text-align: center }
.underline { text-decoration: underline; color: green }
.bold { color: gold }
.italic { color: red }
#postamble p,#preamble p { font-size: 90%; margin: .2em }
p.verse { margin-left: 3% }
pre { border: 1px solid #ccc; padding: 8pt; font-family: monospace; overflow: auto; margin: 1.2em }
pre.src { position: relative; overflow: visible; padding-top: 1.2em }
pre.src::before { display: none; position: absolute; background-color: white; top: -10px; right: 10px; padding: 3px; border: 1px solid black }
pre.src:hover::before { display: inline }
pre.src-sh::before { content: "sh" }
pre.src-bash::before { content: "sh" }
pre.src-emacs-lisp::before { content: "Emacs Lisp" }
pre.src-R::before { content: "R" }
pre.src-perl::before { content: "Perl" }
pre.src-java::before { content: "Java" }
pre.src-sql::before { content: "SQL" }
table { border-collapse: collapse }
caption.t-above { caption-side: top }
caption.t-bottom { caption-side: bottom }
td,th { vertical-align: top }
th.right { text-align: center }
th.left { text-align: center }
th.center { text-align: center }
td.right { text-align: right }
td.left { text-align: left }
td.center { text-align: center }
dt { font-weight: bold }
.footpara:nth-child(0n+2) { display: inline }
.footpara { display: block }
.footdef { margin-bottom: 1em }
.figure { padding: 1em }
.figure p { text-align: center }
.inlinetask { padding: 10px; border: 2px solid gray; margin: 10px; background: #ffffcc }
#org-div-home-and-up { text-align: right; font-size: 70%; white-space: nowrap }
textarea { }
.linenr { font-size: smaller }
.code-highlighted { background-color: #ffff00 }
.org-info-js_info-navigation { border-style: none }
#org-info-js_console-label { font-size: 10px; font-weight: bold; white-space: nowrap }
.org-info-js_search-highlight { background-color: #ffff00; color: #000000; font-weight: bold }
Chapter 1 Building Abstractions with Procedures
Table of Contents
- 1. 构造过程抽象
- 1.1. 程序设计的基本元素
- 1.1.1. 表达式(expressions)
- 1.1.2. 命名和环境
- 1.1.3. 组合式的求值
- 1.1.4. 复合过程(compound procedures)
- 1.1.5. 过程应用的代换模型
- 1.1.6. 条件表达式和谓词
- 1.1.7. 练习
- 1.1.8. 实例:采用牛顿法求平方根
- 1.1.9. 过程作为黑箱抽象
- 1.1.10. 练习
- 1.1. 程序设计的基本元素
1 构造过程抽象
- 心智活动就是:
- 组合. 将 simple ideas 变成 compound ideas
- 关系. 将idea放在一起, setting them by one another, 同时观察.
- 抽象. 隔离开实际中的相伴的其他idea, 从而抽取出 general idea.
- 我们研究 the idea of a computational process.
- 计算过程(computational process) 计算机中的 抽闲存在, 操作 data.
- 一套规则即 program 指导process的evolution. 就像法师用咒语控制精灵. 用咒语(program)来施展法术(process). procedure是咒语实体
- master有organize programs 的能力:预见,合理规划;防灾救灾;模块化.
- Lisp 是一种语言 describing processes. 类似 自然语言用来描述everyday thougths, 数学用来描述定量现象.
- Lisp本意只是一种数学记述形式, 用来处理一种特定形式的逻辑表达式(递归方程); 但也适合作为program.
- 名字来自表处理list processing. 通过新增原子和表, 获得符号计算的能力,如微积分;
- 最重要的一个特征是: process的Lisp描述(称为procedure)本身又可以做为Lisp的数据来表示和操作 许多威力强大的程序设计法术都是为了: 填平"被动的"数据 和 "主动的"过程 之间的传统划分 represent prcedures as data 的能力 使得 Lisp 非常适合于 写programs that must 操作其他 programs as data, 例如: interpreters, compilers.
1.1 程序设计的基本元素
- 程序设计语言不仅指挥计算机,更是一种框架,让人在其中组织思想
- 都有将简单认识组织成更复杂认识的方法,3种机制: 完全对应心智活动
- 基本表达式,最简单的个体
- 组合的方法,从简单东西出发构造出复合的元素
- 抽象的方法,给复合对象命名,当作单元去操作
- 程序设计中,处理两类要素:过程和数据 并不严格分离 ;简单说,数据是操作的"东西",而过程是操作数据的规则的描述. 对数据和过程都要提供上述3种机制
1.1.1 表达式(expressions)
- 基本表达式:486和+都是.
- 组合表达式(combinations):一对括号括起的一个表,表示 过程应用. 最左运算符,其余是运算对象 求值就是将运算符表示的过程 用于运算对象的值(实际参数)
- 前缀表示, 优点:
- 使用可带任意个实参的过程,而没有歧义
- 易嵌套,允许组合式元素本身又是组合式
1.1.2 命名和环境
- using names to refer to computational objects. We say that the name identifies a variable whose value is the object.
- (define var 2) define是最简单的抽象方法,允许用简单名字去引用 组合运算的结果 emacs lisp: (set ‘<name> <value>) 或者 (setq <name> <value>)
- 实际上, 构造程序,就是逐步创建出越来越复杂的计算性对象 ;逐步创建名字-对象关联, 解释器提供这种便利,并促进采用递增方式去开发调试. 这里所说的计算对象是维护在解释其中的环境?似乎可以拆分为, 构建和计算?
- 环境 将值与符号关联,解释器必须维护名字-值对偶, 确定符号的意义;这种存储就称为环境;一个计算过程中可能有多个不同环境
1.1.3 组合式的求值
- 组合式求值规则:
- 求值各子表达式;
- 将最左子式的值,应用于所有其他子式的值
- 递归,树形积累
- 这一规则,是递归的,有调用规则本身的需要 子式又是组合式
- 可用一棵树来表示求值过程,结点为组合式,发出分支对应子式;求值就是从端点向上穿行组合,进行"树形积累" 每个节点都把孩子收到一起
- 反复用步骤一,直到不再遇到组合式而是基本表达式 不是函数调用,或者是使用内部运算符.即, 找到了机器码.,取出数和名字的值
- 基本表达式:
- 数的值, 就是它所表示的 数值
- 内部运算符的值, 就是相应的 机器指令序列
- 其他名字的值 在环境中查找
- 基本表达式:
- 特殊形式
- 如define, 不是组合式 ,有自己的求值规则.
- 这些不同的表达式(expression)(及相应求值规则)组成语法形式. the evaluation rule for expressions 可表述为 普通形式 + 特殊形式.
1.1.4 复合过程(compound procedures)
- 过程定义的一般形式:(define (<name> <formal parameters>) <body>) 复合过程对应基本过程 emcas lisp: (defun name (formal parameters) <body>)
- 过程定义,强大的抽象技术,为复合操作提供名字,以作为单元使用,隐藏实现, 然后使用就与基本过程完全一样。
1.1.5 过程应用的代换模型
- 基本过程应用机制:解释器已做好 复合过程用用机制:形参用实参取代,对过程体求值.
- 过程应用的代换模型: 取出函数体, 应用参数, 求解; 归于为其他问题, 不断求解.
- 只是帮助理解意义, 不真操作过程正文,去代换; 实际是利用局部环境,产生"代换"的效果。
- 第一个且简化的模型, 当问题变复杂时, 会引入其他模型.
- 应用序和正则序
正则序求值(normal-order evaluation): fully expand and then reduce
应用需求值(applicative-order evaluation): evaluate the arguments and then apply- 代换模型可用时, 两者结果一样.
- 但, 正则序可能有重复求值 不同位置的形参都要计算一次; 代换模型不可用时, 正则序会很复杂.
1.1.6 条件表达式和谓词
- cond,if
- (cond (<p1> <e1>) (<p2> <e2>)…), 依次 看谓词pi,直到某谓词为真
- else特殊符号,用在cond最后一句,永远返回,即else后谓词永为真
emacs lisp: 没有else, 用t代替. - if是cond只有两情况的特例, 先 算predicate, 然后 选择一个分支;形式(if <predicate> <consequent> <alternative>)
- 逻辑符合运算符
- and,or,not
1.1.7 练习
;1.1 10 (+ 5 3 4) (- 9 1) (/ 6 2) (+ (* 2 4) (- 4 6)) (set ‘a 3) a (set ‘b (+ a 1)) ;如果不quote, 就会求值. set的两个参数都可以quote (setq a 3 b (+ a 1)) ;第一个参数自动quote; 多对同时赋值, 两个一对. (+ a b (* a b)) (= a b) (if (and (> b a) (< b (* a b))) b a) (cond ((= a 4) 6) ((= b 4) (+ 6 7 a)) (t 25 )) ;emacs lisp的cond里面没有else, 用t. t的value是t. (+ 2 (if (> b a) b a)) (* (cond ((> a b) a) ((> a b) b) (t -1)) (+ a 1)) ;1.2 (/ (+ 5 5 (- 2 (- 3 (+ 6 (/ 4 5)))) (* 3 (- 6 2) (- 2 7))) ;1.3 (defun bigest (a b c) (cond ((and (> a b)(> a c)) a) ((and (> b a)(> b c)) b) (t c) ) ) ;function 的定义形式和 scheme不一样. (bigest 1 2 3) (bigest 21 89 30) ;1.5 (defun p () (p)) (defun test (x y) (if (= x 0) 0 y)) (test 0 (p));应用序的话, 会不断求(p), 死循环; 正则序不会求(p), 直接0结束.
1.1.8 实例:采用牛顿法求平方根
- 说明与行动,数学函数与计算过程 有趣的观点. 贯穿在现实生活中. 说的正确性, 做的现实性. 平方根函数定义:根号x = y,使得y>=0且y^2=x 正统的数学函数,能推导出一些一般性事实;但并没有描述计算过程,给一个x,如何找到y 数学函数与计算过程的差异是说明性知识 与 行动性知识 间的普遍性差异的一个具体体现 数学关心说明性的描述(是什么),而CS关心行动性的描述(怎么做)
- 求平方根的牛顿法: 已有猜测y, 新猜测为 y 与 x/y 的平均值,该值会更接近实际的平方根
- 不用特殊迭代结构来实现迭代
1.1.9 过程作为黑箱抽象
- 过程抽象
- sqrt有多个有明确分工的过程构成. 直接反应了从原问题到子问题的分解.
- sqrt中square是一过程抽象;任何能计算出平方的过程都可用 一个过程定义需要能隐藏起一些细节,能被当作黑箱使用,不用弄清其中的具体实现。
- 局部名
- 约束变量: 形式参数的名字, 名字具体是什么不重要, 可统一更换, 约束于的一集表达式称为 该名字的 作用域
- 自由变量: 非约束变量, 不能任意替换.
- 内部定义和块结构
- 对用户由暴露必要的过程,其他都定义到该过程内部; 这种嵌套定义,称为 块结构 ,解决名字包装(减少冲突)问题
- 部分参数传递就不必要了,可以改为内部定义中的 约束变量, 这种方式称 词法作用域 , 省去很多参数传递. 约束变量在某范围内是有定义的. 自由超越了一定的范围? 约束,自由都是对于一定范围来说的吧?
1.1.10 练习
;1.7 (defun sqrt-iter (guess x) (if (good-enough? guess x) guess (sqrt-iter (improve guess x) x))) (defun improve (guess x) (average guess (/ x guess))) (defun average (x y) (/ (+ x y) 2)) (defun good-enough? (guess x) (< (abs (- (square guess) x)) 0.001)) (defun square (x) (* x x)) (defun sqrt (x) (sqrt-iter 1.0 x)) (defun good-enough? (guess x);用变动比例来衡量结束时间 (< (/ (abs (- (square guess) x)) x) 0.001));guess是开方结果, 而x是被开方数. (sqrt 9) (sqrt 100000) (square (sqrt 2984881264950492));统计一下计算次数呢? (sqrt 0.09) (square (sqrt 0.0009)) ;小值, 误差很大. 因为结束条件与0.001相关. ;1.8 (defun cube-root (x) (sqrt-iter 1.0 x)) ;sqrt-iter不只用来求sqrt, 而是一种更通用的形式 (defun improve (guess x) (/ (+ (/ x (square guess)) (* 2 guess))3)) (defun cube (x) (* x x x)) (defun good-enough? (guess x) (< (abs (- (cube guess) x)) 0.00001)) ;cube, square写到了里面. good-enough?就不得不写两份. (cube (cube-root 9)) (cube (cube-root 0.0003))
- ex1_6: if,cond都是特殊形式,与一般求值规则不同. 都是"从上到下"计算. 定义的new-if使用组合式的一般求值规则, 没有短路性,所有子表达式都会计算,而死循环。
- ex1_7:对很大和很小的y,good-enough?不用固定差值了,而用相对值,用变化和猜测值的比率. 太小直接停了. 太大结束不了, 左右震荡.
是什么和怎么做 是有区别的. 尾递归可以不需要迭代的特殊形式 而仅用函数调用 就实现迭代. 局部变量, 内部定义块结构, 作用域 都能有效的 增加代码的维护性.
原文地址:https://www.cnblogs.com/flameNoodleKing/p/9551679.html