Skip to content

Commit

Permalink
update notes
Browse files Browse the repository at this point in the history
  • Loading branch information
ecmadao committed Jan 2, 2020
1 parent a32587b commit d04be92
Show file tree
Hide file tree
Showing 18 changed files with 617 additions and 468 deletions.
147 changes: 126 additions & 21 deletions Notes/Book/《计算机程序的构造和解释》.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@

心智的活动,除了尽力产生各种简单的认识之外,主要表现在如下三个方面:

1. 将若干简单认识组合为一个复合认识,由此产生出各种复杂的认识
2. 将两个认识放在一起对照,不管它们如何简单或者复杂,在这样做时并不将它们合而为一。由此得到有关它们的相互关系的认识
3. 将有关认识与那些在实际中和它们同在的所有其他认识隔离开,这就是抽象,所有具有普遍性的认识都是这样得到的
1. 将若干简单认识组合为一个复合认识,由此产生出各种复杂的认识 -- **组合**
2. 将两个认识放在一起对照,不管它们如何简单或者复杂,在这样做时并不将它们合而为一。由此得到有关它们的相互关系的认识 -- **对比**
3. 将有关认识与那些在实际中和它们同在的所有其他认识隔离开,这就是抽象,所有具有普遍性的认识都是这样得到的 -- **抽象**

一个强有力的程序设计语言,不仅是一种指挥计算机执行任务的方式,它还应该成为一种框架,使我们能够在其中组织自己有关计算过程的思想。每一种强有力的语言都为此提供了三种机制:

Expand All @@ -34,11 +34,62 @@

在程序设计中,我们需要处理两类要素:**过程和数据**。任何强有力的程序设计语言都必须能够表述基本的数据和基本的过程,还需要提供对过程和数据进行组合和抽象的方法。

解释器对语句求值的过程,分为*应用序**正则序*两种。对于正则序求值,解释器会将语句完全展开然后规约。而对于应用序,则先求值参数而后应用。
解释器对语句求值的过程,分为*应用序**正则序*两种

人们对功能强大的程序设计语言有一个必然要求,就是能为公共的模式命名,建立抽象,而后直接在抽象的层次上工作。而过程提供了这种能力,因此,除了最简单的程序语言以外,其他语言都包含定义过程的机制。
- 对于正则序求值,解释器会将语句完全展开然后规约。例如 Haskell
- 对于应用序求值,则先求值参数而后应用。例如 Lisp

但是即便在数值计算的过程中,如果将过程限制为只能以数作为参数,也会严重限制我们建立抽象的能力。经常有一些同样的程序设计模式能用于若干不同的过程。为了把这种模式描述为相应的概念,则需要构建出这样的过程:以过程为参数,或者以过程为返回值。*这类能操作过程的过程称为高阶过程*
```scheme
; 解释过程举例
(define (square x) (* x x))
(define (sum-of-square x y) (+ (square x) (square y)))
; 求值
(sum-of-square (+ 1 2) (+ 1 3))
; 对于正则序解释:
(sum-of-square (+ 1 2) (+ 1 3))
(+ (square (+ 1 2)) (square (+ 1 3)))
(+ (* (+ 1 2) (+ 1 2)) (* (+ 1 3) (+ 1 3)))
(+ (* 3 3) (* 4 4))
(+ 9 16)
(25)
; 对于应用序解释:
(sum-of-square (+ 1 2) (+ 1 3))
(sum-of-square 3 4)
(+ (square 3) (square 4))
(+ (* 3 3) (* 4 4))
(+ 9 16)
(25)
```

人们对功能强大的程序设计语言有一个必然要求,就是**能为公共的模式命名,建立抽象,而后直接在抽象的层次上工作**。而过程提供了这种能力,因此,除了最简单的程序语言以外,其他语言都包含定义过程的机制。但是即便在数值计算的过程中,如果将过程限制为只能以**作为参数,也会严重限制我们建立抽象的能力。经常有一些同样的程序设计模式能用于若干不同的过程。为了把这种模式描述为相应的概念,则需要构建出这样的过程:**以过程为参数,或者以过程为返回值。这类能操作过程的过程称为高阶过程**

```scheme
; 考虑下面 2 个过程
; 1. 计算从 a 到 b 的各整数之和
(define (sum-integers a b)
(if (> a b)
0
(+ a (sum-integers (+ a 1) b))))
; 2. 计算给定范围内的整数的立方之和
(define (sum-cubes a b)
(if (> a b)
0
(+ (cube a) (sum-cubes (+ a 1) b))))
#|
可以看出这个两个过程共享了一样的基础模式:
(define (<name> a b)
(if (> a b)
0
(+ (<term> a)
(<name> (<next> a) b))))
|#
```

一般而言,程序设计语言总会对计算元素的可能使用方式强加上某些限制。带有最少限制的元素被称为具有第一级的状态。第一级元素的某些特权包括:

Expand All @@ -49,7 +100,7 @@

## 构造数据抽象

将数据对象组合起来,形成复合数据 --> 为了提升我们在设计程序时所位于的概念层次,提高设计的模块性,增强语言的表达能力。形成复合数据的关键则在于,程序设计语言里应该提高了某种“黏合剂”,它们可以用于把一些数据对象组合起来,形成更复杂的数据对象。
将数据对象组合起来,形成复合数据 --> 为了提升我们在设计程序时所位于的概念层次,提高设计的模块性,增强语言的表达能力。形成复合数据的关键则在于,程序设计语言里应该提供某种“黏合剂”,它们可以用于把一些数据对象组合起来,形成更复杂的数据对象。正如定义过程的能力使我们有可能在更高的概念层次上处理计算工作一样,能够构造复合数据的能力,也将使得我们可以在比语言提供的基本数据对象更高的概念层次上,处理与数据有关的各种能力

- 过程抽象:将过程的使用方式,与该过程究竟如何通过更基本的过程实现的具体细节相互分离
- 数据抽象:将一个复合数据对象的使用,与该数据对象怎样由更基本的数据对象构造起来的细节隔离开
Expand All @@ -58,23 +109,70 @@

层次性数据和闭包性质:

某种组合数据对象的操作满足闭包性质,即代表,通过它组合起数据对象得到的结果本身,还可以通过同样的操作再进行组合。闭包性质使我们能够建立起层次性的结构,这种结构由一些部分构成,而其中的各个部分又是由它们的部分构成,并可以如此继续下去。
*某种组合数据对象的操作满足闭包性质,即代表,通过它组合起数据对象得到的结果本身,还可以通过同样的操作再进行组合。*闭包性质使我们能够建立起层次性的结构,这种结构由一些部分构成,而其中的各个部分又是由它们的部分构成,并可以如此继续下去。

复合结构`cons`可以组合两种元素,利用这种性质,可以把某一个元素作为节点的值,另一个元素指向下一个节点,以此构建出链表(序列)或者树。例如:

```scheme
(cons 1 (cons 2 (cons 3 nil)))
; 借助 cons,可以创建「有理数」这个复合数据对象 - 由整数分子和整数分母组成
(define (make-rat n d) (cons n d))
; 获取有理数的分子
(define (number x) (car x))
; 获取有理数的分母
(define (denom x) (cdr x))
; 例如 1/2
(define x (make-rat 1 2))
(number x) ; 1
(denom x); 2
```

甚至只用基本过程实现序对,即将序对这个数据结构用过程表示:

```scheme
; 返回一个过程
(define (cons x y)
(define (dispatch m)
(cond ((= m 0) x)
((= m 1) y)
(else (error "Argument not 0 or 1 -- CONS" m))))
dispatch)
(define (car z) (z 0))
(define (cdr z) (z 1))
```

scheme 本身也提供了基本操作`list`

```scheme
(list 1 2 3)
(define arr (list 1 2 3))
(car arr) ; 1
(cdr arr); (2 3)
```

针对于序列这样的数据结构,我们可以抽象出各种高阶操作,例如对表的映射`map`,接收一个过程参数和一个表参数,将过程依次作用在表的各个元素上,并返回结果。

```scheme
; 表的映射 - 将某种变换应用于一个表的所有元素,得到由所有结果构成的表
; 抽取这一过程的公共模式:即
; 1. 如果表为空,则返回 nil
; 2. 否则利用 car 获取表中第一个元素,代入映射过程获取结果,并将 map 作用在表中剩下的元素上,最后合并为新表
(define (map proc items)
(if (null? items)
nil
(cons (proc (car items))
(map proc (cdr items)))))
(map (lambda (x) (* x x))
(list 1 2 3 4))
```

针对于序列这样的数据结构,我们可以抽象出各种高阶操作,例如对表的映射`map`,接收一个过程参数和一个表参数,将过程依次作用在表的各个元素上,并返回结果。这类操作建立起了一种处理表的高层抽象,抑制了细节层面的情况(例如,如何对表进行遍历,每个元素的处理结果如何返回),而强调的是一个表到另一个表的缩放变换。它将表变换的过程的实现,与如何提取表中元素以及组合结果的细节隔离开来。
这类操作建立起了一种处理表的高层抽象,抑制了细节层面的情况(例如,如何对表进行遍历,每个元素的处理结果如何返回),而强调的是一个表到另一个表的缩放变换。它将表变换的过程的实现,与如何提取表中元素以及组合结果的细节隔离开来。

将程序表示为一些针对序列的操作,帮助开发者得到模块化的程序设计 --- 得到由一些比较独立的片段的组合构成的设计 --- 以此控制复杂性。
将程序表示为一些针对序列的操作,帮助开发者得到模块化的程序设计得到由一些比较独立的片段的组合构成的设计以此控制复杂性。

一个复杂的系统应该通过一系列的层次构造出来。为了描述这些层次,需要使用一系列的语言。构造各个层次的方式,就是设法组合起作为这一层次中的部件的各种基本元素。而这样构造出的部件,又可以作为另一个层次里的基本元素。在分层设计中,每个层次上所用的语言都提供了一些基本元素、组合手段,还有对该层次中的适当细节做抽象的手段。

Expand Down Expand Up @@ -109,15 +207,15 @@ scheme 本身也提供了基本操作`list`:
(+ (square x) (square y)))
(sum-of-squares 2 3) ; 13
; 条件表达式和谓词
; 类似其他语言的 switch 或者 case
; 一般形式为
#|
条件表达式和谓词
类似其他语言的 switch 或者 case
一般形式为
(cond (<p1> <e1>)
...
(<pn> <en>))
|#
#|
首先包含了一个符号 cond,在它之后跟着一些称为子句的用括号括起来的表达式对偶 (<p> <e>)
在每个对偶中的第一个表达式是一个谓词(即 p),也就是说,这是一个表达式,它的值将被解释为真或者假
当某一个谓词为真时,返回后面的值,不再继续检查剩下的谓词
Expand All @@ -140,24 +238,31 @@ scheme 本身也提供了基本操作`list`:
(- x)
x))
; and/or/not 表达式
; (and <e1> ... <en>) 全部表达式为真时返回真,否则返回假
; (or <e1> ... <en>) 全部表达式为假时返回假,否则返回真
; (not <e>)
#|
and/or/not 表达式
(and <e1> ... <en>) 全部表达式为真时返回真,否则返回假
(or <e1> ... <en>) 全部表达式为假时返回假,否则返回真
(not <e>)
|#
(and (> x 5) (< x 10)) ; 判断 5 < x < 10
; lambda 表达式
; (lambda (<parameters>) <body>)
(lambda (x) (+ 4 x))
; let 创建局部变量
#|
let 创建局部变量
(let ((<var1> <exp1>)
(<var2> <exp2>)
...
(<varn> <expn>))
<body>)
创建的变量只在 body 块内有效
|#
; 创建的变量只在 body 块内有效
; 38
(+ (let ((x 3))
(+ x (* x 10)))
Expand Down
Loading

0 comments on commit d04be92

Please sign in to comment.