Positioning SICP 3: Modularity, Objects, and State

Positioning SICP 3: Modularity, Objects, and State #

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

0.前言:Why I design those concepts—Modularity, Objects, and State?为什么我要设计模块化,对象和状态这些概念? #

为了,更简单的模拟物理世界。
我们看世界的视角有两种:
1)将世界看成由彼此对立的物质对象组成,即,Object View
2)将世界看成彼此始终相互作用的信息流构成,即,Stream View
虽然真实的世界是Stream的。但是如果我们人脑用Stream View很难理解和模拟试卷,所以为了简化,降低人脑负荷,我们采用Object View来简化的模拟真实世界。

而,我们生下来被教育的也正是Object View,对象世界观,我们都习惯于此,误以为真实的世界就真的只是Object组成,却不知道,还有另一种看世界的方式Stream View,而,佛经里的世界观就是Stream View,所以很难理解,但是又那么自洽。

我们用代码来对比一下Object view和Stream View,给自己找点感觉先:
有这样一个需求:银行账户account,取款withdraw,显示余额balance。
1)Bank Account with Object View
(define balanc 100)

(define (withdraw amount)
(if (>= balance amount)
(begin (set! balanc (- balance amount))
balance)
“Insufficient funds”))
2)Bank Account with Stream View
(define (stream-withdraw balance amount-stream)
(cons-stream
balance
(stream-withdraw
(- balace (stream-car amount-stream))
(stream-cdr amount-stream))))
我只是想让你感官体验一下,对于同样一个需求,两种世界观的编码不同,后面我们会再解释这个例子。
这两种视角不分好坏,而是,要根据具体的场景来选择。

WHAT - Object View的关键点是有状态
1)Object View和Stream View的本质不同就在于是否“有状态”,Object View认为任何对象都有自己的状态,而状态也是对象的本质。状态是可以静止的存在于时间之中的。只有当其他对象发来干涉,状态才被改变。改变后的状态在时间这个载体上可以继续维持(静态保持),所以,在Object View来看,对象是大多数时间不动的。
2)而Stream View不这么看世界,Stream View认为这个世界不存在时间,也就不存在静止不动的所谓的“状态”,世界里的所有事物都是一刻不停的在运动中,哪怕那些看上去没有变化的石头,在量子视角来看,每个组成石头的最小单元都在随机的量子态运动。所以,时间并不存在,时间概念只是对于世界中的事物运行的虚幻感知,是一种interface。我们抓不住时间,也看不见时间,我们只能用钟表的机械运转(变化)来模拟一种根本不存在的“时间”。所以,真实的世界一直都只有一刻,就是现在,没有过去也没有未来,只有一刻,在这一刻上,世界在运行,就好比一个人站在此刻站在刀锋上,一直在做着动作,却没有失去平衡掉下来,世界就是一个站在刀锋上平衡运行的人。

3)Object View中对于“有状态”的另一层意思是,对象之间是独立的,只有相互发消息的时候才会改变彼此的状态。
4)而Stream View认为时间万物都是相互作用的,而且是持续的在互相作用中,这种复杂程度根本无法用计算机建模,也就无法完全客观模拟。所以才无法被人理解,也不适合工程化。但是,我们需要模拟,只能降低清晰度,模糊的构建事物之间的关系。
而,能够感知到时间万物都在相互效力的设备就是我们的心。

5)在Object View中,为了保存状态,我们需要创造一个存储空间,这就是environment。衍生出来的概念体系是:environment model.

6)人们的焦虑/兴奋本质上只不过是为了增加/保护那个虚幻的“状态”STATE。财富是个State,地位是个State,总之可以用一个数字简单代表的就是State,而这个评估因为来自于他人,所以我们才那么在乎他人,因为如果让他人讨厌,我们的state就会很低,我们的自我存在感就会很低。这就是受制于人。
7)但是有些智者活在Stream View世界观下,他们不在乎已经取得的成绩,他们更关注每一天的做事的行为,有没有比之前更好,即关注改变。改变就是掌控感,从掌控感中直接获得快感和自我意义,这就不再受制于人,而只取决于自己的评估,即,面对当下情况,老我如何做,新我如何做,改变就这么被感知到了,成就感随即产生。
所以说,Object View下的人关注存量(数值的多少),Stream View下的人关注改变(变化本身,增量,节奏,信息),这也是有限和无限游戏的本质不同。
所以,想要转变成Stream View世界观,信息是个关键词。
关注获得的信息,而不要关注身外之物。

Chapter 3.1 Assignment and Local State #

1)为了,可以操控State,需要我们设计一种新概念,即,Assignment赋值。
前两章,变量不可以被修改,只能创建变量。所有procedure都是无状态的,即,只要参数不变,不管调用多少次,结果也不变。

2)所以,真实的世界本不存在状态,真实世界是都是由无状态的procedure叠加构成,我们看到的每一个“色”的背后都叠加了我们无法感知到的超级复杂的procedure相互效力。所以,真实世界是一个无法想象数量级的并发系统。

3)因为计算机系统资源有限,无法处理过多的的并发,只能简单的模拟“色”而不能模拟产生色的所有叠加因素procedure。

4)而,bitcoin的utxo模型则是stream view下的产物,交易永远在叠加,交易不可改,每笔交易都是无状态的。所以,bitcoin更加符合宇宙模型。

5)关于状态,是在Object之中的,所以叫做Local State

6)为了表示对State的操作,我们增加一个操作符:SET!

1.为了解释得更清楚,我们用银行取款的例子来讲解。 #

— Chapter 3.1.1 Local State Variables
假设我们初始余额为100,我们希望的效果是,对于interface withdraw 每次调用之后,显示的余额都发生变化。
(withdraw 25)
75

(withdraw 25)
50

(withdraw 60)
“Insufficient funds”

(withdraw 15)
35
同样的参数,同样的procedure,每次调用后结果不同,这就是和前两章不同所在。
HOW - withdraw的实现代码如下:
(define balance 100)

(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
“Insufficient funds”))

;;Decrementing balance is accomplished by the expression
(set! balance (- balance amount))

;;This uses the set! special form, whose syntax is
(set! )
Here is a symbol and is any expression. Set! changes so that its value is the result obtained by evaluating .
SET!的本质就是其他语言中的等号“=”,可以看出“=”只是一个语法糖。从SICP里我们可以看到各种语法糖背后的本质,即,获得物理视角。

Why - I need “begin”?
因为,begin类似一个时间锁,在begin这个block中,时间是单线流转的。
(begin … )
the value of the final expression to be returned as the value of the entire begin form.

HOW - 升级withdraw为具有Local State,为了让balance变成私有,不被其他procedure引用。
(define new-withdraw
(let ((balance 100))
(lambda (amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
“Insufficient funds”))))
这就是一个闭包。

HOW - 升级为一个完整的账户操作模块
(define (make-account balance)
(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
“Insufficient funds”))

(define (deposit amount)
    (set! balance (+ balance amount))
    balance)

(define (dispatch m)
    (cond 
        ((eq? m 'withdraw) withdraw)
        ((eq? m 'deposit) deposit)
        (else (error "Unknown request -- MAKE-ACCOUNT" 
            m)))

dispatch)   ;;return interface procedure dispatch

这就是一个Object,dispatch就是interface,唯一入口,因为procedure只能返回一个procedure,所以只能返回dispatch作为分发入口。这里用到的概念是消息驱动message-passing style.
下面是演示如何使用make-account
(define acc (make-account 100))

((acc ‘withdraw) 50)
50

((acc 'withdraw) 60)
“Insufficient funds”

((acc 'deposit) 40)
90

((acc 'withdraw) 60)
30

;; another call to make-account
(define acc2 (make-account 100))
;; will produce a completely separate account object, which maintains its own local balance.
;; Why? 因为会分配独立的frame

Exercise 3.1 实现make-accumulator,累加
设计效果如下:
(define A (make-accumulator 5))

(A 10)
15

(A 10)
25
代码实现如下:
(define (make-accumulator x)
(let ((localvale x))
(lambda (y)
(begin
(set! localvalue (+ localvalue y))
localvalue))))

Exercise 3.2 实现make-monitored,监控f的执行次数
为了测试procedure,我们要实现一个第三方的procedure:make-monitored。
每次procedure执行一次,计数器counter就加一。
我们使用消息驱动,如果消息是how-many-calls?则返回计数器的值
如果消息是reset-count则重置counter为0.
其他消息则返回procedure的值。
设计效果如下:
(define s (make-monitored sqrt))

(s 100)
10

(s 'how-many-calls?)
1

实现思路如下:
1)如何感知到procedure的执行?换句话说如果是一个递归procedure 如何计数?
2)或者我理解错了,计数不是要记录procedure的递归次数,而是make-monitored被调用的次数。这样是可以通过不侵入procedure的代码来实现计数。
具体代码实现如下:
(define (make-monitored f)
(let ((count 0))
(define (how-many-calls?)
(count))

    (define (reset-count)
        (set! count 0))

    (define (other)
        (set! count (+ 1 count))
        f)

    (define (dispatch m)
        (cond
            ((eq? m 'how-many-calls?)
                how-may-calls?)
            ((eq? m 'reset-count)
                reset-count)
            (else
                other)))
    dispatch))

Exercise 3.3 实现账户密码功能
效果如下:
(define acc (make-account 100 'secret-password))

((acc 'secret-password 'withdraw) 40)
60

((acc 'some-other-password 'deposit) 50)
“Incorrect password”

具体实现代码如下:
(define (make-account balance password)
(define (withdraw amount)
(if (<= amount balance)
(begin
(SET! balance (- balance x)
balance)
(else
(“Insufficient funds”)))

(define (deposit amount)
    (SET! balance (+ balance amount)))

(define (dispatch pwd m)
    (cond
        ((eq? pwd password) 
            (cond 
                ((eq? m 'withdraw) withdraw)
                ((eq? m 'deposit) deposit)
                (else (error "Unknow request -- MAKE-ACCOUNT" m))))         
        (else "Incorrect password")))
dispatch)    

Exercise 3.4 升级上一个例子,加入一个入参call-the-cops,即,如果密码错误超过7次则调用call-the-cops的procedure
代码如下:
(define (make-account balance password call-the-cops)
(let ((incorrectTimes 0))
(define (withdraw amount)
(if (<= amount balance)
(begin
(SET! balance (- balance x)
balance)
(else
(“Insufficient funds”)))

    (define (deposit amount)
        (SET! balance (+ balance amount)))

    (define (dispatch pwd m)
        (cond
            ((eq? pwd password) 
                (cond 
                    ((eq? m 'withdraw) withdraw)
                    ((eq? m 'deposit) deposit)
                    (else (error "Unknow request -- MAKE-ACCOUNT" m))))         
            (else 
                (begin 
                    (SET! incorrectTimes (+ 1 incorrectTimes)
                    (error "Incorrect password, incorrectTimes now is:" incorrectTimes)))))
    dispatch))  

2.What is the benefits of Introducing Assignment? 赋值语句的好处是什么? #

— Chapter 3.1.2 The Benefits of Introducing Assignment
Assignment can create random number。
这个是pure procedure搞不定的事情。
WHAT - 随机数的本质是什么?
不可重复性。
而pure procedure是可重复的。

3.What is the costs of Introducing Assignment?引入赋值语句的成本是什么? #

— Chapter 3.1.3 The Costs of Introducing Assignment
assignment的引入,procedure不再等价于数学函数mathematical functions
前两章,没有assignment之前,所有的procedure都符合面向函数编程:functional programming
我们来对比一下,这两种编程方式的不同效果:
1)assignment:相同的参数,不同的结果
(define (make-simplified-withdraw balance)
(lambda (amount)
(set! balance (- balance amount))
balance))

(define W (make-simplified-withdraw 25))

(W 20)
5

(W 10)
-5

2)not use set! : 相同的参数,相同的结果
(define (make-decrementer balance)
(lambda (amount)
(- balance amount)))

(define D (make-decrementer 25))

(D 20)
5

(D 20)
5

(D 10)
15

我们用substitution model 来解释make-decrementer的运行
((make-decrementer 25) 20)

((lambda (amount) (- 25 amount)) 20)

(- 25 20)

5

如果我们用类似的substitution来分析make-simplified-withdraw
((make-simplified-withdraw 25) 20)

((lambda (amount) (set! balance (- 25 amount)) 25) 20)

(set! balance (-25 20)) 25

25

WHY - I need Environment model
如果用类似的substitution model则返回了错误答案25.
因为,在set!作用之前,balance的值就已经被替换了。正确的做法应该是等SET!之后再替换balance的值。
所以,引入了SET!的procedure就不再可以使用substitution model
而要使用一种新的model,即,Environment model.
SET!的关键点,就是需要有一个地方来存储状态STATE,并且执行SET!之后可以替换。Enviroment就是这个空间。

Exercise 3.7 实现make-joint
效果如下:
(define paul-acc
(make-joint peter-acc 'open-sesame 'rosebud))
实现思路:
方案1:
1)refer make-account
2)改造make-account用一个list来存储多个密码
方案2:
1)不用refer make-account
2)只是在make-joint中用Higer-Order插入一层映射层,将pwd1和pwd2做映射,如果pwd2等于之前的pwd2则映射到pwd1在调用acc1,剩下的事全部有acc1去做,这样的好处是不侵入代码,只是增加一个代理层来解决。

(define (make-joint acc1 pwd1 pwd2)
(lambda (password m) ;;lambda的意义:代表返回的procedure接受参数
(cond
((eq? m pwd2)
(acc1 pwd1 m))
(else
“Incorrect the second password”))

这里可以看到Higher-Order的意义,加入一个lambda作为代理procedure来接受本来应该返回的procedure相同的参数。这样,使用者就以为自己在使用本应该返回的procedure。其实只是外面包裹了一层逻辑的匿名procedure。

Chapter3.2 The Environment Model of Evaluation #

1.Why I design The Environment Model of Evaluation?为什么我要设计环境求值模型? #

因为,SET!让过去的Substitution Model不再适用,所以,需要开辟一个新Model。
WHAT - Model从抽象上来看是什么?
规则,世界运转的规律,选择不同的视角来理解世界,就是选择不同的规则。
在一定范围内,两种规则都适用,都是自洽体系。甚至不那么准确的规则更实用。

两种规则对于applying的描述对比:what is meant by applying a procedure to arguments
1)Substitution model of evaluation: To apply a compound procedure to arguments, evaluate the body of the procedure with each formal parameter replaced by the corresponding argument.

2)Environment model of evaluation: In the presence of assignment, a variable can no longer be considered to be merely a name for a value. Rather, a variable must somehow designate a “place” in which values can be stored. In our new model of evaluation, these places will be maintained in structures called environments.

本质的区别在于:变量变得不同,前者是a name for a value,后者是designate a “place” in which values can be stored.

2.What is the Rules for Evaluation #

— Chapter 3.2.1 The Rules for Evaluation
Substitution model
1)Evaluate the subexpressions of the combination.
2)Apply the value of the operator subexpression to the values of the operand subexpressions
3)To apply a compound procedure to arguments, evaluate the body of the procedure with each formal parameter replaced by the corresponding argument.

Environment model
1)A procedure object is applied to a set of arguments by constructing a frame, binding the formal parameters of the procedure to the arguments of the call, and then evaluating the body of the procedure in the context of the new environment constructed. The new frame has as its enclosing environment the environment part of the procedure object being applied.
2)A procedure is created by evaluating a lambda expression relative to a given environment. The resulting procedure object is a pair consisting of the text of the lambda expression and a pointer to the environment in which the procedure was created.

两者本质的区别在于,apply的时候,前者是replace,后者是bind。即,to apply a procedure to argument, create a new environment containing a frame that binds the parameters to the values of the arguments.

HOW - 用square来示意
(define (square x) (* x x))

;;等价于

(define square
(lambda (x) (* x x)))

1)A procedure is created如下图:shows the result of evaluating this define expression.
(img)
2)A procedure object is applied如下图:shows the procedure be applied: (square 5)
(img)
Define就像是模型,Apply就像是实例化

3.Applying Simple Procedures #

— Chapter 3.2.2 Applying Simple Procedures

为了实践Environment Model,我们定义如下procedures
(define (square x)
(* x x))

(define (sum-of-squares x y)
(+ (square x) (square y)))

(define (f a)
(sum-of-squares (+ a 1) (* a 2)))

下图为Procedures be created
(img)
下图为Procedure objects be applied:(f 5)
(img)

Exercise 3.9 : 用Environment Model来解释factorial procedure
;; 1.recursive version
(define (factorial n)
(if (= n 1)
1
(* n (factorial (- n 1)))))

思路:
首先来看recursive version
1)Procedure create,parameters:n,body:(if (= n 1)…(factorial…)
2)Procedure create,global environment中将factorial bind到procedure object
3)Procedure object be applied,(factorial 6),创建一个E1,n:6,evaluate body:
(if (= 6 1)
1
(* 6 (factorial (- 6 1)))))

(* 6 (factorial 5))))

(* 6 (body… 5))))
4)Procedure object be applied,(factorial 5),创建E2,n:5,evaluate body
(* 6 (* 5 (body… 4)))))

直到
(* 6 (* 5 (* 2 1)))))

再来看interactive version
;; 2.iteractive version
(define (factorial n)
(fact-iter 1 1 n))

(define (fact-iter product counter max-count)
(if (> counter max-count)
product
(fact-iter
(* counter product)
(+ counter 1)
max-count)))
1)Procedure create,env bind factorial和fact-iter
2)Procedure object applied,E1 中n:6 , (fact-iter 1 1 6)
3)Procedure object applied,E2中product:1, counter:1, max-count:6,
(fact-iter (* 1 1) (+ 1 1) 6)

(fact-iter 1 2 6)
4)Procedure object applied,E3中product:1, counter:2, max-count:6
(fact-iter (* 1 2) (+ 2 1) 6)

(fact-iter 2 3 6)
…直到 counter > max-count

4.HOW - 如何用frames来存储Local State #

Environment Model就是为了处理Local State而创造的,所以之前我们演示了简单的部分,即,本来可以用Substitution Model搞定的simply procedure,下面我们进入真正的主角,处理拥有assignment的procedure

(define (make-withdraw balance)
(lambda (amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
“Insufficient funds”)))

(define W1 (make-withdraw 100))

(W1 50)
50

1)Result of defining make-withdraw in the global environment
(img)

2)Result of evaluating (define W1 (make-withdraw 100))
We see that E1 serves as the “place” that holds the local state variable for the procedure object W1
(img)

3) Environment created by applying the procedure object W1
(img)

4)Environments after the call to W1
When the set! is executed, the binding of balance in E1 is changed.
本质上,拥有SET!和不用SET!在构建frame层面没有变化,唯一变化的点只是SET!可以去改之前frame中的值。而没有SET!的只有创建新的E里面再绑定新的value。
(img)

5)Using (define W2 (make-withdraw 100)) to create a second object.
(img)

Exercise 3.10 如果用了let,模型会如何构建
答案是多了一层E,因为let的本质就是多包了一层lambda
(define (make-withdraw initial-amount)
(let ((balance initial-amount))
(lambda (amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
“Insufficient funds”))))

(let (( )) )
;;is interpreted as an alternate syntax for
((lambda () ) )

(define (make-withdraw initial-amount)
(lambda (balance)
(lambda (amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))))
) (initial-amount)

多了一层E,如下图:
(img)

5. Why I need Internal Definitions? 我为什么需要内部定义? #

— Chapter 3.2.4 Internal Definitions
因为,internal Definitions更加模块化,不污染global environment
(define (sqrt x)
(define (good-enough? guess)
(< (abs (- (square guess) x)) 0.001))
(define (improve guess)
(average guess (/ x guess)))
(define (sqrt-iter guess)
(if (good-enough? guess)
guess
(sqrt-iter (improve guess))))
(sqrt-iter 1.0))

(img)

Internal Definitions的优点:
1)The names of the local procedures do not interfere with names external to the enclosing procedure, because the local procedure names will be bound in the frame that the procedure creates when it is run, rather than being bound in the global environment. (不运行则指存在于Procedure Object的body部分中)
2)The local procedures can access the arguments of the enclosing procedure, simply by using parameter names as free variables. This is because the body of the local procedure is evaluated in an environment that is subordinate to the evaluation environment for the enclosing procedure.

Chapter 3.3 Modeling with Mutable Data #

1.Why I import this concept that Modeling with Mutable Data? #

因为,要用Mutable Data来代表State。
又因为,State需要修改,所以Data就需要变成Mutable Data。
而,之前的Data都只有constructor和selector无法修改,如果变化只有创建。

WHY - 为什么需要Mutable Data来代表状态?
因为,模块化的思想,简单的Data无法足够好的模拟真实的复杂对象的状态。
也就是说,真实世界的对象的状态很复杂,需要用复杂的Data来模拟,又因为State的本质就是要可修改SET!,所以Data要称为Mutable Data。

例如:对于如下复杂数据的修改:
1)List ,通过set-car! 和 set-cdr!进行修改
2)Queue,通过insert-queue! 和 delete-queue!进行修改
3)Table,通过insert!进行修改

这些修改操作符就叫做mutators
这些Data objects就叫做mutable data objects

2.HOW - 如何让List变成可修改呢? #

— Chapter 3.3.1 Mutable List Structure
通过set-car!和set-cdr!
set-car!和set-cdr!的本质就是操纵Pair中的指针Pointer
数据早已存在,改变指针其实是很小的动作。
过去不用的数据,会有GC回收。
类比真实世界,转念就是改变指针。
真正改变命运不需要我们费劲的去构建数据,而只需要轻松的改变指针,将底层假设一变,我们自己的世界就变化了。
影响我们的不是世界,而是我们对世界的看法。
而,传统的观念是,改变的本质是改变数据,或者创造新的数据。这样就很费劲。
其实好的数据已经存在,只需要我们免费的改变指针指向它们。

这本质上需要对Data和Pointer的理解更加深入。
Pointer代表了关系。
Data代表了虚拟的实体。
真正起到决定作用的反而是看起来更轻的关系Pointer。

WHAT - Mutation is just assignment
对比 Data is just Procedure
Mutation的本质就是SET!

之前对于Pair的实现,如下:
(define (cons x y)
(define (dispatch m)
(cond
((eq? m 'car) x)
((eq? m 'cdr) y)
(else (error "Undefined operation -- CONS" m))))
dispatch)

(define (car z) (z 'car))

(define (cdr z) (z 'cdr))

改造后的Pair可以实现修改,如下:
(define (cons x y)
(define (set-x! v) (set! x v))
(define (set-y! v) (set! y v))
(define (dispatch m)
(cond
((eq? m 'car) x)
((eq? m 'cdr) y)
((eq? m 'set-car!) set-x!)
((eq? m 'set-cdr!) set-y!)
(else (error "Undefined operation -- CONS" m))))
dispatch)

(define (car z) (z 'car))

(define (cdr z) (z 'cdr))

(define (set-car! z new-value)
((z 'set-car!) new-value)
z)

(define (set-cdr! z new-value)
((z 'set-cdr!) new-value)
z)

3.HOW - 如何构建Queues #

Queue的设计效果如下:
Operation Resulting Queue

(define q (make-queue))
(insert-queue! q 'a) a
(insert-queue! q 'b) a b
(delete-queue! q) b
(insert-queue! q 'c) b c
(insert-queue! q 'd) b c d
(delete-queue! q) c d

Queue的设计如下:
;; a consturctor:
(make-queue)

;; two selectors:
(empty-queue? )

(front-queue )

;; two mutators:
(insert-queue! )

(delete-queue! )

Queue的设计图,如下:
(img)

WHAT - QUEUE的设计思想符合分层思想,通过增加一层控制层来实现效率的增加。如果用少一层的list来实现也可以,不过效率会变低,复杂度微O(n),而增加一层控制层,复杂度就变成了O(1)

HOW - 启发是什么?
资源都在,只需要重新设计一下,组织一下指针,神奇的事情就会发生。
这就是定位的本质。
真正的控局者是指针控制者,或者称为指针设计者。

QUEUE的具体实现代码如下:
;; 控制层pointer
(define (front-ptr queue) (car queue))

(define (rear-ptr queue) (cdr queue))

(define (set-front-ptr! queue item) (set-car! queue item))

(define (set-rear-ptr! queue item) (set-cdr! queue item))

;; 对body的控制
(define (empty-queue? queue) (null? (front-ptr queue)))

(define (make-queue) (cons '() '()))

(define (front-queue queue)
(if (empty-queue? queue)
(error "FRONT called with an empty queue" queue)
(car (front-ptr queue))))

(define (insert-queue! queue item)
(let ((new-pair (cons item '())))
(cond
((empty-queue? queue)
(set-front-ptr! queue new-pair)
(set-rear-ptr! queue new-pair)
queue)
(else
(set-cdr! (rear-ptr queue) new-pair)
(set-rear-ptr! queue new-pair)
queue))

(define (delete-queue! queue)
(cond
((empty-queue? queue)
(error "DELETE! called with an empty queue" queue))
(else
(set-front-ptr! queue (cdr (front-ptr queue)))
queue)))

4.HOW - 如何构建Table #

one-dimensional table单维表
a:1
b:2
c:3
设计图如下:
(img)

代码实现如下:
(define (lookup key table)
(let ((record (assoc key (cdr table))))
(if record
(cdr record)
false)))

(define (assoc key records)
(cond
((null? records) false)
((equal? key (caar records)) (car records))
(else (assoc key (cdr records)))))

(define (insert! key value table)
(let ((record (assoc key (cdr table))))
(if record
(set-cdr! record value)
(set-cdr! table
(cons (cons key value) (cdr table)))))
'ok)

(define (make-table)
(list 'table))

Two-dimensional tables 二维表
math:
+: 43
-: 45
*: 42
letters:
a: 97
b:98
设计图如下:
(img)

我们可以看到通过Pair的自由组合,可以构建任意的复杂Data
本质上,自由组合基于Pair的粘性,即,最小可组合性。
具体代码实现:
(define (lookup key-1 key-2 table)
(let ((subtable (assoc key-1 (cdr table))))
(if subtable
(let ((record (assoc key-2 (cdr subtable))))
(if record
(cdr record)
false))
false)))

(define (insert! key-1 key-2 value table)
(let ((subtable (assoc key-1 (cdr table))))
(if subtable
(let ((record (assoc key-2 (cdr subtable))))
(if record
(set-cdr! record value)
(set-cdr! subtable
(cons (cons key-2 value)
(cdr subtable)))))
(set-cdr! table
(cons (list key-1 (cons key-2 value))
(cdr table)))))
'ok)

HOW - creating local tables
(define (make-table)
(let ((local-table (list 'table)))
(define (lookup key-1 key-2)
(let ((subtable (assoc key-1 (cdr local-table))))
(if subtable
(let ((record (assoc key-2 (cdr subtable))))
(if record
(cdr record)
false))
false)))
(define (insert! key-1 key-2 value)
(let ((subtable (assoc key-1 (cdr local-table))))
(if subtable
(let ((record (assoc key-2 (cdr subtable))))
(if record
(set-cdr! record value)
(set-cdr! subtable
(cons (cons key-2 value)
(cdr subtable)))))
(set-cdr! local-table
(cons (list key-1
(cons key-2 value))
(cdr local-table)))))
'ok)

    (define (dispatch m)
        (cond
            ((eq? m 'lookup-proc) lookup)
            ((eq? m 'insert-proc!) insert!)
            (else (error "Unknown operation -- TABLE" m))))
    dispatch))

;; how to use it
(define operation-table (make-table))
(define get (operation-table 'lookup-proc))
(define put (operation-talbe 'insert-proc!))

5.HOW - 如何构建电子器件Digital Circuits #

— Chapter 3.3.4 A Simulator for Digital Circuits

1.What is the purpose of this case? #

1)模拟真实,用代码来模拟真实世界的对象
2)这次要模拟的真实系统就是电子器件系统
3)电子元件包括:电线wire,电子器件digital function box:inverter/and-gate/or-gate
4)电子器件可以组合,通过电线实现组合,所以我们也要用代码来实现wire的组合能力。
5)这是一个非常精髓的case,演示了如何用lisp来模拟真实系统。可以从这里找到很多思想的源头。包括:event-driven事件驱动、delay、call back

2.我们先来看一下电子器件和组合起来的抽象器件半加法器 #

primitive element : Inverter、And-gate、Or-gate
如下图:
(img)
通过wire来实现组合,通过define来抽象为一个新的电器单元。如下图;
(img)
高级组合实现的抽象:full-adder circuit,如下图:
(img)

3.设计效果 #

;; wires
(define a (make-wire))
(define b (make-wire))
(define c (make-wire))
(define d (make-wire))
(define e (make-wire))
(define s (make-wire))

;; function box
(or-gate a b d)
ok

(and-gate a b c)
ok

(inverter c e)
ok

(and-gate d e s)
ok

;; abstraction 借助lisp的内嵌能力:组合和抽象
(define (half-adder a b s c)
(let ((d (make-wire)) (e (make-wire)))
(or-gate a b d)
(and-gate a b c)
(inverter c e)
(and-gate d e s)
'ok))

;; 高级抽象
(define (full-adder a b c-in sum c-out)
(let ((s (make-wire))
(c1 (make-wire))
(c2 (make-wire)))
(half-adder b c-in s c1)
(half-adder a s sum c2)
(or-gate c1 c2 c-out)
'ok))

4.HOW - Primitive function boxes 的实现 #

1)用构建新语言的思路来模拟电器系统。
2)WHY?因为,真实世界的领域系统本质上就是一套规则,只要是规则本质上就可以用语言来解释,也就可以抽象为语言framework:primitive/combination/abstraction
3)我们可以看到很多编程思想的源头:异步、call/back、订阅模式
4)wire相当于消息总线,复杂消息的set和get还有notify和注册,由于notify是wire的内部逻辑,所以不用再interface中,interface就三个足够:
get-signal、set-signal!、add-action!
add-action!实现的是消息的订阅功能,即,电子器件将自己注册到wire中订阅wire的消息变化(wire的notify)
set-signal!消息变化后,会主动遍历之前注册的电子器件,并notify。
5)HOW -启发,真实工作中,一般系统只有一个消息总线,任何一个变化都全局notify,而这个case中,给我的启发是,可以将消息总线分段,形成局部消息共享系统,消息子线。或者一个消息总线,虚拟出来多个消息子线。
(get-signal )

(set-signal! )

(add-action! )

1)inverter的实现:
(define (inverter input output)
(define (invert-input)
(let ((new-value (logical-not (getsignal input))))
(after-delay inverter-delay ;;异步的源头
(lambda() ;; call back的源头
(set-signal! output new-value)))))
(add-action! input invert-input) ;;订阅模式的源头
'ok)

(define (logical-not s)
(cond
((= s 0) 1)
((= s 1) 0)
(else (error "Invalid signal" s))))

2)And-gate的实现
(define (and-gate a1 a2 output)
(define (and-action-procedure)
(let ((new-value
(logical-and (get-signal a1) (get-signal a2))))
(after-delay and-gate-delay
(lambda ()
(set-signal! output new-value)))))

(add-action! a1 and-action-procedure)
(add-action! a2 and-action-procedure)
'ok)

3)Or-gate的实现
(define (or-gate a1 a2 output)
(define (or-action-procedure)
(let ((new-value
(logical-or (get-signal a1) (get-signal a2))))
(after-delay or-gate-delay
(lambda ()
(set-signal! output new-value)))))

(add-action! a1 or-action-procedure)
(add-action! a2 or-action-procedure)
'ok)

4)Or-gate使用inverter和And-gate来实现
(define (or-gate input-1 input-2 output)
(let ((invert-1 (make-wire))
(invert-2 (make-wire))
(and-invert-1-invert-2 (make-wire)))
(inverter input-1 invert-1)
(inverter input-2 invert-2)
(and-gate invert-1 invert-2 and-invert-1-invert-2)
(inverter and-invert-1-invert-2 output))
'ok)

5.HOW - Wires的实现 #

1)Wire需要的保持的局部状态变量有两个:1是signal-value,2是action-procedures
(define (make-wire)
(let ((signal-value 0) (action-procedures '()))
(define (set-my-signal! new-value)
(if (not (= signal-value new-value)
(begin (set! signal-value new-value)
(call-each action-procedures));notify
'done))

    (define (accept-action-procedure! proc)
        (set! action-procdures (cons proc action-procedures))
        (proc));注册listener

    (define (dispatch m)
        (cond
            ((eq? m 'get-signal) signal-value)
            ((eq? m 'set-signal!) set-my-signal!)
            ((eq? m 'add-action!) accept-action-procedure!)
            (else (error "Unknown operation -- WIRE" m))))

    dispatch))

(define (call-each procedures)
(if (null? procedures)
'done
(begin
((car procedures));;execute
(call-each (cdr procedures)))))

2)语法糖的意义,换视角,将procedure看成data,其实procedure和data是一回事。
(define (get-signal wire)
(wire 'get-signal))

(define (set-signal! wire new-value)
((wire 'set-signal!) new-value))

(define (add-action! wire action-procedure)
((wire 'add-action!) action-procedure))

6.HOW - Agenda的实现,即,Delay效果的实现 #

1)为什么需要delay?因为真实世界的电器元件有delay,为了模拟真实所以需要用代码来构建delay
2)delay的本质就是异步,这就异步的源头。
3)设计思路是用一个全局变量queue来存储所有待执行的procedure,然后有一个全局procedure来遍历这个queue并依次执行。

1)agenda的本质就是一个存储procedure的queue,interface设计如下:
(make-agenda)

(empty-agenda? )

(first-agenda-item )

(remove-first-agenda-item! )

(add-to-agenda!

(current-time )

2)after-delay的实现
(define (after-delay delay action)
(add-to-agenda! (+ delay (current-time the-agenda))
action
the-agenda))

3)执行引擎propagate的实现
(define (propagate)
(if (empty-agenda? the-agenda)
'done
(let ((first-item (first-agenda-item the-agenda)))
(first-item)
(remove-first-agenda-item! the-agenda)
(propagate))))

4)设计测试系统
因为,wire可以订阅,所以我们创建一个只用来显示信息的procedure:probe,让probe来订阅wire,这样以来之前的wire真正运行中改变了消息,就会通过probe显示日志。
这就是非侵入式日志的思想源头!!!!需要被日志者具备订阅能力。
这样以来,任何系统变化只需要notify就行,在业务编码的时候就不需要考虑日志系统的构建。
应该是,在模块连线的Object上具备订阅能力。
(define (probe name wire)
(add-action! wire
(lambda ()
(newline)
(display name)
(display " ")
(display (current-time the-agenda))
(display " New-value - ")
(display (get-signal wire)))))

(define the-agenda (make-agenda)) ;;全局变量
(define inverter-delay 2)
(define and-gate-delay 3)
(define or-gate-delay 5)

(define input-1 (make-wire))
(define input-2 (make-wire))
(define sum (make-wire))
(define carry (make-wire))

;; 注册日志
(probe 'sum sum)
sum 0 New-value = 0

(prob 'carry carry)
carry 0 New-value = 0

;; 构建
(half-adder input-1 input-2 sum carry)
ok

(set-signal! input-1 1)
done

(propagate)
sum 8 New-value = 1
done

(set-signal! input-2 1)
done

(propagate)
carry 11 New-value= 1
sum 16 new-value = 0
done

5)agenda的具体实现
agenda是这样的数据结构:time segments, 每个time segment都是一个pair,由time和queue组成
1.对于time segment的实现如下:
(deine (make-time-segment time queue)
(cons time queue))

(define (segment-time s) (car s))

(define (segment-queue s) (cdr s))

2.对于agenda的实现:
agenda就是一个一维table of time segments
(define (make-agenda) (list 0))

(define (current-time agenda) (car agenda))

(define (set-current-time! agenda time)
(set-car! agenda time))

(define (segments agenda) (cdr agenda))

(define (set-segments! agenda segments)
(set-cdr! agenda segments))

(define (first-segment agenda) (car (segments agenda)))

(define (rest-segments agenda) (cdr (segments agenda)))

add-to-agenda!的实现
(define (add-to-agenda! time action agenda)
(define (belongs-before? segments)
(or (null? segments)
(< time (segment-time (car segments)))))

(define (make-new-time-segment time action)
    (let ((q (make-queue)))
        (insert-queue! q action)
        (make-time-segment time q)))

(define (add-to-segments! segments)
    (if (= (segment-time (car segments)) time))
        (insert-queue! (segment-queue (car segments))
                        action)
        (let ((rest (cdr segments)))
            (if (belongs-before? rest)
                (set-cdr!
                segments
                (cons (make-new-time-segment time action)
                      (cdr segments)))
                (add-to-segments! rest)))))
    (let ((segments (segments agenda)))
        (if (belongs-before? segments)
            (set-segments!
            agenda
            (cons (make-new-time-segment time action)
                    segments))
            (add-to-segments! segments))))

(define (remove-first-agenda-item! agenda)
(let ((q (segment-queue (first-segment agenda))))
(delete-queue! q)
(if (empty-queue? q)
(set-segments! agenda (rest-segments agenda)))))

(define (first-agenda-item agenda)
(if (empty-agenda-item agenda)
(error "agenda is empty -- FIRST-AGENDA-ITEM")
(let ((first-seg (first-segment agenda)))
(set-current-time! agenda (segment-time first-seg))
(front-queue (segment-queue first-seg)))))

HOW - 启发:享受SICP,品味SICP的逻辑之美
像品酒品茶一样,品玩读SICP的过程。
享受才能精通。

Chapter 3.4 Concurrency: Time is of the Essence #

0.前言 #

1.Why I import the concept Concurrency?为什么我要引入并发这个概念? #

1)因为,真实的世界是并发的
2)因为,并发可以提升计算效率

2.What is the problem of Concurrency?并发带来了什么问题? #

1)对于Share State,时序的不同结果会不同。
2)如果交错操作Share State,会出现认知偏差。
3)所以,关键点是State依赖于时间,也就有了限制limited

3.How to fix the problem?如何解决并发带来的问题? #

1)在操作资源的地方加上锁
2)给一个业务流程加锁
3)锁的本质就是Not Concurrency
4)锁的本质就是资源的抽象代理,就像货币是价值的代理。这样做的目的是简化了处理方案,从抽象维度统一处理,将繁杂的具象层面问题,提升到了抽象维度统一解决,这也是通用视角的价值所在。

下面我们来详细讨论

1.WHAT - 并发的问题 #

1)案例还是银行账户,取余额
(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))
2)问题出现:如果Peter和Paul同时取钱,修改余额的时候,就等于双花了,见下图:
(img)
3)一种解决方案是,对于同一个资源,不允许同时操作,见下图:
(img)
但是这样控制行为,还是太过于具象层面,我们需要一个通用工具,在抽象维度,统一视角的来制定规则,而不是具体业务具体解决。

2.HOW-如何解决并发问题 #

1)具象的思路,头痛医头脚痛医脚,
如果从行为层面去矫正,那么面对(a,b,c) 和 (x,y,z)两个交错业务,则会出现20种复杂的行为流程,这种在表面处理问题的复杂度是无法承受的,如下图:
(img)
2)所以,我们必须找到通用视角,在抽象维度解决,这就是抽象的力量,
具体思路是,抽象出来一个锁的概念,所有对共享资源操作的地方,都要加上锁。
这样就不用关心具体的业务流程如何了。一了百了。治标治本。
我们管那个可以控制锁的对象叫做序列化:serializer。

1.演示一下没有serializer的效果 #

;;并发执行
(parallel-execute ... )

;; an example of how this is used
(define x 10)

(parallel-execute
(lambda () (set! x (* x x)))
(lambda () (set! x (+ x 1))))
因为没有序列化的限制,所以结果很乱:
101,121,110,11,100
而我们想要的结果只应该是101或者121,不希望交错。

2.加上serializer的效果如下 #

(define x 10)

(define s (make-serializer))

(parallel-execute
(s (lambda () (set! x (* x x))))
(s (lambda () (set! x (+ x 1)))))
这样以来,两个lambda就不会交错,只会有两种结果101或者121
所以,serializer的作用就是避免交错

3.HOW - 改造一下make-account,加上serializer #

(define (make-account balance)
(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))

(define (deposit amount)
    (set! balance (+ balance amount))
    balance)

(let ((protected (make-serializer)))
    (define (dispatch m)
        (cond
            ((eq! m 'withdraw) (protected withdraw))
            ((eq! m 'deposit) (protected deposit))
            ((eq? m 'balance) balance)
            (else (error "Unknown request -- MAKE-ACCOUNT" m)))
    dispatch))

4.HOW - 如何解决多个资源之间的复杂并发问题? #

1)例如两个账号之间的转账
(define (exchange account1 account2)
(let ((difference (- (account1 'balance)
(account2 'balance))))
((account1 'withdraw) difference)
((account2 'deposit) difference)))
因为如果并发执行,就会出现两个人看到的和操作之间的时间差,这个时间差就可能出现事实上的变化。
简单来说就是,你看见之后和行为之间,不能保障别人没有在背后修改。
之前对于单一资源,解决了看见和修改之间的锁,现在面临的是看见两个资源,修改第一的之后,和修改第二个之间的时间差问题。
这时候那个单一资源的锁就不够用了,
本质上来说,我们需要范围更大的锁,来锁住多个资源的协作时候的时间区间。
这时候,解决思路就是,将锁由模块内,暴露到模块外,由组合业务的使用者,来手工构建时间区间锁。
这带来的问题就是,将复杂性留给了使用者,破坏了封装思想,很不优雅。这也是Obejct view带来的不优雅。
2)具体改造的代码如下,将serializer暴露到外面
(define (make-account-and-serializer balance)
(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance (- balance amount))
balance)
"Insufficient funds"))

(define (deposit amount)
    (set! balance (+ balance amount))
    balance)

(let ((balance-serializer (make-serializer)))
    (define (dispatch m)
        (cond
            ((eq? m 'withdraw) withdraw)
            ((eq? m 'deposit) deposit)
            ((eq? m 'balance) balance)
            ((eq? m 'serializer) balance-serializer)
            (else (eror "Unknown request -- MAKE-ACCOUNT" m)))
    dispatch))

;; 手工操作serializer
(define (deposit account amount)
(let ((s (account 'serializer))
(d (account 'deposit)))
((s d) amount)))

;; 重新改造exchange,依次锁住account1和account2的资源,再进行exchange
(define (serialized-exchange account1 account2)
(let ((serializer1 (account1 'serializer))
(serializer2 (account2 'serializer)))
((serializer1 (serializer2 exchange))
account1
account2)))

5.HOW - 如何实现serializers #

1.serializer的定位是一个序列化者,它掌控着锁mutex, #

获得mutex 用 acquire
释放mutex 用 release
具体实现如下:
(define (make-serializer)
(let ((mutex (make-mutex)))
(lamba (p)
(define (serialized-p . args)
(mutex 'acquire)
(let ((val (apply p args)))
(mutex 'release)
val))
serialized-p)))

2.HOW - 如何实现mutex,真正的锁事一个cell的data #

如果cell为true说明锁生效
如果cell为false说明锁处于释放状态
(define (make-mutex)
(let ((cell (list false)))
(define (the-mutex m)
(cond
((eq? m 'acquire)
(if (text-and-set! cell)
(the-mutex 'acquire))) ;retry
((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)))

从上面的代码中我们可以看到,锁的本质,就是阻塞下面代码的运行,阻塞的本质就是循环loop,在循环loop中不停的判断cell是否为false。这就是一种巨大的损耗,“busy-waiting”,当然真正的busy-waiting会统一由操作系统的一个常驻进程来代理实现,但是还是很耗损能量。
这也是Object view不优雅的地方,需要主动的不停的去做一些无意义的查询。
而,这一切的根源就是state无法同时被两个人使用,这就是一种资源有限感,会导致多个人无时无刻的焦虑,处于将注意力放在轮询资源上。而不是去做有意义的业务。
这种state导致的匮乏感,才是导致有限游戏价值观的元凶。

另外,我们可以看到,抽象思想的力量,用cell来抽象的代理资源,争夺资源的控制权,就是查询cell是否为false。谁能改变cell谁就拥有了权力。
所以真实世界也是如此,货币和权力就像cell一样抽象的代理了使用价值。

Chapter 3.5 Streams #

0.前言 #

1.Why I design Streams?我为什么要设计Streams这个概念? #

因为,Stream view相比Object View可以带来不一样的好处,1)更加优雅(数据与算法分离)2)更加好的效率(不需要busy waiting锁)
当然,Stream view也会带来坏处,不容易调试等等。

2.What is the essence of Stream view?stream view的本质是什么? #

1)不同的世界观:将业务看成一个整体,局部不受time限制。
2)不同的代码style:数据和算法分离
3)Streams是模拟state的一种Data:delayed list
4)Stream view对应复杂系统,《失控》,控制的方式是治理。
5)Stream view与Object view根本的不同不在于是否有赋值语句Assignment,而是看业务的视角,是整体还是分离。

3.Object view ,Stream view,Object,Streams之间的关系是什么? #

1)Object view的本质不是Object,而是一种分离视角—将业务看成又多个割裂的部分组成,是一种,分离世界观。
2)Object只不过是分离世界观下的必然结局方案,用Object这种模式来模拟分离的state,用到的关键技术就是赋值Assignment。Assignment可以产生local state。而因为local state的存在,并发就需要抢夺共享资源,导致就是效率低下,buzy waiting.
3)Stream View的本质也不是Stream,而是一种整体的视角—将业务看成一个整体,是一种,融合世界观。
4)Streams只不过是整体世界观下面的解决方案,即,用Stream来代表整体业务的State。用到的关键技术是list和delay。
5)所以,不用Object,只用纯函数,一样可以编出Object view的代码
6)不用stream,用Object语言,也可以编出Stream view的代码
7)我们不要被概念定义的名词所局限。要跳出表象看到本质。本质就是两种世界观,导致的两种编码的style。
8)style的本质就是模式。就是信息。

3.How - 如何构建stream view? #

1)构建模块体系
2)让数据流转其中
3)将数据由固体(LIST)改为液体(Stream)
4)关键的改造点是引入delay概念。
5)生活的指导意义,关注体系的构建,而不要关注目标。即养成好的习惯更重要。

1.WHAT - Stream的本质就是Delayed Lists #

— Chapter 3.5.1 Streams Are Delayed Lists
我们从之前的代码可以看到Object view和Stream view的雏形,本质上是style的不同,更本质是世界观的不同,即,将业务看成整体还是分离。
对于同一个业务:求素数之和
1)Object view的style代码如下:业务分离/代码一坨/运行分离
(define (sum-primes a b)
(define (iter count accum)
(cond
((> count b) accum)
((prime? count) (iter (+ count 1) (+ count accum)))
(else (iter (+ count 1) accum))))
(iter a 0))
2) Stream view的style代码如下:业务整体/代码分离/运行整体
(define (sum-primes a b)
(accumulate + 0
(filter prime?
(enumerate-interval a b))))

1.HOW - 指导意义 #

1)由上面这两段远古时期的代码,我们可以看到两种世界观的不同,即,如何看业务,整体还是割裂。
2)所以,Assignment不是因,而是果,是因为选择了分离业务视角的必然结局方案。
3)所以,抽象来看,就是选择分别心还是平等心。
4)但是分别和融合又是悖论的平衡。想要融合,手段需要分离。由分得合。想要分离,手段需要融合,由合得分。一切就是分与合的平衡。所以一切解决问题的思路,都是选择分还是合。抽象中的抽象。思路中的思路。
5)而,分与合没有高下之分,只是两种存在与世界上的平等存在而已。
6)分与合之上的抽象是什么?那就是道。无分无合。

2.HOW - 回到主题,因为第二段代码虽然优雅,但是数据的传递是全量的,这就导致了问题的出现。 #

1)List就像是固体,运输起来太重。虽然代码优雅,但是计算机承受不住。
2)Stream就是在List之上的改良,将固体改成了液体。
stream的interface都合list相近
1)cons-stream
2)stream-car
3)stream-cdr
(stream-car (cons-stream x y)) = x
(stream-cdr (cons-stream x y)) = y

(define (stream-ref s n)
(if (= n 0)
(stream-car s)
(stream-ref (stream-cdr s) (- n 1)))))

(define (stream-map proc s)
(if (stream-null? s)
the-empty-stream
(cons-stream (proc (stream-car s))
(stream-map proc (stream-cdr s)))))

(define (stream-for-each proc s)
(if (stream-null? s)
'done
(begin (proc (stream-car s))
(stream-for-each proc (stream-cdr s)))))

(define (stream-map proc . argstreams)
(if (null? (car argstreams))
'()
(cons-stream
(apply proc (map (lambda (s)
(stream-car s))
argstreams))
(apply stream-map
(cons proc (map (lambda (s)
(stream-cdr s))
argstreams))))))

;; stream-for-each is useful for viewing streams:
(define (display-stream s)
(stream-for-each display-line s))

(define (display-line x)
(newline)
(display x))

3.HOW - 如何实现stream的delay? #

1)what is delay ? delay的本质是什么
delay的本质就是一件衣服,将真正的procedure进行了修饰,包裹了一层。
让别人看到的是一个虚幻的entity。换句话说是一个promise
2)对应promise的兑现,需要一个主动的动作来trigger,这就是force概念
force与delay是一对,一个许愿一个兑现。
3)delay在stream中的定位如下:
(cons-stream )
;;is equivalent to
(cons
(delay ))

(define (stream-car stream) (car stream))

(define (stream-cdr stream) (force (cdr stream))

4.HOW 在例子中演示stream的运行 #

我们的需求是求第二个素数
(stream-car
(stream-cdr
(stream-filter prime?
(stream-enumerate-interval 10000 1000000))))

(define (stream-enumerate-interval low high)
(if (> low high)
the-empty-stream
(cons-stream
low
(stream-enumerate-interval (+ low 1) high))))

(define (stream-filter pred stream)
(cond
((stream-null? stream) the-empty-stream)
((pred (stream-car stream))
(cons-stream (stream-car stream)
(stream-filter pred (stream-cdr stream)))))
(else (stream-filter pred (stream-cdr stream)))))
系统运行起来如下:
;;1.stream-enumerate-interval求值得到
(cons 10000
(delay (stream-enumerate-interval 10001 1000000)))

;;2.stream-filter求值,stream-enumerate-interval返回
(dons 10001
(delay (stream-enumerate-interval 10002 1000000)))

;;3.filter直到发现10007
(cons 10007
(delay
(stream-filter
prime?
(cons 10008
(delay
(stream-enumerate-interval 10009 1000000))))))

;;4.通过stream-cdr找到第二个
(cons 10009
(delay
(stream-filter
prime?
(cons 10010
(delay
(stream-enumerate-interval 10011 1000000))))))

5.HOW delay和force的具体实现 #

1)delay的实现,很简单
(delay )
;;is syntactic sugar for
(lambda () )
2)force的实现,也很简单
(define (force delayed-object)
(delayed-object))
3)优化一下,加入memoized
(define (memo-proc proc)
(let ((already-run? false) (result false))
(lambda ()
(if (not already-run?)
(begin (set! result (proc))
(set! already-run? true)
result)
result))))

;; Delay is then defined so that (delay ) is equivalent to
(memo-proc (lambda () ))

2.Why I design Infinite Streams?为什么需要设计无限流? #

— Chapter 3.5.2
1)Streams出现之前,计算机无法模拟Infinite Data。Stream的promise特性让模拟infinite称为可能。
2)为什么要模拟无限?因为,无限代表着抽象维度上的真相。如果能让计算机运行无限,则代表着计算机可以更好的模拟真实世界。虚拟内存就是这种概念。
3)如果计算机可以计算无限,那么计算机就变得没有极限,任何业务都可以模拟了。包括模拟宇宙大爆炸的演进。直到宇宙的尽头。例如,元胞自动机的演化。可以承诺喂养其无限的Data,让其自动演化。

1.HOW - 如何模拟无限的integers #

(define (integers-starting-from n)
(cons-stream n (interger-starting-from (+ 1 n))))

(define integers (integers-starting-from 1))

;; 另一种integer的方式,后面会提到
(define ones (cons-stream 1 ones))

(define (add-stream s1 s2)
(stream-map + s1 s2))

(define integers (cons-stream 1 (add-streams ones integers)))

2.HOW - 如何定义不能被7整除的整数集合: #

(define (divisible? x y)
(= (remainder x y) 0))

(define no-sevens
(stream-filter
(lambda (x)
(not (divisible? x 7)))
integers)))

(stream-ref no-sevens 100)
117

3.HOW - 如何定义fibonacci numbers: #

(define (fibgen a b)
(cons-stream a (fibgen b (+ a b))))

(define fibs (fibgen 0 1))

4.HOW - 如何定义sieve of Eratosthenes #

(define (sieve stream)
(cons-stream
(stream-car stream)
(sieve
(stream-filter
(lambda (x)
(not (divisible? x (stream-car stream))))
(stream-cdr stream)))))

(define primes (sieve (integers-starting-from 2)))

(stream-ref primes 50)
233
本质上,是每次递归的算法再根据cadr再变化,算法的变化也是无穷的。
如下图:
(img)

5.Why I need define streams implicitly?为什么需要用另一种更直接的方式来定义streams? #

1)因为,要用流的视角来治理业务,就像治理两条河流的并道。整体观。
2)之前,的视角是关注单个元素的运算。类似于用砖头构建。现在的Data成了水流,要用治理河流的思维来,所以要用直接定义的方式。
对于流的操作符,类比基本数字的操作符
add-stream, sub-stream, mul-stream, div-stream

HOW - 启发
1)一切的运行时基于希望,也就是不确定性。这正是人类社会运行协作的神奇之处,人们基于彼此的想象共识而在当下运转。
2)所以,真正的资源不是当下看得见的Data,而是未来看不见的承诺/希望。谁能构建出未来的希望,谁就可以拥有掌控权。

HOW - 如何构建
1)ones
(define ones (cons-stream 1 ones))

2)integers
(define (add-streams s1 s2)
(stream-map + s1 s2))

(define integers (cons-stream 1 (add-streams ones integers)))

3)fibonacci numbers
(define fibs
(cons-stream 0
(cons-stream 1
(add-streams (stream-cdr fibs) fibs))))

HOW - 启发
1)将stream作为像primitive data一样的组合计算,计算符号时类似add-streams这样的procedure
2)这等于是换了个时间视角来理解计算和data。用一种流视角。
3)这种对于业务的理解,类似于,整体的组合两个流状态的子业务。

4)scale-stream
(define (scale-stream stream factor)
Stream-map (lambda (x) (* x factor)) stream))

(define double (cons-stream 1 (scale-steam double 2)))

5)primes
(define primes
(cons-stream
2
(stream-filter prime? (integers-starting-from 3))))

(define (prime? n)
(define (iter ps)
(cond
((> (square (stream-car ps)) n ) true)
((divisible? n (stream-cr ps)) false)
(else (iter (stream-cdr ps)))))
(iter primes))

Exercise 3.55 : partial-sums : 对s的装饰器
(define (partial-sums s)
(cons-stream (stream-car s)
(add-streams (partial-sums s)
(stream-cdr s))))

(partial-sums integers)
1,3,6,10,15...

3.HOW - Stream有什么使用价值? #

— Chapter 3.5.3 Exploiting the Stream Paradigm

1.Formulating iterations as stream processes #

1)sqrt-improve
;; from
(define (sqrt-improve guess x)
(average guess (/ x guess)))

;; to
(define (sqrt-stream x)
(define guesses
(cons-stream 1.0
(stream-map (lambda (guess)
(sqrt-improve guess x))
guesses)))
guesses)

(display-stream (sqrt-stream 2))

2)pi-stream
(define (pi-summands n)
(cons-stream (/ 1.0 n)
(stream-map - (pi-summands (+ n 2)))))

(define pi-stream
(scale-stream (partial-sums (pi-summands 1)) 4))

(display-stream pi-stream)

2.Infinite streams of pairs #

3.Stream as signals #

(define (integral integrand initial-value dt)
(define int
(cons-stream initial-value
(add-streams (scale-stream integrand dt)
int)))
int)

4.Why I need Delayed Evaluation?我为什么需要Delayed Evaluation? #

1)因为,参数不改为delay,程序运行会报错。因为引用了一个未定义参数。
2)之前直接定义stream,在procedure中直接引用define的名字,正是因为cons-stream具有的delay求值能力。例如:
(define int
(cons-stream initial-value
(add-streams (scale-stream integrand dt)
int)))

3)我们来看一个没有delay的报错例子:
(define (solve f y0 dt)
(define y (integral dy y0 dt))
(define dy (stream-map f y))
y)
这个procedure就不会运行,因为第一行调用的dy需要被defined,但是dy是在第二行才被defined的。另外这里没有cons-stream所以也就没有delay,如果需要delay则需要手工在参数中加上delay
我们改造一下:
(define (integral delayed-integrand initial-value dt)
(define int
(cons-stream initial-value
(let ((integrand (force delayed-integrand)))
(add-streams (scale-stream integrand dt)
int))))
int)

(define (solve f y0 dt)
(define y (integral (delay dy) y0 dt))
(define dy (stream-map f y))
y)

(stream-ref (solve (lambda (y) y) 1 0.001) 1000)
2.716924

4)WHY - 为了从根上解决问题,抽象思维,通用视角,我们引入一种完全不同的求值模式:Normal-order evaluation
这种求值模式在section 1.1.5已经讨论过
Normal-order Evaluation:直到参数需要被使用的时候,才会被求值。
这种模式的改变需要在第四章的解释器构建时候才能说明。
5)HOW- 启发是什么?
面向愿望,让自己有奔头的行为。
假装自己已经财富自由的做手头的事情。将未来的状态在现在兑现。
BEGING->DOING->HAVING

5.Object view VS Functional view #

1)Object view
(define (make-simplified-withdraw balance)
(lambda (amount)
(set! balance (- balance amount))
balance))

2)Functional view
(define (stream-withdraw balance amount-stream)
(cons-stream
balance
(stream-withdraw (- balance (stream-car amount-stream))
(stream-cdr amount-stream))))

3)Functional view的局限
当两个人共享账户的时候,他们操作取款就彼此干扰了,这种情况下又要引入时间的限制,因为谁先取款,后者看到的余额就不同,两者彼此的时间决定了业务的“real”
HOW - 但是面对同样的问题,bitcoin就用utxo方案完美的解决了这个问题。多个用户共享一个账户,就等于bitcon中多个人共用同一个私钥。大家如果同时转账,只需要将未花费分成两份即可,彼此就不会理解成错误的余额。甚至即便两人同时使用同一个未花费交易,bitcoin系统也会在秒级别判断双花中的真交易,失败的那个人,马上就可以知道交易被拒绝,然后选择下一个未花费交易来引用。而这一切的逻辑,甚至由钱包客户端帮用户搞定,所以用户是感知不到的。

4)两种view的定义
Object view: We can model the world as a collection of separate, time bound, interacting objects with state.

Functional view: We can model the world as a single, timeless, stateless unity.

总的来说,
Object view为分解观,Functional view为整体观。
Object view将state拆碎到每个object中,Functional view用整体来代表the one state。
Object view代表着分别心,Functional view代表着平等心。
Object view代表着简单系统世界观,Functional view代表复杂系统世界观。
Object view为有限游戏,Functional view为无限游戏。

 
0
Kudos
 
0
Kudos

Now read this

第一章.进入忍者的世界(1.Enter the ninja)[完成]

翻译 Secrets of the JavaScript Ninja (JavaScript忍者禁术) 第一章 进入忍者的世界(1.Enter the ninja) 本章重点: 1.介绍本书的目的和结构 2.我们将要关注的类库,我们将要讲解的比较cool的亮点都会在这些类库中找到具体的实现。 3.什么是JavaScript的高级编程(世界级的开发者用什么方式来编写JavaScript) 4.什么是跨浏览器编程 5.展示测试组件(如何测试你的代码) 目录链接:... Continue →