SICP 读书笔记——第 一 章 构造过程抽象——第 3 节 用高阶函数做抽象

3.  用高阶函数做抽象

  • 在作用上,过程也是一类抽象,它们描述了一些对于数的复合操作,但并不依赖于特定的数。
  • 功能强大的程序设计语言: 能为公共的模式命名,建立抽象,而后直接在抽象的层次上工作。过程提供了这种能力,这也是为什么除最简单的程序语言外,其他语言都包含定义过程的机制的原因。
  • 即使在数值计算过程中,如果将过程限制为只能以数作为参数,那也严重的限制我们建立抽象的能力。经常有一些同样的设计模式能用于若干不同的过程。为了把这种模式描述为相应的概念,我们就需要构造出这样的过程,让它们以过程作为参数,或者以过程作为返回值。这类能操作过程的过程称为高阶过程 。本节将展示高阶过程如何能成为强有力的抽象机制,极大的增强语言的表述能力。

3.1  过程作为参数

考虑下面 3 个过程。

  • 计算从 a 到 b 的各整数之和
  • 计算给定范围内的整数的立方之和
  • 计算一个特殊序列之和

程序分别如下:
《SICP 读书笔记——第 一 章 构造过程抽象——第 3 节 用高阶函数做抽象》


可以明显看到,这三个过程共享着一种公共的基础模式。他们的很大一部分是共同的,只在所用的过程名字上不一样:用于从 a 算出需要加的项的函数 (term),还有用于提供下一个 a 值的函数 (next)。我们可以通过填充下面模板中的各空位,产生出上面的各个过程。

《SICP 读书笔记——第 一 章 构造过程抽象——第 3 节 用高阶函数做抽象》

按照上面给出的模式,将其中的“空位”翻译为形式参数:

《SICP 读书笔记——第 一 章 构造过程抽象——第 3 节 用高阶函数做抽象》

这里sum仍然以参数a和b作为上下界,但是又增加了过程参数term和next。使用sum的方式和其他函数一样。

《SICP 读书笔记——第 一 章 构造过程抽象——第 3 节 用高阶函数做抽象》



3.2 用 lambda 构造过程

在上面利用函数 sum 时,我们必须定义出一些如 pi-term 和 pi-next 一类的简单函数,以便用它们作为高阶函数的参数,这样做法看起来很不舒服。如果不需要显示定义 pi-term 和 pi-next,而是有一种方法直接刻画 “那个返回其输入值加4的过程” 和 “那个返回其输入与它加2的乘积的倒数的过程”,事情就方便多了。我们可以通过引入一种 lambda 特殊形式完成这类操作,这种特殊形式能够创建所需要的过程。

利用 lambda,我们就能按照如下方式写出所需要的东西:

  • (lambda (x) (+ x 4)
  • (lambda (x) (/ 1.0 (* x (+ x 2))))

这样就可以直接描述 pi-sum过程,而无需定义任何辅助过程了:
《SICP 读书笔记——第 一 章 构造过程抽象——第 3 节 用高阶函数做抽象》



3.3 过程作为一般性的方法

本节讨论两个实例:找出函数的零点和不动点的一般性方法,并说明如何通过过程去直接描述这些方法。

通过区间折半寻找方程的根

《SICP 读书笔记——第 一 章 构造过程抽象——第 3 节 用高阶函数做抽象》

找出函数的不动点

《SICP 读书笔记——第 一 章 构造过程抽象——第 3 节 用高阶函数做抽象》


计算某个数字 x 的平方根,就是要找到某个 y 使得 y*y =x。将这个等式变形 y = x/y,就可以发现等价找到: y —> x/y 的不动点。因此可以用下面的方式计算平方根:

(define (sqrt x)

(fixed-point (lambda (y) (/ x y))  1.0)) 

容易发现,这个不动点的搜索并不收敛,在两个值之间震荡,陷入了无限循环。

利用一种称为 平均阻尼 的技术,我们取 y 之后的下一个猜测值是 (1/2)(y+ x/y),而不是 x/y。也就是搜寻:y —> (1/2)(y+ x/y)的不动点:

《SICP 读书笔记——第 一 章 构造过程抽象——第 3 节 用高阶函数做抽象》

3.4 过程作为返回值

上面的例子说明,将过程作为参数传递,能够显著增强我们的程序设计语言的表达能力。通过创建另一种其返回值本身也是过程的过程,我们还能得到进一步的表达能力。

还是来看上述不动点的例子。在那里计算平方根时,我们将它转化为求一个函数的不动点搜寻过程。开始时,我们注意到 sqrt(x) 就是 y -> x/y 的不动点,而后又利用了 平均阻尼 技术使得这一逼近收敛。平均阻尼本身也是一种很有用的一般性技术。很自然, 给定了一个函数 f 之后,我们就可以考虑另外一个函数,它在 x 处的值等于 x 和 f(x) 的平均值。


我们可以将平均阻尼的思想表述为下面的过程 average-damp。利用它,我们可以重做前面的平方根过程。


《SICP 读书笔记——第 一 章 构造过程抽象——第 3 节 用高阶函数做抽象》

上面过程 my-sqrt 是怎样 把三种思想结合在同一个方法里:不动点搜寻,平均阻尼,函数 y -> x/y。


当我们利用这些抽象描述计算过程时,其中的想法如何变得更加清晰明了。将一个计算过程形式化为一个过程,一般说,存在很多不同的方式,有经验的程序员知道如何选择过程的形式,使其特别清晰且易理解,使该计算过程中有用的元素能表现为一些相互分离的个体,并使它们还可能重新用于其它的应用


作为重用的一个简单实例,注意 x 的立方根就是函数 y-> x / (y*y) 的不动点。因此我们可以立刻将前面的平方根的过程推广为一个提取立方根的过程。

《SICP 读书笔记——第 一 章 构造过程抽象——第 3 节 用高阶函数做抽象》


顿法

牛顿法: 如果 x->g(x) 是一个可微函数,那么方程 g(x)=0 的一个解就是函数 f(x)=x – g(x) / Dg(x) 的一个不动点。这里Dg(x) 是 g 对 x 的导数。


为了将牛顿法实现为一个过程,我们首先必须描述导数的概念。函数 g(x) 的导数是:Dg(x) = [ g(x+dx) – g(x) ] / dx。这样,我们可以用下面的过程描述导数的概念。

《SICP 读书笔记——第 一 章 构造过程抽象——第 3 节 用高阶函数做抽象》


与 average-damp 一样,deriv 也是一个以过程为参数,并且返回一个过程值的过程。有了 deriv 之后,牛顿法就可以表述为一个求不动点的过程了:

《SICP 读书笔记——第 一 章 构造过程抽象——第 3 节 用高阶函数做抽象》


举个例子,为了求 x 的平方根,我们可以用初始猜测值 1,通过牛顿法来找  y —> y*y – x 的零点。这样就得到了求平方根的另一种形式:

《SICP 读书笔记——第 一 章 构造过程抽象——第 3 节 用高阶函数做抽象》


抽象和第一级过程

上面看到两种方式,它们都能够将平方根计算表述为某种更一般方法的实例。它们分别是:

  • 作为不动点搜寻过程
  • 使用牛顿法

因为牛顿法本身表述的也是一个不动点计算过程,所以我们实际看到了将平方根计算作为不动点的两种形式。每种方法都是从一个函数出发,找到这一函数在某种变换下的不动点。将这一具有普遍性的思想描述为一个函数:


《SICP 读书笔记——第 一 章 构造过程抽象——第 3 节 用高阶函数做抽象》



这个非常具有一般性的过程有一个计算某个函数的过程参数g,一个变换g的过程,和一个初始猜测值,它返回经过这一变换后的函数的不动点。


我们可以利用这一抽象重新塑造上述的两个平方根过程,如下:


《SICP 读书笔记——第 一 章 构造过程抽象——第 3 节 用高阶函数做抽象》

复合过程,是一种至关重要的抽象机制,因为它使我们能将一般性的计算方法,用这一程序设计语言里的元素明确描述。现在我们看到,高阶函数 能如何去操作这些一般性的方法,以便建立起进一步的抽象。


作为编程者,应该对这类抽象保持高度敏感,设法从中识别出程序里的基本抽象,基于它们去进一步构造,并推广它们以创建为例更加强大的抽象。

高阶过程的重要性,就在于使我们能显示的用程序设计语言的要素去描述这些抽象,使我们能像操作其他计算元素一样去操作它们。


一般而言,程序设计语言总会对计算元素的可能使用方式强加上某些限制。带有最少限制的元素被称为具有 第一级 的状态。第一级元素的某些“权力或者特权”包括:

  • 可以用变量命名
  • 可以提供给过程作为参数
  • 可以由过程作为结果返回
  • 可以包含在数据结构中

Lisp 给了 过程 完全的第一级状态。



    原文作者:s亮
    原文地址: https://blog.csdn.net/yl1415/article/details/22421241
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞