《计算机程序的构造和解释》(sicp) chap-1

第一章 构造过程抽象

本章主要讲述程序运行过程的共通抽象部分:

  1. 包括程序设计的基本元素
  2. 过程循环的抽象(迭代和递归)
  3. 抽象过程传递给过程(高阶函数)

1.1 程序设计的基本元素

  每一种编程语言的设计,都应该具备三种最基本的机制:基本表达式,组合的方法,抽象的方法。

  所有程序设计中,我们需要关心的永远只有数据过程。通俗的讲,平时编写代码,无非就是变量函数两个部分组成,函数就是过程,用以操作数据,最后达成我们想要的效果。就前端来说,不管你是写css样式,还是js代码,最终都是把一个具量化的结果,传递到显示终端(浏览器)当中,显示渲染。

表达式

  基本表达式实质就是最简单的词法单元,也就是碎片化的过程。比如在 scheme 中,输入512,编译器返回512,这就是一个最基本的表达式。书中通过本节简单介绍了 scheme 基本语法。

;; (运算符 被运算单元 运算单元 )
(+ 10 20)
(- 10 20)
(* 20 30)
(/ 20 30)

;; 括号的语法使得运算优先级很明确
(/ 20
    (+ 20 30)
    (- 10 (* 20 30)))

命名和环境

  最简单的抽象方法,就是给予一个对象命名,通过使用名字的方法去进行计算,就是程序设计当中俗称的变量。在我的理解当中变量的存在是为了方便代码的复用和程序的可读性,在日常代码过程当中,变量命名除了一眼就能知道它所代表的的意义外,还要符合变量种类的命名规范(比如常量使用全大写,类名使用大写开头),有效率的传达到多种意义。

  变量所能访问到的上下文位置,称为环境,也就是我们常说的作用域

  scheme 当中,使用 (define 变量名 变量值)的方式来进行变量的声明。值得一提的是,scheme 中变量是没有赋值方法的。也就是说,变量永远当做常量来进行使用。

(define PI 3.1415926)
PI
;; 3.1415926

复合过程

  定义过程是一种强大的抽象过程,合理的程序设计当中应该存在。定义过程实际上就是一段表达式的复用。scheme中我们使用define来定义一个过程(define (name parameters) body)

;; 这里我们定义一个求放入两个参数的平方和的过程(函数)
(define (sum-square par1 par2) 
    (+
        (* par1 par1)
        (* par2 par2)))
;; 使用函数
(sum-square 1 2)
;; 等同于1 + 4, 5

过程应用的代换模型

  过程的抽象解释,分为正则序应用序两种模型。正则序的特征为完全展开后规约,而应用序为先求值参数而后规约

  正则序应用序是程序解释器两种解释顺序模型的简单体现。

  下面是刚才sum-square函数正则序和应用序的示例。

;; 调用 sum-square
(define par1 10)
(sum-square par1 (+ 10 20))

;; 正则序代换
;; 1
(sum-square par1 (+ 10 20))
;; 2
(+
    (* par1 par1)
    (* (+ 10 20) (+ 10 20))))
;; 3
(+
    (* 10 10)
    (* (+ 10 20) (+ 10 20))))

;; 应用序代换
;; 1
(sum-square 10 (+ 10 20))
;; 2
(sum-square 10 30)
;; 3
(+
    (* 10 10)
    (* 30 30)))

条件表达式和谓词

  条件表达式是增强过程谓词的关键。根据不同的情况执行不同的过程步骤是必要的功能。

  scheme中有ifcond两种条件语句。

;; cond 适用多分支的情况
(cond (p e)
      (p e)
      (else e))

;; if 适用于两种情况的模式
(if p e elseE)

;; 这里用大于0时候加上2,否则减去2为例书写一下 cond 和 if

;; 这儿为了展示cond的多分支特性,特意书写=0的情况
(cond ((> x 0) (+ x 2))
      ((= x 0) (+ x 2))
      (else (- x 2))
)
(if (< x 0)
    (- x 2)
    (+ x 2)
)

  scheme还有三个基础的逻辑复合运算符。and not or。运算返回布尔值#t #f。and为一假为假,全真为真。or为一真为真,全假为假,not为取相反。

(and e1 ... e2)
(not e1)
(or e1 ... e2)

过程作为黑箱抽象

  过程的封装需要有黑箱的抽象概念,即任何使用过程的用户,只需要关心输入,和得到的输出,不要去关心其他任何的东西。为此,我们可以把过程所用到的东西,封装在过程当中,唯一露出给使用者的就是他所输入的函数。这种结构称之为块结构

  这里,我们设计一个函数isEven,用户输入一个数字,判断这个数字是否是偶数,是的话反而真,不是返回false。

;; 函数的思路是一直减少2,直到小于2为止,如果为0,那么是偶数,否则为奇数
;; 用户只需要关心输入的 isEven 以及它的返回值 #t #f
(define (isEven testNum)
    ;; 定义过程,输入一个数字 返回它减2的值
    ;; 这个减2的过程封装在 isEven 当中
    (define (sub2 num) (- num 2))
    ;; 递归,如果没有小于2继续
    (if (< testNum 2) (= testNum 0)
        (isEven (sub2 testNum))
    )
)

1.2 过程与它们所产生的的计算

  这里一小节将重点研究递归循环过程的两种形态,以及如何计算它们的运算复杂度(算法的概念)。

线性的递归和迭代与树形递归的比较

  线性的递归,即每一步平行复杂度的递归方式。这里使用一个求数阶乘的函数来说明。

;; factorial 输入一个n 求n的阶乘 n! 即 n * (n - 1) * ... 1 
(define (factorial n)
    ;; calnum 当前计算的值
    ;; counter 当前计算到哪一个数字
    ;; max-count 求乘阶的值
    (define (fact-iter calnum counter max-count)
        (if (> counter max-count)
            calnum
            (fact-iter 
                (* calnum counter) 
                (+ counter 1) 
                max-count)))
    (fact-iter 1 1 n)
)

  上面的factorial函数,在输入6的时候使用应用替换模型进行展开的话,不难发现每一步都是平行复杂度的的。

(factorial 6)
(fact-iter 1 1 6)
(fact-iter 1 2 6)
(fact-iter 2 3 6)
(fact-iter 6 4 6)
;; ...以下省略

  整个运算过程如同一条直线,因此是线性的递归思路。这种迭代写法的特征就是会用一个专门的值去保存需要返回计算的值,即累计器的概念。

  下面我们看一下树形迭代的概念,用树形迭代的思想来重写factorial函数。

;; factorial-treeway 树形迭代求阶乘
(define (factorial-treeway n)
    ;; counter 当前计算到的值
    ;; max-count 求乘阶的值
    (define (fact-iter counter max-count)
        (if (= counter max-count)
            max-count
            (* counter (fact-iter (+ counter 1) max-count))))
    (fact-iter 1 n))

  我们把factorial-treeway用应用序展开。

(factorial-treeway 6)
(fact-iter 1 6)
(fact-iter (* 1 (fact-iter 2 6)) 6)
(fact-iter (* 1 (* 2 (fact-iter 3 6))) 6)
(fact-iter (* 1 (* 2 (* 3 (fact-iter 4 6)))) 6)
;; 括号看得我脑壳有点疼,以下过程省略

  我们不难看到树型迭代是一个展开的过程,而不是像线性递归一样,每一步都是平行前行的。树形迭代的特点就是没有一个特定的变量去保存函数需要返回的值,而是通过到递归终止条件,去进行一个展开的过程。

增长的阶

  为了比较两种递归的方法差异,这里书本引入了θ记资源消耗的概念,其实这儿就是算法复杂度的思想。

  我们需要一个抽象的概念去衡量一个程序消耗的资源程度,来规避一些影响条件(如计算机硬件)。这里的θ就相当于算法中的O表示法,表示一步程序运行所消耗的资源。比如(+ 1 2),这里执行了一步程序操作,1+2。这里的消耗就是θ(1)。记总消耗为f(n),消耗了n步,那么这个公式就可以写成f(n)=θ(n)

  具体算法方面的内容,我在 github 上面用node写了一个学习的库,感兴趣的可以去看一下,这里就不展开讨论了。

1.3 高阶函数做抽象

  这一章主要讲述了高阶函数的函数式编程的概念。所谓高阶函数,就是能把函数作为参数传递给一个函数,并且还能作为返回值来返回一个函数。React当中的高阶组件,其实就是高阶函数的思想组件版。

  以过程作为参数,返回一个过程,这个过程称为高阶过程。这种强有力的抽象机制,极大的增强语言的表述能力。

  scheme中为了方便过程的传递,创造了lambdalet的方式来方便进行函数的传递。下面我们来定义一个函数,功能类似于for循环,它能够接收一个过程和一个区间范围,返回过程计算的区间范围的值。

;; each 循环的过程
;; cal 累计器的初始值
;; a 起始点
;; b 结束点
(define (eachInterval each cal a b)
    ;; a小于等于b的时候进行循环操作
    (if (not (> a b))
        (eachInterval each (each a cal) (+ a 1) b)
        cal
    )
)

  我们可以使用 lambda 来方便的传递过程给 eachIntervallambda使用的方式是(lambda (parms) body)

;; 计算1+100的值,虽然我们都知道是5050
(eachInterval (lambda (nowIdx cal) (+ cal nowIdx)) 0 1 100)

  scheme中还可以使用let来创建一个结构代码块。注意,let创建的变量仅在let结构块中生效。

(let (
    (val1 exp1)
    (val2 exp2)
) body)

;; 这里简单说明一下作用域的例子
(define x 10)
(let (
    (x 3)
    ;; 此时y赋值时引用的x仍为外部定义的x
    (y (+ x 2))
    ;; let 定义的 x 和 y 仅在 body体 当中生效!在参数定义部分使用的话仍然使用的是全局变量的值
) (* x y))

总结

  其实本质上,第一章是在介绍 scheme 写法的基本要素。变量声明,定义函数,循环的用法以及高阶函数的用法。但是本书通过抽象的思想把scheme的这些基本语法,站在程序的设计共通性的高度上讲解了出来。这就是这本书高明的地方。编程修炼的内功,就是指这些思考吧。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容