美文网首页Positioning SICP
Positioning SICP 2: Building Abs

Positioning SICP 2: Building Abs

作者: 牛头酋长 | 来源:发表于2020-09-24 07:06 被阅读0次

    作者:何岩,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

    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 <n> <d>)
    2.设计selectors,即,(numer <x>) 和 (denom <x>)

    具体实现:
    利用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来作为有理数的interfaxe?
    因为,这是一个定位问题,需要在虚空中给有理数一个位置,哪怕有理数的构建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开发的原因,太过于教条。没有彰显自由意志的氛围。程序员们都是想着如何学会权威留下的规范。以学会的规范数量为资本。

    4.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是一个数学概念。

    5.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基于硬件,加速运行。所以,规范都是快的。就像人的习惯思维不用思考就执行了。

    6.有了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。
    而,想其他语言对于数据的抽象,就很复杂了,因为他们的定位是更加的倾向实用性。更加的可以让用户方便的使用轮子。但缺点就是,如果一直只用这些语言,会一直被表象蒙蔽,看不到本质的源头。

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

    — 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

    7.Example:A Picture Language

    TODO

    相关文章

      网友评论

        本文标题:Positioning SICP 2: Building Abs

        本文链接:https://www.haomeiwen.com/subject/wbaayktx.html