Johnson的SICP读书笔记第一篇:构造过程抽象

构造过程抽象

Is it possible that software is not like anything else,that it is meant to be discarded: that the whole point is to see it as a soap bubble? -Alan J.Perlis

计算过程抽象(Building Abstractions with Procedures)

Procedure is some kind of magic.When we are designing a procedure,we just ignore the details, and imagine that we have got all the element we need.

Intro

Process是控制一些抽象的数据的过程。Program就是用来控制Process演化的规则。这里SICP讲的很抽象,接着往下看便是。

计算的过程就像变魔术,一个process看不见摸不着,不是由真实的物质组成的但却是真真切切存在的东西。我们写的程序就像是完全由符号组成的精灵的咒语。不过学编程要比学咒语简单多了hhhh。不过从现在开始我们可以想象计算机就是some kind of sorcery ,只要咒语正确,事情就能正确的完成。
回到SICP这门课程要讨论的话题:软件工程,又或者说,怎样构造一个复杂的系统。成为大牛软件工程师要有组织程序的能力,从一个高级的视角观察系统的能力, 知道怎么样构造在意外的情况下也不会导致严重问题的计算机程序(鲁棒性好),并且知道问题发生时如何debug。

Programming in Lisp

1. Why Lisp?

Lisp语言的特性使其成为学习程序构造和数据结构的利器,最重要的是Lisp是描述性语言,他所描述的procedures本身可以作为Lisp的data进行表示和处理,Lisp这种描述计算过程的能力让他成为了优秀的计算机语言。并且Lisp下编程与以往所学过的C family programming language相当不同,用Lisp编程将会相当有意思。

The Elements of Programming

一个好的编程语言不单单意味着能写出让计算机do something的程序,语言更是我们组织思想的框架,因此我们描述一个语言的时候要尤其注意这个语言是如何将简单的想法进行有机的结合而构造出复杂的机制的。所有的优秀的语言都至少完成一下三个机制:

  1. primitive expressions:基本的表达式,代表了整个语言最简单的实体
  2. means of combination,通过什么方法组合简单元素能形成复杂的元素
  3. 抽象方法: 元素怎样作为一个单元被命名和控制
    本章我们只讨论简单的数值型数据而把精力集中在建立procedures的规则上。之后我们再考虑复杂的数据类型。

Expressions in Lisp

所有的表达式都是前缀表达式(波兰表达式),即
( p r o c e d u r e (procedure v a l 1 val_1 v a l 2 val_2 \dots v a l n ) val_n)
例如(加粗表示输出):

(+ 137 349) ;加减乘除and or not都写在前面
486

你甚至可以这样

(+ 2.7 10 5 6)
23.7

也可以用括号嵌套

(+ (* 3 5) (- 10 6))
19

对于复杂的表达式解释器也只是不停的在做同样的一个基本的简单的循环:

从终端读入一个表达式
计算表达式
输出结果

从终端读入一个表达式 计算表达式,输出结果
这个简单的循环称为read-eval-print loop,即REPL

命名与环境

  • 变量: 变量就是给计算对象的一个名字标识符, 使得程序员可以通过名字使用计算对象.
  • scheme里通过define关键字来给事物命名,
  • 环境: 我们可以将值和符号关联, 而后又能提取这些值, 这意味着解是其必须维护某种存储能力, 一边保持 name-value对. 这个存储对应关系的东西叫做环境.(或是全局环境)

表达式求值

Lisp 可以将表达式写成一棵树, 基本模型如下:

operation
expression A
expresson B

而每一个expression也同样可以表达成更小的操作和表达式, 这是递归定义的求值方法(求一个表达式的值的方法和求其子表达式的方法相同), 可以容易的知道这种表达方式的正确性.

复合过程

程序设计语言必然会出现的元素:

  • 数和算数运算, 最基本的数据和过程
  • 组合式的嵌套提供了一种组织起多个操作的方法
  • 定义是一种受限制的抽象手段, 它为名字关联相应的值

我们已经学会了定义数据, 那么我们如何定义一个过程? 定义过程又有什么作用?
为过程提供一个名字, 而后就可以将这样的操作作为一个单元进行使用了.
举个例子:
(define (square x) (* x x))
这样我们就有了一个复合过程, 给它取的名字是square(实际上这里我们做了两件事情, 1. 建立了一个过程, 2. 为他命名为 squre)
Lisp定义过程的一般形式:

(define (< name-of-process > < formal parameters > < body >)

定义好以后就可以像一个普通的符号一样使用这个过程了. 使用者根本不需要分辨出这是一个基本过程(就像+, -, *, /那样),还是后来人为定义的过程.
好处就是让使用者无需关心实现细节, 只需要用就可以了.

过程应用的代换模型

我们已经知道了怎样定义一个过程, 并且知道如何使用这个过程. 深入思考一下, 解释器是如何对一个复合过程进行求值的呢? 实际上, 解释器只会运算基本过程(只有这些过程是实际嵌入在解释器里的), 解释器的运算方式就是: 对表达式的各个元素求值, 而后将得到的那个过程应用于实际参数上.
看一个例子就很清楚了
(define (f a) (square a))
(define x 1)
(f (* x 2 3))
我们看这个表达式的求值过程:
1. 提取出f的body,
2. 用1代换形式参数x
3. 然后问题归约为求解表达式( * 1 2 3), 求出值6
4. 将square用6进行计算. 即把6带入( * 6 6)得出结果
以上这个过程称为过程应用的代换模型;
注意,

  1. 这个模型值是为了帮助我们领会过程应用吗而不是对解释器的实际工作方式的具体描述.
  2. 这个模型值是一个原型, 而后会进一步完善;
应用序和正则序

我们上文说到表达式的求值涉及到对运算符和各个运算对象的求解而后应用过程, 这就涉及到求值和代换谁先进行的问题了, 随之诞生了应用序和正则序.
正则序: 完全展开以后再归约计算
应用序: 先求出参数的值而后应用
例子:

(define (square x) (* x x))
(define (sum-of-square x y) (+ (square x) (square y))
(define (f a) (sum-of-square (+ a 1) (* a 2))
(f 5)
应用序和正则序的区分方法

区分方法来自练习1.5, Ben Bitdiddle法:

(define (p)(p))
(define (test x y)
	(if (= x 0)
		0
		y))
(test 0 (p))
; 如果解释器采用的是应用序,会陷入死循环;
; 因为引用序需要先求出(p)的值之后才会继续求解外层的表达式, 而(p)是一个死循环
; 正则序则会求出0, 因为正则序先进行带换, 而不会先求(p)
; 带换后(= x 0) = #t, 所以返回0, (p)不求值

牛顿法求平方根

函数与过程的区别

数学上的函数比如说 y = x y=\sqrt{x} , 定义为, y 0 & y 2 = x y \ge 0 \& y^2 = x , 这解决了平方根是什么的问题,但仍未解决任意给定一个数如何计算平方根的问题.
而过程(procedure)更加注重如何计算出平方根具体的值, 比如说如何计算出 2 1.414 \sqrt{2} \approx 1.414 .
如何计算出平方根呢?
1. 二分查找法
2. 牛顿迭代法
3. 猜测
接下来着重说说猜测法,因为此方法体现了本书的一个灵魂—把细节代码的实现看作黑河,我们使用的时候已经有某种魔法把我们需要的函数实现了。 具体分析如下:

(define (sqrt x)
	(define (sqrt-iter guess x)
    (if (good-enough? guess x)
		 guess
		 (sqrt-iter (improve guess x) x)))
	(sqrt-iter (/ x 2.0) x)
)

这里我们实现sqrt的时候完全不需要考虑improve和good-enough的具体实现细节, 只需要明确我们通过这两个过程要达到什么效果,以及给这两个过程那些信息可以完成任务, 至于细节我们假装已经有某种魔法实现了吧。 good-enough?用来判断猜测值是否足够接近 x x 的平方根, improve用来把我们的预测值向更接近平方根的方向靠近。于是, 我们可以把good-enough?实现如下:

(define (good-enough? guess x)
	(< (abs (- square guess) x)) precision))

这里又出来两个未知的东西square和precision, 留下precision便于我们调整精度。我们继续往下实现

(define (square x) (* x x))
(define precision 0.0001)

precision我们可以留到程序使用的时候再决定, 这种可以到使用的时候再决定的性质称为程序的弹性

一级公民

程序语言中受限制最少的元素就是一等公民, 特征有:
- 可以用变量命名
- 可以提供给过程作为参数
- 可以由过程作为结果返回
- 可以包含在数据结构里
Lisp中函数是完全的第一等公民, 非常的特殊

猜你喜欢

转载自blog.csdn.net/li2636733583/article/details/106024213