Positioning SICP 2: Building Abstractions with Data

Positioning SICP 2: Building Abstractions with Data #

作者:何岩,recreating.org出品,谢绝转载
阅读SICP,站在Lisp发明者的上帝视角来思考:“我为什么要这么设计Lisp?”

0.前言:Why I design the concept of Data? 为什么要设计Data这个概念?Procedure不够用吗? #

因为,control complexity。
因为,人脑习惯Object View,需要让人们产生Data这个Object想象体。这样可以降低思考复杂度。
因为,Object/Data的抽象思想是Black-Box Abstraction,也可以称为modularity。modularity可以降低复杂度,因为,modularity减少了关系发生的数量。
因为,统一视角,抽象思维,type的本质。例如:Java中的接口
Procedure is Stream View
Data is Object View

Chapter 2.1 Introduction to Data Abstraction #

1.How I design the implementation of Data?我如何设计Data的实现? #

— Chapter 2.1.3 What Is Meant by Data?
我将用Procedures来虚拟出Data。
Data对外提供可以被感知到的是一层interface,interface的本质就是procedure。
用户就会想象,Data貌似真的存在。
其实,那些作为interface的procedure也是由更底层的procedure组成的。
例如,用procedure来模拟Pairs的存在
为了欺骗使用Pair的人,我会提供Cons/Car/Cdr这三个Procedures作为interface。
使用Pair的人会这么操纵Pair暴露的interface:cons/car/cdr:
=>(define x (cons 1 2) ;构建一个叫做x的Pair,由1和2组成
=>(car x) ;获得Pair中存储的第一个元素
1
=>(cdr x) ;获得Pair中存储的第二个元素
2

我们来看看这三个Procedures的内部究竟有没有Data的存在?

方案1:用Procedure模拟Data
(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))

方案2:用Procedure模拟Data,上一个方案里还用到了数字,我们这次用纯Procedure:
(define (cons x y)
(lambda (m) (m x y)))

(define (car z)
(z (lambda (p q) p)))

(define (cdr z)
(z (lambda (p q) q)))

因为,Lisp的Procedure的设计是基于Lambda演算的思想,即,Procedure就是Lambda。
所以,Data的存在,本质上是基于Lambda,更微观的看是基于Lambda的可组装能力,在Lambda演算中,这种组装变形的能力,称为:Beta规约(β-reduction)
再抽象来思考,Lambda的可组装能力来自于什么呢?
答案是,来自于,阿隆索·丘奇定义了一个规则/逻辑,也就是人脑的想象。
这个组合的逻辑来自于哪里?
答案是,来自于,人类对宇宙规律的观察,模拟出宇宙的底层规律,就是最小的元素的可组装性。
而这个逻辑是真理吗?不知道,无法证明,所以说,第一公理只能靠相信。
所以说,计算机科学的整座大厦都悬在Lambda演算所定义出的这个逻辑想象之上。

Data并不“真的”存在,我们能感受到的Data是它的interface(色),而它的本体却是“空”,所以说,色即是空。
而Data虽然是“空”但并不是什么也没有,只不过是没有我们认为的那种物质感存在感,Data的构建还是要基于其他的procedures(色),所以说:空即是色。

这里可以隐喻出,真实世界的原子并不是物质性的存在,原子也是由非物质的夸克组成,而夸克只是一种震动,可以将夸克理解成信息。而这个信息来自于哪里?来自于一种想象,那就是上帝意识的想象。这种上帝意识同样在我们每个人之中,所以这个世界本质上就是一个想象共同体。

Data隐喻着原子,Procedure隐喻着夸克。
Data隐喻着物质,Procedure隐喻着信息。

2.我为什么要如此设计构建Data的Framework,即selectors and constructors #

— Chapter 2.1.1 Example: Arithmetic Operations for Rational Numbers
因为,抽象上来看,这是一分一合。一收一放。犹如阴阳,漩涡模型的核心,旋转着涌现出丰盛的世界。
selectors:car/cdr、分、放
constructors:cons、合、收
隐喻着,Lisp的漩涡核心,Lisp的元解释器,Meta-circular Evaluator,即,Eval/Apply
Eval就是Selectors,分、放
Apply就是Constructors,合、收
所以说,抽象来看一切都是相通的。

How - 根据这个构建Data的Framework,我将如何构建有理数Rational Numbers
思路如下:
1.设计constructors,即,(make-rat )
2.设计selectors,即,(numer ) 和 (denom )

具体实现:
利用pairs的interface来构建Rational Numbers的interface。
所以,真正的编程都是在编写interface而已,即,面向interface编程。
(define (make-rat n d) (cons n d))

(define (numer x) (car x))

(define (denom x) (cdr x))
不要以为,make-rat直接代理了cons,没有添加新的逻辑,就没有意义。有人会问为什么不直接用cons来作为有理数的interface?
因为,这是一个定位问题,需要在虚空中给有理数一个位置,哪怕有理数的构建procedure中什么也没做,只要有位置,就有概念,就会有想象体的产生。
另外,有了位置,有理数就有空间来加入自己的逻辑。例如实现分子和分母自动归约:
(define (make-rat n d)
(let ((g (gcd n d)))
(cons (/ n g) (/ d g))))

3.Why I design Abstraction Barriers Framework? 我为什么要提出分层思想框架? #

这里,是分层思想的源头。SICP不愧为编程世界的“元典”。
因为,分层是一种抽象的哲学思想,它的设计思路适用于如下领域:Lisp解释器的设计,领域语言的设计(DSL),嵌入式新语言的设计,接口的设计,新类型数据的设计。面向服务的设计。

— Programs that use rational numbers—
Rational numbers in problem domain
— add-rat sub-rat mul-rat sub-rab —
Rational numbers as numerators and denominators
— make-rat number denom —
Rational numbers as pairs
— cons car cdr —
However pairs are implemented

本质上,每一层都是使用下一册提供的interface来构建自己的逻辑,再对上一层提供自己的interface。

另一个洞见是,一种新语言就是一种Data,因为,设计语言的思路和设计新Data的思路样,抽象上来看,他们就是一回事。
Data就是尊从了Black-Box Abstraction的思想。
分层思想实现了Black-Box Abstraction思想,但是不止于此。
分层思想还提供了一种Power,即,分层协作所产生的强大Power
为了解决一类特别领域的业务,设计一个针对性的语言,这就是分层思想。换句话说是针对这个领域设计了一套新类型的DATA,Data对外提供的interface就是能力。Data并不存在,Data只是定位,定位是想象的产物。

所以,之前刻意的遵守web三层架构是有问题的。表现层,业务层,数据层。
每一层都可以再分层,而不是将业务逻辑都写在一个BLOCK中。很多人为了将业务挪出来,就又都放到了数据层,但是数据层不应该放置业务逻辑,这就是概念错位。这也是我为什么不喜欢传统Web开发的原因,太过于教条。没有彰显自由意志的氛围。程序员们都是想着如何学会权威留下的规范。以学会的规范数量为资本。

Chapter 2.2 Hierarchical Data and the Closure Property #

1.Why I import the concept—Closure Property?我为什么要提出Closure Property这个概念? #

— Chapter 2.2 Hierarchical Data and the Closure Property
因为,要让Data成为“水”,Data能够像水一样流动,无限的自由。
另外,Data是被Procedure实现,所以Procedure的Higher-Order特质,使得Data具备了Closure Property的属性。
换句话说,水的属性不是被添加的,而是Data本来就是水,只不过才被认识到如此。在此之前,我们可能认为Data是大石头或者是盒子。

WHAT - 什么是Closure Property?
例如,自然数通过加法之后的结果还是自然数,我们就说自然数对于加法是闭合的(Closure Property)
而,自然数通过减法之后可能是负数,就不再是自然数,所以自然数对于减法就不是闭合的(Closure Property)
注意,这里不只说自然数是闭合的(Closure Property),而要基于一个行为:加法。所以,Closure Property需要Data和Procedure的配合实现。
所以,本质上是在定位Data的流动感,这个定位的概念就叫做Closure Property,或者说,Lisp中可以通过Porcedure让Data流动起来,我们的编程方向,也是要构建可以让Data流动起来的那种Procedure。
我们来看例子:Pairs经过Cons之后还是Pairs
(cons 1 2) ;这是一个Pair

(cons (cons 1 2)
(cons 3 4)) ;这还是Pair
所以,我们说Pairs对于CONS具有Closure Property

The ability to create pairs whose elements are pairs is the essence of list structure’s importance as a representational tool. We refer to this ability as the closure property of cons. In general, an operation for combining data objects satisfies the closure property if the results of combining things with that operation can themselves be combined using the same operation.

How - Closure Property的用处是什么?
自由组合出复杂机构的Data。
Closure is the key to power in any means of combination because it permits us to create hierarchical structures—structures made up of parts, which themselves are made up of parts, and so on.

注意,SICP中说到的Closure并不是函数式编程中通常说的Closure,SICP中的Closure是一个数学概念。

2.WHY - 我为什么要设计List这个概念出来? #

— Chapter 2.2.1 Representing Sequences
因为,要增加中间层,降低复杂度。这样遇到可以使用List的业务场景,就不需要每次都从Pairs开始构建。 Pairs —> List —> Business

HOW - 如何设计List的interface?
根据构建Data的思想框架framework:Constructors & Selectors
1.设计List的Constructors
我们可以利用Pairs的property构建复杂的数据结构,例如List:
(cons 1
(cons 2
(cons 3
(cons 4 nil)))
加上一层语法糖,List可以如此创建:
(list 1 2 3 4)
;等价于
(cons 1 (cons 2 (cons 3 (cons 4 nil)))

2.设计List的Selectors
我们可以直接使用car和cdr
(define one-through-four (list 1 2 3 4))

one-through-four
(1 2 3 4)

(car one-through-four)
1

(cdr one-through-four)
(2 3 4)

3.设计更有Power的,具有List特色的interface:List operations:
例如:
list-ref
(define (list-ref items n)
(if (= n 0)
(car items)
(list-ref (cdr items) (- n 1))))

(define squares (list 1 4 9 16 25))

(list-ref squares 3)
16

length
(define (length items)
(if (null? items)
0
(+ 1 (length (cdr items)))))

(define odds (list 1 3 5 7))

(length odds)
4

append
(define (append list1 list2_
(if (null? list1)
list2
(cons (car list1) (append (cdr list1) list2))))

(append squares odds)
(1 4 9 16 25 1 3 5 7)

(append odds squares)
(1 3 5 7 1 3 9 16 25)

map
Mapping over lists
目的:遍历list中的每个元素,并且将每个元素做与变量factor的乘法
传统思路如下,具象思维,头痛医头:
(define (scale-list items factor)
(if (null? items)
nil
(cons (* (car items) factor) ;这里写死了业务逻辑:乘法
(scale-list (cdr items) factor))))
高级思路,抽象思维,分离通用模式和业务接口,利用Higher-Order:
(define (map proc items)
(if (null? items)
nil
(cons (proc (car items));这里的proc就是对外开放的接口通道
(map proc (cdr items)))))

(map abs (list -10 2.5 -11.6 17)
(10 2.5 11.6 17)

(map (lambda (x) (* x x)) (list 1 2 3 4))
(1 4 9 16)
这就是Map这个概念的源头,Google的Mapping-Reduce思想就是源于此。
所以说,SICP就是计算机世界的元典。

之前的需求就可以改造成这样了:
(define (scale-list items factor)
(map (lambda (x) (* x factor))
items))

再一次,我们体会到了真正高级的Abstraction是如何思考问题的。
抽象模式,但是构建内部和外部的通道。给个性化业务以空间。
做到:古典主义(规范)和浪漫主义(超越)的平衡。
忽然醒到,J的画就是这种思路,画的主题很具象(规范),画的细很抽象(超越)。

Map一旦称为共识,Lisp解释器就可以为Map构建特殊,让Map基于硬件,加速运行。所以,规范都是快的。就像人的习惯思维不用思考就执行了。

3.有了List,我就可以构建更加复杂的数据结构了 #

— Chapter 2.2.2 Hierarchical Structures
(define x (cons (list 1 2) (list 3 4)))

(lenght x)
3

Lisp中的数据结构只用一种,就是List(要我说应该是只有Pairs),其他的数据结构都是由List组合而来。
为什么,Lisp要只抽象到List这一层就停止了?
为什么,Lisp没有更下沉,只提供Pairs,而不提供List?
答案是,这是一种平衡,即,自由度和可用性的平衡。
而,Lisp是倾向于自由的元典,所以只抽象了一层Data,即,List。
而,想其他语言对于数据的抽象,就很复杂了,因为他们的定位是更加的倾向实用性。更加的可以让用户方便的使用轮子。但缺点就是,如果一直只用这些语言,会一直被表象蒙蔽,看不到本质的源头。

4.现在的程序写的太混乱,如何能让程序变得有序,简单易懂? #

— Chapter 2.2.3 Sequences as Conventional Interfaces
现在的程序的形状太弯弯绕。如何能让程序的形状简单成一条线形。
这就是流程化编程思想的源头(这也是常江的Proc框架的思想源头,元典again!)

WHY - 程序复杂混乱的原因是什么?
因为,每个procedure的input和output都太个性化。
所以,导致procedure之间的组装都加入了个性化的逻辑,这些个性化的逻辑正是复杂性的原因。
HOW - 如何让Procedure的input和output统一呢?
启发,来自于Data概念引出的Closure Property,如果我们让Procedure和Data配合起来,都满足Closure Property,那么,写程序,就会想搭积木/LEGO积木一样,简单明了。因为Procedure称为了统一的链接上下游的标准LEGO块。
而之前的构建,就像是在用橡皮泥进行搭建,每个链接都要花费大量的思考。因为Procedure的链接不统一。
这,正是抽象出Data这个想象概念的作用。
想象出一个不同维度的中间层,让协作变得简单。

我们看例子:求一棵树结构的每个叶子节点的平方后的和
1.传统的思路:以数据为中心,围着数据构建procedure进行操作。这种感觉就像是自己在厨房做菜一样,围着食材进行操作。
(define (sum-odd-squares tree)
(cond
((null? tree) 0)
((not (pair? tree))
(if (odd? tree) (square tree) 0))
(else (+ (sum-odd-squares (car tree))
(sum-odd-squares (cdr tree))))))
2.流程化的思路: 以流程为中心,先构建流程体系,让Data进入流水线。这种感觉就像是食品加工工厂。同样的需求流程化改造后如下:
| emumerate:tree leaves |—>| filter:odd? |—>| map:square |—>| accumulate: +,0 |

(define (sum-odd-squares tree)
(accumulate + 0
(map square
(filter odd?
(enumerate-tree tree)))))
这样以来,tree作为食材(Data)流经了食品加工体系(procedures)
这样就抽象出了一个层,业务宏观层。而之前的方案是没有逻辑分层的,只有一层。分层可以降低复杂性,对于Abstraction的理解是不是又加深一步?
让抽象无处不在吧!
但也不能无节制的抽象,要找到一种平衡。抽象的本质就是分类。一类事就是同一个level的概念。

3.模块的实现:下面我们将各个模块的细节实现吧:这种感觉即是建设加工体系
这让我想起一个故事,一个村子要引入水源,方案1是用桶拎,方案2是铺设水管。
如何选,要根据业务来定,并不能什么需求都大动干戈的铺设水管。
模块1:enumerate-tree,枚举tree,将tree的元素搞成线性list)
(define (enumerate-tree tree)
(cond
((null? tree) nil)
((not (pair? tree)) (list tree))
(else (append (enumerate-tree (car tree))
(enumerate-tree (cdr tree))))))

(enumerate-tree (list 1 (list 2 (list 3 4)) 5))
(1 2 3 4 5)

** 模块2:filter,按照特定逻辑过滤元素 **
(define (filter predicate sequence)
(cond
((null? sequence) nil)
((predicate (car sequence))
(cons (car sequence)
(filter predicate (cdr sequence))))
(else (filter predicate (cdr sequence)))))

(filter odd? (list 1 2 3 4 5)
(1 3 5)
突然有了一个领悟,要允许自己看不懂代码,要用直觉去感受代码的意思,要想象的去理解。要虚看,要会猜。有点像用英语的感觉。

模块3:map,可以复用之前存在的map, 放
(define (map proc items)
(if (null? items)
nil
(cons (proc (car items));这里的proc就是对外开放的接口通道
(map proc (cdr items)))))

(map square (list 1 2 3 4 5))
(1 4 9 16 25)

模块4:accumulate,聚合,收
(define (accumulate op initial sequence)
(if (null? sequence)
inital
(op (car sequence)
(accumulate op initial (cdr sequence)))))

(accumulate + 0 (list 1 2 3 4 5))
15

(accumulate * 1 (list 1 2 3 4 5))
120

(accumulate cons nil (list 1 2 3 4 5))
(1 2 3 4 5)

这里有个领悟,对于我自己,构建体系就是养成好习惯,每一个模块就是一个习惯。数据就是我们的生命本身。如果想改变命运,要看的长远去构建体系(修正习惯)而不是做眼前的自我评判。所以,以后不要评判自己为什么总是懒惰,为什么总是浪费时间,而是要耐心的养成好习惯。
评判自己然后改正,就是用水桶拎水,见效快,但是效益递减:对数曲线。
构建新的习惯体系,就是建立水管系统,见效慢,但是效益递增:指数曲线。
所以,工欲善其事,必先利其器。
看得长远些,做时间的朋友。这也是我为什么投资自己重构底层概念系统的思路。
《掌控习惯》这本书就是在说这个事,要关注体系,不要关注目的。

另一个启发,要让自己的生活流程简单化。这样就不用总想该干啥。一段时间就干一个事,要干的事的类别要少,要抽象。
例如啃SICP的流程:
Data:每个章节
体系:Read—>Write—>Exercise

5.Why I Need design A Picture Language?我为什么要设计一个图像语言? #

— Chapter 2.2.4 Example:A Picture Language
因为,要构建一种特别的图像。如下:
(img) (img)
另外,我们可以用这个实际的需求串联之前讨论过的概念,包括:
1.Data Abstraction
2.Closure Property
3.Higher-order Procedures

因为,这类图像的特殊之处是,从一个基本图像开始,通过组装而成。所以,具有逻辑性,可以用简单的语言来描述,所以,我们可以通过设计一门新语言来描述它。

所以,设计思想还是要遵从framework如下:
1.The language’s primitives: painter
2.Means of combination: beside, below, flip-vert, flip-horiz
3.Means of abstraction: as Scheme Procedures

1.Painter #

In this picture language is that there is only one kind of element, called a painter. A painter draws an image that is shifted and scaled to fit within a designated parallelogram-shaped frame.

painter是一个procedure,目的是画出一个基础图像,如下:
(img)
当,frame变化时候,painter根据frame,自动的进行扭曲来适应frame,如下:
(img) (img) (img)
WHY-为什么要设计Frame的概念?
因为,要遵循分层思想,将图像分离成三个部分:
1.基础图像的绘制:painter
2.基础图像的画框:frame
3.多个画框的组合:operations
分层之后,降低了每一个层的复杂度。
如果没有frame的独立概念,那么要表达扭曲的图像,则要增加painter的逻辑复杂性。painter的参数就不只frame一个了。
例如,frame没有变化,只是更改基础图像,我就就会看到下面效果:
(img) (img)
(img) (img)

HOW - 如何用procedure来实现painter?
(define (segments->painter segment-list)
(lambda (frame)
(for-each
(lambda (segment)
(draw-line
((frame-coord-map frame) (start-segment segment))
((frame-coord-map frame) (end-segment segment))))
segment-list)))

2.Frame #

HOW - 如何用procedure实现frame?
frame的本质是一个四边形,是一个静态的名词,所以我们可以用Data来表示frame,我们可以用三个坐标vector来描述一个矩形:初始点坐标origin,第一条边的坐标edge1和第二条边的坐标edge2。如下:
(img)
Data的LEVEL1的interface实现要符合constructor和selectors的framework。
方案1:use list
;1.constructor
(define (make-frame origin edge1 edge2)
(list origin edge1 edge2))

;2.selectors
(define (origin-frame frame)
(list-ref frame 0)

(define (edge1-frame frame)
(list-ref frame 1)

(define (edge2-frame frame)
(list-ref fame 2)

方案2:use cons
;1.constructor
(define (make-frame origin edge1 edge2)
(cons origin (cons edge1 edge2)))

;2.selectors
(define (origin-frame frame)
(car frame)

(define (edge1-frame frame)
(car (cdr frame))

(define (edge2-frame frame)
(cdr (cdr frame))

HOW - 如何设计frame的LEVEL2的interface?
上面我们实现了frame的基础interface,接下来为了要让frame对外提供特殊的服务,我们需要构建LEVEL2的interface。
frame可以提供什么服务呢?
WHY - 还记得我们为什么要设计frame吗?
为了,要让painter绘画的时候有个参考坐标的画框。
所以,frame提供的服务,就是提供给painter,告诉painter你之前的坐标在frame中的新坐标是什么。
所以,frame提供的服务,就是接收一个坐标vector,返回一个经过frame映射之后的新坐标vector,并且这个新坐标一定在frame之中。
实现思路如下:
对于一个painter提供的新坐标vector v=(x, y)
经过frame的转化后:Origin(Frame) + x·Edge1(Frame) + y·Edge2(Frame)
这里约定,painter提供的坐标是基于标准的1乘1的frame

具体实现如下:
(define (frame-coord-map frame)
(lambda (v)
(add-vert
(origin-frame frame)
(add-vect (scale-vect (xcor-vect v)
(edge1-frame frame))
(add-vect (scale-vect (xcor-vect v)
(edge2-frame frame))

这里又根据抽象思想,分离出了一个新的Data概念:坐标Vector,而不是让frame-coord-map来承担所有的复杂性。
将复杂性剥离出来,是编程高手的体现。
复杂性的剥离,需要用抽象的视角,来定位哪些概念可以独立存在,成为一个Data或者Procedure。动词则抽象为Procedure,名词则抽象为Data。

所以我们还需要来构建Vector
1.Implementation of Level1-Interface
;1.constructor
(define (make-vect x y)
(cons x y))

;2.selector
(define (xcor-vect v)
(car v)

(define (ycor-vect v)
(cdr v)

2.Implementation of Level2-interface: add-vect, sub-vect, scale-vect
思路如下:
add-vect: (x1, y1) + (x2, y2) = (x1 + x2, y1 + y2)
sub-vect: (x1, y1) - (x2, y2) = (x1 - x2, y1 - y2)
scale-vect: s·(x, y) = (sx, sy)
具体实现如下:
(define (add-vect v1 v2)
(make-vect
(+ (xcor-vect v1) (xcor-vect v2))
(+ (ycor-vect v1) (ycor-vect v2))))

(define (sub-vect v1 v2)
(make-vect
(- (xcor-vect v1) (xcor-vect v2))
(- (ycor-vect v1) (ycor-vect v2))))

(define (scale-vect s v)
(make-vect
(* s (xcor-vect v))
(* s (ycor-vect v))))

3.Operations of picture language #

我们实现了The language’s Primitives: Painter/Frame
下一步就要实现It’s means of combination了
如何将基础的图像组合,
这里就是picture language的Level 2 的interface了:beside, below, flip-vert, flip-horiz

忽然有个感悟,感觉通了,对于抽象思想,分层思想:
Level 1 interface = primitives expressions
Level 2 interface = means of combination
Level 3 interface = means of abstraction
抽象就是定位,思考分与合的平衡,抽离出想象的位置。

我们先想象一下如果这些procedure已经实现后的效果,最后再去实现这些procedure,
flip-vert反转后再beside左右并列—(define wave2 (beside wave (flip-vert wave)))—的效果如下:
(img)

上下并列—(define wave4 (below wave2 wave2))—的效果如下:
(img)

我们还可以使用递归来进行组合:
效果如下:
(img) (img)
实现如下:
(define (right-split painter n)
(if (= n 0)
painter
(let ((smaller (right-split painter (- n 1))))
(beside painter (below smaller smaller)))))

(define (corner-split painter n)
(if (= n 0)
painter
(let ((up (up-split painter (- n 1)))
(right (right-split painter (- n 1))))
(let ((top-left (beside up up))
(bottom-right (below right right))
(corner (corner-split painter (- n 1))))
(beside (below painter top-left)
(below bottom-right corner))))))

还记得最初的需求吗,我们可以画出那种四周递归效果
效果如下:
(img)
基于刚才实现的corner-split,我们只需将四个corner-split拼接,实现如下:
(define (square-limit painter n)
(let ((quarter (corner-split painter n)))
(let ((half (beside (flip-horiz quarter) quarter)))
(below (flip-vert half) half))))

所以,当我们让the picture language嵌入lisp,它就可以继承lisp的能力。
包括刚才看到的procedure的组合能力,Data的closure property,procedure的抽象能力define。
接下来还有procedure的Higher-order能力
Higher-Order的本质目的是通过入参组合多个procedure,输出一个新的procedure。这也是遵从着Abstraction思想,Black-Box Abstraction.
但是,这里的输入procedure因为是变量,所以可变,也就是,本质上这个Black-Box有了通道/接口。

所以,Higher-Order正是Means of Abstraction的一种牛逼体现。

我们来看一个使用Higher-Order的需求:构建一个模式,即,四个frame,对外提供的接口为四个可以控制图像方向的procedure,分别是tl tr bl br
具体实现如下:
(define (square-of-four tl tr bl br)
(lambda (painter)
(let
((top (beside (tl painter) (tr painter)))
(bottom (beside (bl painter) (br painter))))
(below bottom top))))

这样一来,flipped-pairs可以用上的square-of-four来实现,如下:
(define flipped-pairs painter

(let ((combine4 (square-of-four identity flip-vert identity flip-vert)))
    (combine4 painter)))

这样,square-limit可以这样实现:
(define (square-limit painter n)
(let ((combin4 (square-of-four flip-horiz identity rotate180 flip-vert)))
(combine4
(corner-split
painter n)))
对比一下之前square-limit的实现,感受一下Higher-Order的精髓:模型思维
(define (square-limit painter n)

(let ((quarter (corner-split painter n)))
(let ((half (beside (flip-horiz quarter) quarter)))
(below (flip-vert half) half))))

Higher-Order思维:
1.定位思维
2.模型思维
3.抽象思维
以上都是一回事

HOW - implementation of Operations: flip-vert , beside, etc #

回过头来,我们现在要真正的来实现这些Operation了,包括:beside,below,flip-vert,flip-horiz
分一下类:
操纵单个painter的扭曲类:flip-vert, flip-horiz, rotate90
组合多个painter的组合类:beside, below

HOW - implementation of flip-xxx
flip-xxx类的Operation的目的是要操纵单个的Painter,使其发生位置的扭曲。而不是内容的变化。所以,只需要操纵frame即可。
也就是说,Operations的抓手interface是frame.
Operations—>Frame—>Painter
我们将这个操纵Painter的行为,抽象成一个概念:transform-painter。
也就是说,这些Operations在抽象上的概念定位就是在transform-painter.
实现的思路就是通过创建一个新的frame来影响painter。
还记得吗?定义frame需要三个坐标:origin, end of edge1, end of edge2
这里要提醒一下,新坐标是要相对于painter的默认frame的,painter的默认frame的坐标为:
origin: (0, 0)
edge1: (1, 0)
edge2:(0, 1)
具体实现如下:
(define (transfor-painter painter origin corner1 corner2)
(lambda (frame)
(let ((m (frame-coord-map frame)))
(let ((new-origin (m origin)))
(painter
(make-frame new-origin
(sub-vect (m corner1) new-origin)
(sub-vect (m corner2) new-origin)))))))
刚才我们定义了operation的统一的模式:操纵painter,下面我们要来看如何实现具体的operation。这里体现的还是Higher-Oder思想
flip-vert:反转
(define (flip-vert painter)
(transform-painter painter
(make-vect 0.0 1.0) ;new origin
(make-vect 1.0 1.0) ;new end of edge1
(make-vect 0.0 0.0) ;new end of edge2

我们还可以定义出一个新的操作:逆时针旋转90度,rotate90,只需要通过定义frame的三个坐标。
(define (rotate90 painter)
(transform-painter painter
(make-vect 1.0 0.0)
(make-vect 1.0 1.0)
(make-vect 0.0 0.0)))

**HOW - implementation of beside
上面,我们实现的是单个的painter的操纵,下面我们要来看如何组合Painter。
beside(左右组合)的具体实现:
(define (beside painter1 painter2)
(let ((split-point (make-vect 0.5 0.0)))
(let (
(paint-left
(transform-painter painter1
(make-vect 0.0 0.0)
split-point
(make-vect 0.0 1.0)))
(paint-right
(transform-painter painter2
split-point
(make-vect 1.0 0.0)
(make-vect 0.5 1.0))))
(lambda (frame)
(paint-left frame)
(paint-right frame)))))

同理,我们可以来实现below(上下组合),具体实现如下:
(define (below painter1 painter2)
(let ((split-point (make-vect 0.0 0.5)))
(let (
(paint-up
(transform-painter painter1
(make-vect 0.0 0.0)
(make-vect 1.0 0.0)
split-point)))
(paint-down
(transform-painter painter2
split-point
(make-vect 1.0 0.5)
(make-vect 0.5 1))))
(lambda (frame)
(paint-up frame)
(paint-down frame)))))

总结Summary #

体会一下抽象思维,一开始我们先假设具体的operation已经实现,例如假设beside,flip-vert已经实现,在此想象的基础上,我们在讨论如何使用Higher-Order的组合例如:corner-split,square-limit。这就是抽象思维,忽略细节,只进行形而上的思考,这样做的好处是,宏观思维,整体掌控,系统性思考。
最后,我们再把欠下的债实现,进入形而下的细节实现,例如:flip-vert的实现。即便是具体实现flip-vert ,我们也要使用我们的思维武器:抽象,Higher-Order,首先,思考operation能否定位出抽象模式,例如:transform-painter。
即,定位:不变的模式和易变的业务。
这种定位思维,就是编程的核心能力。
定位思维=模型思维=抽象思维
形象化一点的描述:Black-Box Abstraction,漩涡模型,interface模型,分层模型

这里总结一下分层模型,called stratified design.
分层模型思维,适用于设计Data,设计Language,
Each level is constructed by combining parts that are regarded as primitive at that level, and the parts constructed at each level are used as primitives at the next level.

后面我们还会设计一种集成电路语言,目的是针对digital-circuit design,在这个新语言中的primitive将是and-gates , or-gate
通过combination将会构建出cpu,memory system。

分层设计思想Stratified design的真正意义:
让Power可以在层与层之间继承,积聚复杂度。复杂度就是力量。
编程的目的就是构建复杂Build Abstraction,但是因为脑力的限制,编程的要解决的核心问题又是如何控制复杂Control Complexity。
即想要复杂,又担心过于复杂。
所以,分层思想就现实了他的价值,
分解:分解多层,控制每一层的复杂度。
合并:层与层之间的连接,让复杂度可以累加。
这就像太极拳,每一个动作都是放松,但是多个动作的力量可以累加,最终像甩鞭子一样爆发。
所以,分层思想所能爆发出来的最终复杂度是指数级的,太极拳的发力也是指数级的。
这也是放松流动的价值:每一件事都做好,每件事之间都有继承性,最终会爆发出意想不到的效果。
关注积累(放松),而非力量(紧张),使用好时间这个链接器。所以,要去构建体系(铺设水管)。
例如,我现在构建的这层认知(啃SICP)未来的应用层(做项目)会用到。

让每一天都很简单,然后用时间将它们连接起来。关键是要有连续性,力量才能呈放大式的积累,最终一刻爆发。所以,选好一个方向,不要变,不要急,时间会让你成为指数曲线的。

分层设计思想隐喻了:放松、慢、甩鞭子、self-refer自引用,closure property,复利曲线、构建指数的秘密。

Chapter 2.3 Symbolic Data #

1.Why I design Symbolic Data?我为什么要设计符号化数据? #

— 2.3 Symbolic Data
因为,要构建另一种规则下的抽象。之前构建的规则都是基于数字。除了数字规则还存在字符规则。字符规则更加接近于自定义规则,也更加多见于真实世界的业务。
HOW- 如何来表达字符数据呢?
答案是,使用Quote,Quote等价于\’
效果如下:
(define a 1)
(define b 2)

(list a b)
(1 2)

(list ‘a 'b)
(a b)

(list 'a b)
(a 2)

(car ’(a b c))
a

(cdr ‘(a b c))
(b c)

(list (list 'george))
((george))

(cdr ’((x1 x2) (y1 y2)))
((y1 y2))

(cadr ‘((x1 x2) (y1 y2)))
(y1 y2)

WHY - 为了呈现Symbolic Data的实际效果,我们来实现一个求微分的需求
— Chapter 2.3.2 Example: Symbolic Differentiation

微分的规则,本质上是字符规则,不是数字规则,虽然看上去属于数学。
规则描述如下reduction rules:
dc/dx = 0
dx/dx = 1
d(u + v)/dx = du/dx + dv/dx
d(uv)/dx = u(dv/dx) + v(du/dx)

下面我们来构建一个procedure将这些规则实现,这个procedure的名字就叫derive。它的本质就是一个解释器,输入一个字符数据,输入一个映射过后的字符数据。映射的本质就是字符求值,就是在做reduction,就是在做组装。
在实现功能derive之前,我们为了遵从分层思想,先要构建一层数据层。
所以我们要新的数据类型。数据的定义要遵从:selectors/constructors思想。
下面我们定义基础层的interface:
(variable? e)
(same-variable? v1 v2)

(sum? e)
(addend e)
(augend e)
(make-sum a1 a2)

(product? e)
(multiplier e)
(multiplicand e)
(make-product m1 m2)

基于这层基础interface来构建应用层deriv 如下:
(define (deriv exp var)
(cond ((number? exp) 0)
((variable? exp)
(if (same-variable? exp var) 1 0))
((sum? exp)
(make-sum (deriv (addend exp) var)
(deriv (augend exp) var)))
((product? exp)
(make-sum
(make-product (multiplier exp)
(deriv (multiplicand exp) var))
(make-product (deriv (multiplier exp) var)
(multiplicand exp))))
(else
(error “unknown expression type – DERIV” exp))))

HOW-如何具体的实现基础层的interface呢?如下:
(define variable? x) (symbol? x))

(define (same-variable? v1 v2)
(and (variable? v1) (variable? v2) (eq? v1 v2)))

(define (make-sum a1 a2) (list ’+ a1 a2))
(define (make-product m1 m2) (list ‘* m1 m2))

(define (sum? x)
(and (pair? x) (eq? (car x) ’+)))

(define (addend s) (cadr s))

(define (augend s) (caddr s))

(define (product? x)
(and (pair? x) (eq? (car x) ‘*)))

(define (multiplier p) (cadr p))
(define (multiplicand p) (caddr p))

(deriv ’(+ x 3) ‘x)
(+ 1 0)

(deriv ’(* x y) ‘x)
(+ (* x 0) (* 1 y))

WHAT - 编程的本质是什么?
编程就是描述规则。规则的本质就是解释器。
Data—input—>业务规则—output—>Data
编程的核心问题:控制复杂度,用规则视角来看,就是通过设计如何更高级的表达规则而已。

2.Why I design Sets?我为什么要设计Set这种类型的Data? #

— Chapter 2.3.3 Example: Representing Sets
因为,去掉重复后的数据更接近抽象的“真实”
WHAT:之前的List是可以重复,Set是去掉了重复。
WHAT - Set有什么实际用处呢?
由于索引唯一,可用于查询数据,效果如下:
(define (lookup given-key set-of-records)
(cond
((null? set-of-records) false)
((equal? given-key (key (car set-of-records)))
(car set-of-records))
(else (lookup given-key (cdr set-of-records)))))

Set的表象是无重复集合,但是却可以用不同的方式来实现,查询和插入的效率各有不同,Set的不同子类如下:
1.Sets as unordered lists:用无序的list来构建Set
;查询x是否在set中存在
(define (element-of-set? x set)
(cond
((null? set) false)
((equal? x (car set)) true)
(else (element-of-set? x (cdr set)))))

;插入x到set中
(define (adjoin-set x set)
(if (element-of-set? x set)
set
(cons x set)))

;求两个set的交集
(define (intersection-set set1 set2)
(cond
((or (null? set1) (null? set2)) ’())
((element-of-set? (car set1) set2)
(cons (car set1)
(intersection-set (cdr set1) set2)))
(else (intersection-set (cdr set1) set2))))

;求两个set的并集
(define (union-set set1 set2)
(cond
((and (null? set1) (null? set2)) ‘())
((element-of-set? (car set1) set2)
(union-set (cdr set1) set2)))
(else
(cons (car set1) (union-set (cdr set1) set2)))))

2.Sets as ordered lists:用有序的list来构建Set
WHY - 为了提高查询效率,有序的list比无序更高效。
Why?因为,有序比无序多了一层逻辑(分层思想),也就是多了一层规则,而规则的本质就是在表达未来(根据眼前的数据就可以推测未来的数据)。所以,掌控规则的目的就是预测未来。
另外一个启发,数据的本质就是描述状态。所以状态即数据,数据即状态。就是在表达一瞬间的微分(世界的切面)。
具体实现如下:
;查询效率更高
(define (element-of-set? x set)
(cond
((null? set) false)
((= x (car set)) true)
((< x (car set)) false)
(else (element-of-set? x (cdr set)))))

;插入set
(define (adjoin-set x set)
(if (element-of-set? x set)
set
;如果x不存在于set则需要找到自己的位置,这里就是POWER的源头
(cond
((< x (car set)) ;先苦后甜,复杂就是力量,复杂度在构建时压入
(cons x set)
(else
(cons (car set) (adjoin-set x (cdr set)))))

;求并集的效率更高
(define (intersection-set set1 set2)
(if (or (null? set1) (null? set2)) ’()
(let ((x1 (car set1)) (x2 (car set2)))
(cond
((= x1 x2)
(cons x1
(intersection-set (cdr set1) (cdr set2))))
((< x1 x2)
(intersection-set (cdr set1) set2))
((< x2 x1)
(intersection-set set1 (cdr set2)))))))

3.Sets as binary trees:用二叉树来构建Set
之前的有序list的复杂度还可以再提升,二叉树就是讲复杂度指数级提升。所以效率提升至了n2,时间复杂度降低到了O(logn)
思路如下:数据的关系为二叉树,并且左边小于右边。
本质上是:即有序,又结构化
具体实现如下:
(define (entry tree) (car tree)

(define (left-branch tree) (cadr tree))

(define (right-branch tree) (caddr tree))

(define (make-tree entry left right)
(list entry left right))

(define (element-of-set? x set)
(cond
((null? set) false)
((= x (entry set)) true)
((< x (entry set))
(element-of-set? x (left-branch set)))
((> x (entry set))
(element-of-set? x (right-branch set)))))

;构建set,构建的逻辑更复杂,binary tree的Power来源
(define (adjoin-set x set)
(cond
((null? set) (make-tree x ‘() ’()))
((= x (entry set)) set)
((> x (entry set))
(make-tree (entry set)
(adjoin-set x (left-branch set))
(right-branch set)))
((> x (entry set))
(make-tree (entry set)
(left-branch set)
(adjoin-set x (right-branch set))))))
启发,Set的构建越来越复杂,查询的效率越来越高。WHY?因为增加了规则层。
所以,规则的本质是预测未来,即,可以提前知道结果。
所以,学习就是再构建复杂层。这里又呼应了流程化和分层思想。

3.Why I Design Huffman Encoding Trees?我为什么要设计Huffman编码树? #

为了,给普通字符加一层逻辑。原来是ABCD这种只有一层,现在是ABCD/000001010011两层逻辑,中间多了一个字符和01之间的规则层:
A=000, B=001, C=010, D=011
分层思想,每增加一层,复杂度指数级增长,复杂就是power。
这样我们的底层就可以更简单(只需要面对0/1两种元素),上层可以更复杂。

第一版的映射规则:fixed-lenght codes 定长编码
A=000, B=001, C=010, D011, E=100, F=101, G=110, H=111
BACADAEAFABBAAAGAH就可以表示成54bits:001000010000011000100000101000001001000000000110000111
第二版的映射规则:variable-lengh codes 变长编码
WHY - 为了提升效率,节省空间,我想升级一下,将频率高的字符制定更短的编码。例如:
A=0, B=100, C=1010, D=1011, E=1100, F=1101, G=1110, H=1111
BACADAEAFABBAAAGAH就可以表示成42bits,并节省20%空间:100010100101101100011010100100000111001111
但是这里来了一个问题,由于每个字符不定长,如何表示字符的分隔?
方案一:separator code—加入分隔符
方案二:prefix code—加入前缀
方案三:Huffman encoding method—我们的主角登场啦!

WHAT - 什么是Huffman Encoding Method?
(img)
Huffman Encoding Method就是,用一个binary tree来表示字符和0/1的映射规则。树的组织按照更复杂的逻辑,即,每个字符的编码为从root到这个叶子字符的路径,从root开始左边路径就是0,右边路径就是1。
例如D在这棵树上的搜寻路径:1011 (1右,0左,1右,1右),所以D=1011
而对于A=0

WHAT -Huffman的本质隐喻了什么?
给我的启发是,如果想要得到更大的Power,让规则结构化。
规则太线形,power就不够。例如之前的构建set的规则递增,power就递增。
Set as unordered list < Set as ordered list < Set as binary tree.
同理,构建字符和01的映射规则越复杂,power越大。
当然,不能太复杂,要找到一种平衡,脑力处理的平衡。
这种平衡可以通过分层思想来控制,即,分层可以control complexity
即想要复杂的power又想要control complexiy,看似矛盾的需求可以通过分层来实现。
fixed-lenght codes < variable-lenght codes ( separator code < prefix code < huffman code)

HOW - 如何表达Huffman Trees?
叶子的具体实现如下:
; constructors 叶子结点的构建
(define (make-leaf symbol weight)
(list ‘leaf symbol weight))

;predicates
(define (leaf? object)
(dq? (car object) 'leaf))

;selectors
(define (symbol-leaf x) (cadr x))

(define (weight-leaf x) (caddr x))

整棵树的构建如下:
;constructors 整棵树的构建
(define (make-code-tree left right)
(list left
right
(append (symbols left) (symbols right))
(+ (weight left) (weight right))))

;selector 树的选择
(define (left-branch tree) (car tree))

(define (right-branch tree) (cadr tree))

(define (symbols tree)
(if (leaf? tree)
(list (symbol-leaf tree)) ;For closure property
(caddr tree)))

(define (weight tree)
(if (leaf? tree)
(weight-leaf tree)
(cadddr tree)))

The decoding procedure,对外提供的解码interface,提供了一个框架,根据参数的tree来对另一个具体编码的Data进行解释,所以,这个procedure就是一个解释器。
(define (decode bits tree)
(define (decode-1 bits current-branch)
(if (null? bits)
’()
(let ((next-branch (choose-branch (car bits) current-branch)))
(if (leaf? next-branch)
(cons (symbol-leaf next-branch)
(decode-1 (cdr bits) tree)
(decode-1 (cdr bits) next-branch)))))
(decode-1 bits tree))

(define (choose-branch bit branch)
(cond ((= bit 0) (left-branch branch))
((= bit 1) (right-branch branch))
(else (error “bad bit – CHOOSE-BRANCH” bit))))

WHY - weight有什么用?
weight表示字符的使用频率,例如A的频率最高为8,D频率最低为1
答案是,构建tree的时候,结点如何可以根据使用频率自动组织。
这里给我的启发是,如果基于Metanet来构建Data,也要引入一个属性来表达使用频率。
所以,这里又揭示了,Data中属性的作用。
即,每一个属性,都在表达某个维度的关系。属性即维度。
就像Huffman tree中,属性包括 symbol和weight,symbol等于应用维度(字符展示),weight等于构建维度(如何自动组织起来,频率越高的字符越靠近root)

具体实现如下:
(define (adjoin-set x set)
(cond
((null? set) (list x))
((< (weight x) (weight (car set))) (cons x set))
(else (cons (car set)
(adjoin-set x (cdr set))))))
有了weight这个维度,就可以自动构建Huffman tree了,可以升级之前构建Huffman tree的纯收工方式,即,之前的构建需要自己设计节点的位置。升级后的构建Procedure可以自动化,更加智能。 具体实现如下:
(define (make-leaf-set pairs)
(if (null? pairs)
‘()
(let ((pair (car pairs)))
(adjoin-set (make-leaf (car pair) ;symbol
(cadr pair)) ;frequency
(make-leaf-set (cdr pairs))))))

WHAT - Huffman揭示了数据库构建索引的本质。
这就是数据库建立索引的思想源头。
即,对于使用者来说,数据还是那些数据,但是内部的组织形式已经升级,加入了更复杂的规则。复杂就是力量,复杂规则提升了数据的使用效率。

Chapter 2.4 Multiple Representations for Abstract Data #

1.Why I need Multiple Representations for Abstract Data?我为什么需要基于多种实现方式来构建Data? #

因为,需要多人协作来构建程序。

之前的rational number有理数也是遵从了分层思想,但是不需要多人协作,因为每次只有一种实现方式,所以层与层是线性关系,如下图:
(img)

但是现在,需要多人协作,每个人对于同一个Data的实现方式不同,但是提供给Higher-Level的interface是相同的(generic procedures)
例如,complex number复数的实现方式,就有至少两种:
1) rectangular form(real part and imaginary part)
2) polar form (magnitude and angle)
complex number对于高层提供的generic procedures包括:add-complex, sub-complex, mul-complex, div-complex
我们说,复杂就是力量,所以要让分层变成结构化逻辑。如下图:
(img)
后面,我们会看到具体实现这种多个lower-level支撑一个higher-lever的思路为两个新的概念,即,type tags类型标记,和,data-directed数据引导

2.HOW - 第一版的complex number实现方案—传统思路 #

— Chapter 2.4.1 Representations for Complex Numbers
WHAT - 我们首先要来看一下complex number的组成和计算规则:

(img)

complex number的描述参考系:
1)Real
2)Imaginary
3)Magnitude
4)Angle
一个具体complex number的元素:
1)x
2)y
3)r
4)A
complex number 元素之间的关系规则:
1)x = r cos A
2)y = r sin A
3)r = √(x2 + y2)
4)A = arctan(y, x)

complex number之间的运算规则
1)Real-part(z1 + z2) = Real-part(z1) + Real-part(z2)
2)Imaginary-part(z1 + z2) = Imaginary-part(z1) + Imaginary-part(z2)
3)Magnitude(z1·z2) = Magnitude(z1) · Magnitude(z2)
4)Angle(z1 · z2) = Angle(z1) + Angle(z2)

WHAT - 抽象思想:CODE就是对于规则的描述,所以对于complex number的编码就是对上面规则的描述。
不够我们先来设计procedure,先定义procedure的名字和架构,后面再来具体实现。WHY?为什么要这样做,这就是抽象思维,先假装已经有了,让系统转起来。

1)设计构建procedures,selectors/constructors interface,效果如下:
;selectors
(define (real-part z) (…))
(define (imag-part z) (…))
(define (magnitude z) (…))
(define (angle z) (…))

;constructors
(make-from-real-imag (real-part z) (imag-part z))
(make-from-mag-ang (magnitude z) (angle z))
2)设计interface层,基于刚才设计的selectors和constructors,效果如下:
(define (add-complex z1 z2)
(make-from-real-imag (+ (real-part z1) (real-part z2))
(+ (imag-part z1) (imag-part z2))))

(define (sub-complex z1 z2)
(make-from-real-imag (- (real-part z1) (real-part z2))
(- (imag-part z1) (imag-part z2))))

(define (mul-complex z1 z2)
(make-from-mag-ang (* (magnitude z1) (magnitude z2))
(+ (angle z1) (angle z2))))

(define (div-complex z1 z2)
(make-from-mag-ang (/ (magnitude z1) (magnitude z2))
(- (angle z1) (angle z2))))

**HOW - 现在我们来具体实现Complex number的selectors/constructors interface层的procedures
这里有两种不同的选择路径来实现complex number
1)Ben选择了rectangular form(real part and imaginary part),具体实现如下:
;selectors
(define (real-part z) (car z))

(define (imag-part z) (cdr z))

(define (magnitude z)
(sqrt (+ (square (real-part z))
(square (imag-part z)))))

(define (angle z)

(atan (imag-part z) (real-part z)))

;constructors
(define (make-from-real-imag x y) (cons x y))

(define (make-from-mag-ang r a)
(cons (* r (cos a)) (* r (sin a))))

2)Alyssa选择了polar form (magnitude and angle),具体实现如下:
;selectors
(define (real-part z)
(* (magnitude z) (cos (angle z))))

(define (imag-part z)
(* (magnitude z) (sin (angle z))))

(define (magnitude z) (car z))

(define (angle z) (cdr z))

;constructors
(define (make-from-real-imag x y)
(cons (sqrt (+ (square x) (square y)))
(atan y x)))

(define (make-from-mag-ang r a) (cons r a))
不论是用Ben还是Alyssa哪种方式实现的complex number,interface层的procedure都不用变更,他们感知不到,这就是black-box abstraction。

3. HOW - 第二版的complex number实现方案—Tagged Data #

— Chapter 2.4.2 Tagged data
WHY - I need Tagged Data?我为什么需要类型标记的Data?
因为,问题出现了,问题是什么呢?
问题是,对于complex number我们得到的是一个pair,当我想要操作这个pair来获取magnitude的时候,我不知道,这个代表着complex number的pair是用哪种类型实现的,例如,我得到到的complex number是这样一个pair :(3 4)
如果我想求它的magnitude,如果我假设它是Ben实现的,那么我的到的结果是5(interpreting the number in rectangular from)。但是,如果我假设它是Alyssa实现的,那么得到的结果是3(interpreting the number in polar form)
所以,我需要发明一种方法来分辨Data的实现类别,这就是Tagged Data。
(启发:如果在没有类型的编程语言中,解决这个问题,就需要类型推导,即,找到定义这个Data的地方,看他具体的实现方式是什么。这就是类型推导思想的源头!)

HOW - Tagged Data的设计思路如下,当然还是通过procedure来表达Data:
1)type-tag:selector 表示类型 — rectangular or polar
2)contents:selector 表示之前的具体数据 是一个pair
3)attach-tag : constructor 将type-tag和contents粘合起来
; constructor
(define (attach-tag type-tag contents)
(cons type-tag contents))

;selectors
(define (type-tag datum)
(if (pair? datum)
(car datum)
(error “Bad tagged datum – TYPE-TAG” datum)))

(define (contents datum)
(if (pair? datum)
(cdr datum)
(error “Bad tagged datum – CONTENTS” datum)))

我们需要自定义判断polar还是rectangular类型的procedure:
(define (rectangular? z)
(eq? (type-tag z) 'rectangular))

(define (polar? z)
(eq? (type-tag z) 'polar))

HOW - 基于Tagged Data来改造第一版的complex number的方案,如下:
(img)
1)HOW-设计重构思路
原来只有两层:
Layer1:selector/constructor interface(Rectangular representation和Polar representation)
Layer2:application interface (add-complex, sub-complex…)
因为整个系统增加了一个type的逻辑,所以整个层级也要相应的多出一层。即,将selector/constructor interface抽象为结构化的三角形状,
上层合并,保持selector和constructor的interface不变,
下层分离,分别抽象出具体的实现层underlying representation,所以在这一层,之前的procedure名字要加上类型后缀。这样就有了三套procedure名字对应着三角。

重构后变为三层:
Layer1:underlying representation (real-part-rectangular, imag-part-rectangular, magnitude-rectangular, angle-rectangular 和 real-part-polar, imag-part-polar, magnitude-polar, angle-polar),名字加后缀。
Layer2:selector/constructor interface(real-part, imag-part, magnitude, angle),名字不变,这层就是通用层,called generic.这就是多态思想的源头。
Layer3:application interface (add-complex, sub-complex…),不用改动。

2)HOW - 重构underlying representations底层实现层。
Ben的重构,Ben选择的是rectangular form的实现,具体代码如下:
(define (real-part-rectangular z) (car z))

(define (imag-part-rectangular z) (cdr z))

(define (magnitude-rectangular z)
(sqrt (+ (square (real-part-rectangular z))
(square (imag-part-rectangular z)))))

(define (angle-rectangular z)
(atan (imag-part-rectangular z)
(real-part-rectangular z)))

(define (make-from-real-imag-rectangular x y)

(attach-tag 'rectangular (cons x y)))

(define (make-from-mag-ang-rectangular r a)
(attach-tag 'rectangular
(cons (* r (cos a)) (* r (sin a)))))
Alyssa的重构,Alyssa选择的是polar form的实现,具体代码如下:
(define (real-part-polar z)
(* (magnitude-polar z) (cos (cos (angle-polar z))))

(define (imag-part-polar z)
(* (magnitude-polar z) (sin (angle-polar z))))

(define (magnitude-polar z) (car z))

(define (angle-polar z) (cdr z))

(define (make-from-real-imag-polar x y)
(attach-tag 'polar
(cons (sqrt (+ (square x) (square y))) (atan y x))))

(define (make-from-mag-ang-polar r a)
(attach-tag 'polar (cons r a)))

3)HOW - 重构selectors/constructors interface层,本层相对于应用层不变,只需要改动body部分,这样一来,应用层就不需要改动。具体代码如下:
(define (real-part z)
(cond
((rectangular? z)
(real-part-rectangular (contents z)))
((polar? z)
(real-part-polar (contents z)))
(else (error “Unknown type – REAL-PART” z))))

(define (imag-part z)
(cond
((rectangular? z)
(imag-part-rectangular (contents z)))
((polar? z)
(imag-part-polar (contents z)))
(lse (error “Unknown type – IMAG-PART” z))))

(define (magnitude z)
(cond
((rectangular? z)
(magnitude-rectangular (contents z)))
((polar? z)
(magnitude-polar (contents z)))
(else (error “Unknown type – MAGNITUDE” z))))

(define (angle z)
(cond
((rectangular? z)
(angle-rectangular (contents z)))
((polar? z)
(angle-polar (contents z)))
(else (error “Unknown type – ANGLE” z))))

4)上面说了,应用层的procedure不用变,因为selectors/selector
interface层没有变,这种通用性就叫做:generic。
(define (add-complex z1 z2)
(make-from-real-imag
(+ (real-part z1) (real-part z2))
(+ (imag-part z1) (imag-part z2))))

5 )对于constructor的重构,选择哪种类型的具体实现层都可以,代码如下:
(define (make-from-real-imag x y)
(make-from-real-imag-rectangular x y))

(define (make-from-mag-ang r a)
(make-from-mag-ang-polar r a))

WHAT - 启发,每增加一个维度的逻辑,就可以通过增加一个新的层来实现。因为复杂的本质就是分别。
启发,宏观视角要有平等心,微观要有分别心。平等心让系统可以顺畅运转。分别心是构成复杂的基石。有时候要虚者看世界,有时候要等着眼睛看世界。

4. HOW - 第三版的complex number实现方案—Data-Directed #

— Chapter 2.4.3 Data-Directed Programming and Additivity
WHY - I need Data-Directed Programming 为什么我需要数据驱动?
因为,为了解决这样一个问题,即,如果类型过多,上一版的complex number代码维护就会很吃力,因为generic interface(selectors/constructors interface)成为了系统逻辑上的单点,每增加一个类型Data就去那里改动代码,因为那里负责了整个类型的分发dispatch。就像是分布式系统中的单点:负载均衡或者服务发现。
The general strategy of checking the type of a datum and calling an appropriate procedure is called dispatching on type.

总结一下,dispatching on type的方案,高度耦合。自上而下的控制,简单系统世界观。潜在的问题有:
1)每次增加新类型,改动的地方太多。维护成本过高。
2)新增类型,很可能procedure的名字会于以前冲突。因为,复杂新类型的人可能不知道procedure的名字是否被占用,除非去看完所有代码。这就是隐性风险。
这也是refer了全局变量带来的风险,所以为了遵从Black-box Abstraction尽量不要用refer.应该用Higher-Order Procedure,通过入参显性的声明要连接的外部Procedures。

总之,dispatching on type的方案,的这些特征我们抽象为一个概念叫做:not additive.非扩展性。

HOW - 如何解决?这就引出了Data-directed programming
1)约定大于配置,将对易变的规则逻辑,从代码分离出来,放到Data中,并且将规则从显而易见的代码描述,变成了一种隐性约定。因为在维护Data的时候,Data中不会写出规则逻辑。这些逻辑需要拼上代码中的解释部分才能完整。所以,维护Data的人,不用每次都看代码,只需要记住规则的约定即可。所以,Data的结构要简单。
2)对于结构化的规则逻辑,要用Tree或者Table来存储。更具有Power。
3)用Data来存储规则,规则可以支撑的很多,上百个类型Data都可以。这就是数据库的意义。数据库不仅存储业务数据,还能存储业务规则。

HOW - 用table来存储operations和类型的关系,如下图:
(img)

对于table的操作interface:
1)(put )
2)(get )

HOW - 重构underlying representations— 将procedures安装到table中
从Ben的角度,他要做的事情不需要考虑其他Data类型的开发者,他要做的只是安装自己的类型包:install-rectangular-package,将procedure安装到table中,table就是代码的中转站,代码和代码之间不再refer,耦合关系被抽离到了另一个维度(约定规则维度)
这也是package思想的起源。遵从Black-Box Abstraction思想。
具体代码如下:
(define (install-rectangular-package)
;; internal procedures
(define (real-part z) (car z))
(define (imag-part z) (cdr z))
(define (make-from-real-imag x y) (cons x y))
(define (magnitude z)
(sqrt (+ (square (real-part z))
(square (imag-part z)))))
(define (angle z)
(atan (imag-part z) (real-part z)))
(define (make-from-mag-ang r a)
(cons (* r (cos a)) (* r (sin a))))

;; interface to the rest of the system
(define (tag x) (attach-tag 'rectangular x))
(put 'real-part '(rectangular) real-part)
(put 'imag-part '(rectangular) imag-part)
(put 'magnitude '(rectangular) magnitude)
(put 'angle '(rectangular) angle)
(put 'make-from-real-imag 'rectangular
    (lambda (x y) (tag (make-from-real-imag x y))))
(put 'make-from-mag-ang 'rectangular
    )lambda (r a) (tag (make-from-mag-ang r a))))
'done)

同理,对于Alyssa’s polar package 具体实现如下:
(define install-polar-package)
;; internal procedures
(define (magnitude z) (car z))
(define (angle z) (cdr z))
(define (make-from-mag-ang r a) (cons r a))
(define (real-part z)
(* (magnitude z) (cos (angle z))))
(define (imag-part z)
(* (magnitude z) (sin (angle z))))
(define (make-from-real-imag x y)
(cons (sqrt (+ (square x) (square y)))
(atan y x)))

;; interface to the rest of the system
(define (tag x) (attach-tag 'polar x))
(put 'real-part '(polar) real-part)
(put 'imag-part '(polar) imag-part)
(put 'magnitude '(polar) magnitude)
(put 'angle '(polar) angle)
(put 'make-from-real-imag 'polar
    (lambda (x y) (tag (make-from-real-imag x y))))
(put 'make-from-mag-ang 'polar
    (lambda (r a) (tag (make-from-mag-ang r a))))
'done)

HOW - 重构generic interface
如何使用selector来构建selector/constructor interface呢?
答案是用一个解释器,通过去table中查询关系,来映射需求和结果。
我们给这个解释器起名字为apply-generic
apply-generic真正承载了规则的逻辑声明,但是却没有规则的数据,规则数据在table中,规则的运行描述在apply-generic中,这就是约定大于配置的源头。
使用table进行配置的人,只需要记住规则的约定即可,不用每次都来这里看规则的运行逻辑。
我们来看apply-generic的具体实现:
(define (apply-generic op args)
(let ((type-tags (map type-tag args)))
(let ((proc (get op type-tags)))
(if proc
(apply proc (map contents args))
(error
“No method for these types – APPLY-GENERIC”
(list op type-tags))))))
HOW-基于apply-generic我们来重构selectors/constructors interface,可以看成apply-generic是一道门,帮助selectors/constructors interface连接上了各种具体的underlying representations。具体代码如下:
(define (real-part z) (aply-generic 'real-part z))
(define (imag-part z) (apply-generic 'imag-part z))
(define (magnitude z) (apply-generic 'magnitude z))
(define (angle z) (apply-generic 'angle z))
对于constructors,代码如下:
(define (make-from-real-imag x y)
((get 'make-from-real-imag 'rectangular) x y))

(define (make-from-mag-ang r a)
((get 'make-from-mag-ang 'polar) r a))

HOW - 第四版的重构—Message Passing
WHY - 我为什么要设计Message Passing的模式呢?
因为,对于同一个需求,我们可以变换视角,上一版Data-directed是以type为dispatch的主角。这一版的Message Passing是以operation为dispatch的主角。
由于Procedure是first-class所以Data和Procedure是一回事。所以dispatch谁本质上都是在Dispatch Procedure。
变化视角,开拓思路,让我们更能看到Lisp世界中的本质。
如果真实需求是,operation很多,要经常变化,那么我们就要选择dispatch operations即Message Passing模式。
选择角度是自由的,根据具体的情况来选择,即,易变的是types还是operations
我们来看看如何dispatch operation。代码如下:
(define (make-from-real-imag x y)
(define (dispatch op)
(cond
((eq? op 'real-part) x)
((eq? op 'imag-part) y)
((eq? op 'magnitude)
(sqrt (+ (square x) (square y))))
((eq? op 'angle) (atan y x))
(else
(error “Unknown op – MAKE-FROM-REAL-IMAG” op))))
dispatch)
基于这个解释器,我们需要重构apply-generic:
(define (apply-generic op arg) (arg op))
当然,我们也可以用table来存储operations,等于是将上一个方案选择90度。
我们将传递的operation的名字,叫做message。
所以,这种模式称为message passing。
万物皆可流转,Data或者Procedure。
万物皆可流转,Type或者Operation。

Chapter 2.5 Systems with Generic Operations #

1.Why I design System with Generic Operations 我为什么要如此设计:将系统实现操作符的通用? #

因为,抽象思想,让用户获得统一视角,用相同的interface来使用所有类型的Data。用户就不用记住各种类型Data独特的interface。
因为,各种类型Data的interface的语义都是相同的,不过就是计算两个Data之间的add,sub,mul,div。那么我们就可以向上抽取出一层interface。
盖住下面的分别三种类型Data:Ordinary arithmetic, Rational arithmetic, Complex arithmetic.
用Type-directed的思想,加一个dispatcher,即,Generic arithmetic package.
设计思路如下图;
(img)
这样以来,数学系统整合了各种类型的Data,这就是一个可扩展系统:The system is additive.
这就是,通过抽象结构化的关联,得到一个概念体系。
每增加一层抽象,复杂指数(Power)级加倍。

2.HOW - 升级系统,在complex number系统基础上进行升级。 #

— Chapter 2.5.1 Generic Arithmetic Operations
因为,要得到通用的数学系统,我们升级complext number系统,增加一层interface,统一的来表达operations:add,sub,mul,div。
中间还是用apply-generic来帮忙做映射:dispatch,具体代码如下:
(define (add x y) (apply-generci 'add x y))
(define (sub x y) (apply-generic 'sub x y))
(define (mul x y) (apply-generic 'mul x y))
(define (div x y) (apply-generic 'div x y))

HOW - 将其他类型Data安装进来:install package
1)ordinary number的包安装,因为是lisp中的primitive 元素所以不需要再定义基本操作procedure,代码如下:
(define (install-scheme-number-package)
(define (tag x)
(attach-tag 'scheme-number x))
(put 'add ’(scheme-number scheme-number)
(lambda (x y) (tag (+ x y))))
(put ‘sub ’(scheme-number scheme-number)
(lambda (x y) (tag (- x y))))
(put ‘mul ’(scheme-number scheme-number)
(lambda (x y) (tag (* x y))))
(put ‘div ’(scheme-number scheme-number)
(lambda (x y) (tag (/ x y))))
(put ‘make 'scheme-number
(lambda (x) (tag x)))
'done)

;;how to use ordinary number
(define (make-scheme-number n)
((get 'make 'scheme-nuber) n))

2)rational number:实数的安装
(define (install-rational-package)
;; internal procedures
(define (number x) (car x))
(define (denom x) (cdr x))
(define (make-rat n d)
(let ((g (gcd n d)))
(cons (/ n g) (/ d g))))
(define (add-rat x y)
(make-rat (+ (* (numer x) (denom y))
(* (numer y) (denom x)))
(* (denom x) (denom y))))
(define (sub-rat x y)
(make-rat (- (* (numer x) (denom y))
(* (numer y) (denom x)))
(* (denom x) (denom y))))
(define (mul-rat x y)
(make-rat (* (numer x) (numer y))
(* (denom x) (denom y))))
(define (div-rat x y)
(make-rat (* (numer x) (denom y))
(* (denom x) (numer y))))

;; interface to rest of the system
(define (tag x) (attach-tag 'rational x))
(put 'add '(rational rational)
    (lambda (x y) (tag (add-rat x y))))
(put 'sub '(rational rational)
    (lambda (x y) (tag (sub-rat x y))))
(put 'mul '(rational rational)
    (lambda (x y) (tag (mul-rat x y))))
(put 'div '(rational rational)
    (lambda (x y) (tag (div-rat x y))))

(put 'mak 'rational
    (lambda (n d) (tag (make-rat n d))))
'done)

;;how to use rational number
(define (make-rational n d)
((get 'make 'rational) n d))
3)complex number:复数的安装,需要引用之前complex number系统定义好的rectangular and polar packages,所以需要两层Type tag。
类型的目的就分别。
(define (install-complex-package)
;; imported procedures from rectangular and polar packages
(define (make-from-real-imag x y)
((get 'make-from-real-imag 'rectangular) x y))
(define (make-from-mag-ang r a)
((get 'make-from-mag-ang 'polar) r a))

;; internal procedures
(define (add-complex z1 z2)
    (make-from-real-imag 
        (+ (real-part z1) (real-part z2))
        (+ (imag-part z1) (imag-part z2))))
(define (sub-complex z1 z2)
    (make-from-real-imag 
        (- (real-part z1) (real-part z2))
        (- (real-part z1) (imag-part z2))))
(define (mul-complex z1 z2)
    (make-from-mag-ang 
        (* (magnitude z1) (magnitude z2))
        (+ (angle z1) (angle z2))))
(define (div-complex z1 z2)
    (make-from-mag-ang 
        (/ (magnitude z1) (magnitude z2))
        (- (angle z1) (angle z2))))

;; interface to rest of the system
(define (tag z) (attach-tag 'complex z))
(put 'add '(complex complex)
    (lambda (z1 z2) (tag (add-complex z1 z2))))
(put 'sub '(complex complex)
    (lambda (z1 z2) (tag (sub-complex z1 z2))))
(put 'mul '(complex complex)
    (lambda (z1 z2) (tag (mul-complex z1 z2))))
(put 'div '(complex complex)
    (lambda (z1 z2) (tag (div-complex z1 z2))))
(put 'make-from-real-imag 'complex
    (lambda (x y) (tag (make-from-real-imag x y))))
(put 'make-from-mag-ang 'complex
    (lambda (r a) (tag (make-from-mag-ang r a))))
'done)

;;how to use complex number;这就是为什么要two-level tag sysytem
(define (make-complex-from-real-imag x y)
((get 'make-from-real-imag 'complex) x y))

(define (make-complex-from-mag-ang r a)
((get 'make-from-mag-ang 'complex) r a))

3.Why I need Combining Data of Different Type为什么我要让不同类型Data之间可以自由组合? #

— Chapter 2.5.2 Combining Data of Different Types
因为,为了获得更大的Power,当前的系统,计算只能发生在各个类型之内。
缺陷,无法实现跨类型cross types的计算,即,不同类型Data混在一起计算。
Why?为什么有这种需求,跨类型计算的意义是什么?
因为,真实世界就是任何类型的事物都可以发生关系,为了模拟真实世界,所以,在程序中也要让不同类型之间可以发生关系,即,跨类型的计算。

HOW - 实现思路是什么呢?
方案1)增加新类型,增加新的package,即,两种类型关联后的中间类型。
方案2)强制转换,例如:一个ordinary number和complex number进行计算,将ordinary number强制转换成complex number,就变成了complex number和complex number之间的计算。

HOW - 方案1的实现,本质上是又新增了一个维度/类型,只不过是安装在之前存在的complex package中。具体代码如下:
;; to be included in the complex package
(define (add-complex-to-schemenum z x)
(make-from-real-imag (+ (real-part z) x)
(imag-part z)))

(put 'add ’(complex scheme-number)
(lambda (z x) (tag (add-complex-to-schemenum z x))))

方案1的缺陷是,太重了,需要手工增加的代码太多。
这时候我们想到一个抽象的思路就是:约定大于配置。
定义一个规则,规则集中化,具体的实现不用手工声明。
这就是方案2了:Coercion强制转换。
即,让类型Data自动的按照约定规则,向上转换。
方案1是自上而下的设计,需要显示声明。(每个新情况都要手工写代码声明)
方案2是自下而上的构建,只需要遵守约定。(每对类型之间只需要一段代码来描述转换逻辑,而不用像方案1每个操作符都需要显性描述。)

HOW - 方案2的实现,将scheme number强制转换成complex number代码如下:
(define (scheme-number->complex n)
(make-complex-from-real-imag (contents n) 0))
因为强制转换是一个新的逻辑,概念与之前不同,所以概念不同就需要一个新的table来承载。代码如下:
(put-coercion ‘scheme-number 'complex scheme-number->complex)

对于apply-generic的具体实现如下:
(define (apply-generic op . args)
(let ((type-tags (map type-tag args)))
(let ((proc (get op type-tags)))
(if proc
(apply proc (map contents args))
(if (= (length args) 2)
(let
((type1 (car type-tags))
(type2 (cadr type-tags))
(a1 (car args))
(a2 (cadr args)))
(let
((t1->t2 (get-coercion type1 type2))
(t2-t1 (get-coercion type2 type1)))
(cond
(t1->t2
(apply-generic op (t1-t2 a1) a2))
(t2->t1
(apply-generic op a1 (t2->t1 a2)))
(else
(error “No method for these types” (list op type-tags))))))
(error “No method for these types” (list op type-tags)))))))

这里要注意,方案2的类型转换是有优先级的:
integer->rational->real->complex
1)在计算时候,我们的方向是让低级别的向上转,最后得到相同的类型,然后进行计算
2)在计算结束时候,我们的方向是让高级别的向下转,去掉多余的部分。
WHY - 什么是类型转换的本质原因?
因为,低级别的Data组成了高级别的Data。
换句话说,procedure是最低级的存在,procedure可以转换成anything。

WHAT - 类型转换给我带来的启发是什么?
1)人本质上就是Anything,所以人可以自由的转换类型。
2)人是由什么组成的,人是由意识组成的,所以说,概念对自己的角色的定义,就是改变了BEING,这就是转念的本质。
3)不论你如何转念都是对的,因为,世界的运转的源头就是人的念头,注意力,这就是自证预言的道理。
4)BEING->DOING->HAVING, 因为,只有BEING了才能有相应的DOING,如果角色不变,DOING是无法凭空存在的。改变自己的定位就是改变角色。例如站在Lisp发明人的位置来思考问题,就是凭空变了BEING,然后自然就有了如此思考的DOING,最后就会收获HAVING。其实任何DOING前面都有一个BEING。
所以,大多数人相信的顺序:DOING->HAVING->BEING其实真正前面还隐藏着一个BEING,即,BEING->DOING-HAVING->BEING。所以改变只能发生也最容易发生在BEING之中。也就是愿力,是生而为人的绝对自由,你想如何定位自己都是可以的。就像演员,想演谁都可以的,好的演员就是可以称为任何BEING,这就是生活的艺术。而大多数人的错误假设就是因为我们的自我定位无法改变,必须看到了我拥有了财富,才能相信定位自己为有钱人。错了,我们就应该先定位自己是一个有钱人,是一个财富自由了的人,BEING,然后再去做事情。
5)找到自己的定位,这是自由的源头,每个人都可以随意的改变。
6)假设自己站在发明人的位置,你自然就会那么想问题,如果不让自己站在那个位置,怎么也得不到那个想法。
7)这也符合分层思想。每一层都很简单,简单的积累就爆发复杂的Power。

4.Why I import Symbolic Algebra?为什么要引入符号代数? #

— Chapter 2.5.3 Example: Symbolic Algebra
因为,多项式是另一种数据类型,更加复杂。
多项式计算的规则是什么?
1)多项式中定位一个单变量(univariate polynomials)
2)剩下的就是项(terms)
3)terms中可能还有多项式(需要terms中计算时候的递归调用)

多项式的提出变量:
5x2 + 3x = 7

(y2 +1)x3 + (2y)x +1

HOW-如何设计多项式类型的Data?
Data命名为poly,包括variable和terms
selectors/constructors interface的设计如下:
1)make-poly
2)same-variable?
3)term-list
4)variable

应用interface层:
1)add-poly
2)mul-poly
具体代码如下:
(define (add-poly p1 p2)
(if (same-variable? (variable p1) (variable p2))
(make-poly (variable p1)
(add-terms (term-list p1)
(term-list p2)))
(error “Polys not in same var – ADD-POLY” (list p1 p2))))

(define (mul-poly p1 p2)
(if (same-variable? (variable p1) (variable p2))
(make-poly (variable p1)
(mul-terms (term-list p1) (term-list p2)))

将poly安装到之前的generic arithmetic system中,类型定义为polynomial
(define (install-polynomial-package)
;; internal procedures
;; representation of poly
(define (make-poly variable term-list)
(cons variable term-list))
(define (variable p) (car p))
(define (term-list p) (cdr p))

<procedures same-variable? and variable? from section 2.3.2>

;; representation of terms and term lists
<procedures adjoin-term ... coeff from text below>

(define (add-poly p1 p2) ...如上...)

(define (mul-poly p1 p2) ...如上...)

;; interface to rest of the system
(define (tag p) (attach-tag 'polynomial p))
(put 'add '(polynomial polynomial)
    (lambda (p1 p2) (tag (add-poly p1 p2))))
(put 'mul '(polynomial polynomial)
    (lambda (p1 p2) (tag (mul-poly p1 p2))))
(put 'make 'polynomial
    (lambda (var terms) (tag (make-poly var terms))))
'done)

因为,terms是一个集合,所以可以作为另一种Data来对待,所以terms也要有自己的selectors/constructors interface:
1)adjoin-term
2)first-term
3)rest-terms
4)the-empty-termlist
5)empty-termlist?

对于单个term,是一个pair,第一个位置代表order,第二个位置代表coeff
所以对于term的selectors/constructors interface 如下:
1)make-term
2)order
3)coeff

具体实现如下:
(define (adjoin-term term term-list)
(if (=zero? (coeff term))
term-list
(cons term term-list)))

(define (the-empty-termlist) ’())
(define (first-term term-list) (car term-list))
(define (rest-terms term-list) (cdr term-list))
(define (empty-termlist? term-list) (null? term-list))

(define (make-term order coeff) (list order coeff))
(define (order term) (car term))
(define (coeff term) (cadr term))

(define (make-polynomial var terms)
((get ‘make 'polynomial) var terms))

对于terms之间的计算的应用interface
1)add-terms
2)mul-terms
3)mul-term-by-all-terms
具体代码如下:
(define (add-terms L1 L2)
(cond
((empty-termlist? L1) L2)
((empty-termlist? L2) L1)
(else
(let ((t1 (first-term L1)) (t2 (first-term L2)))
(cond
((> (order t1) (order t2))
(adjoin-term
t1 (add-terms (rest-terms L1) L2)))
((< (order t1) (order t2))
(adjoin-term
t2 (add-terms L1 (rest-terms L2))))
(else
(adjoin-term
(make-term (order t1)
(add (coeff t1) (coeff t2)))
(add-terms (rest-terms L1)
(rest-terms L2)))))))))

(define (mul-terms L1 L2)
(if (empty-termlist? L1)
(the-empty-termlist)
(add-terms
(mul-term-by-all-terms (first-term L1) L2)
(mul-terms (rest-terms L1) L2))))

(define (mul-term-by-all-terms t1 L)
(if (empty-termlist? L)
(the-empty-termlist)
(let ((t2 (first-term L)))
(adjoin-term
(make-term (+ (order t1) (order t2))
(mul (coeff t1) (coeff t2)))
(mul-term-by-all-terms t1 (rest-terms L))))))

WHAT - 启发:
我们可以看到,任何有层级的地方,都可以抽象定义出一个data,例如term-list和term。哪怕简单到我们都以为可以直接用list来代替,但是不要,因为概念定位,即,概念上那个地方就是有个新概念,那么就不能用list来代替,就应该有一套自己的representation interface。

 
1
Kudos
 
1
Kudos

Now read this

Positioning SICP 1: Building Abstractions with Procedures

Positioning SICP 1: Building Abstractions with Procedures # 作者:何岩,recreating.org出品,谢绝转载。 阅读[SICP],站在Lisp发明者的上帝视角来思考:“我为什么要这么设计Lisp?” 0.前言 # 本系列为Recreating Lisp的准备阶段。因为,硬核知识要基于SICP,所以,我需要一边读SICP一边构建“纯”定位体系。 文字就像是线,串起来的珍珠就是SICP中的Lisp代码。 所以... Continue →