SICP 讀書筆記——第 一 章 構造過程抽象——第 3 節 用高階函數做抽象

3.  用高階函數做抽象

  • 在作用上,過程也是一類抽象,它們描述了一些對於數的複合操作,但並不依賴於特定的數。
  • 功能強大的程序設計語言: 能爲公共的模式命名,建立抽象,而後直接在抽象的層次上工作。過程提供了這種能力,這也是爲什麼除最簡單的程序語言外,其他語言都包含定義過程的機制的原因。
  • 即使在數值計算過程中,如果將過程限制爲只能以數作爲參數,那也嚴重的限制我們建立抽象的能力。經常有一些同樣的設計模式能用於若干不同的過程。爲了把這種模式描述爲相應的概念,我們就需要構造出這樣的過程,讓它們以過程作爲參數,或者以過程作爲返回值。這類能操作過程的過程稱爲高階過程 。本節將展示高階過程如何能成爲強有力的抽象機制,極大的增強語言的表述能力。

3.1  過程作爲參數

考慮下面 3 個過程。

  • 計算從 a 到 b 的各整數之和
  • 計算給定範圍內的整數的立方之和
  • 計算一個特殊序列之和
程序分別如下:


可以明顯看到,這三個過程共享着一種公共的基礎模式。他們的很大一部分是共同的,只在所用的過程名字上不一樣:用於從 a 算出需要加的項的函數 (term),還有用於提供下一個 a 值的函數 (next)。我們可以通過填充下面模板中的各空位,產生出上面的各個過程。


按照上面給出的模式,將其中的“空位”翻譯爲形式參數:


這裏sum仍然以參數a和b作爲上下界,但是又增加了過程參數term和next。使用sum的方式和其他函數一樣。




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過程,而無需定義任何輔助過程了:



3.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)的不動點:




3.4 過程作爲返回值

上面的例子說明,將過程作爲參數傳遞,能夠顯著增強我們的程序設計語言的表達能力。通過創建另一種其返回值本身也是過程的過程,我們還能得到進一步的表達能力。


還是來看上述不動點的例子。在那裏計算平方根時,我們將它轉化爲求一個函數的不動點搜尋過程。開始時,我們注意到 sqrt(x) 就是 y -> x/y 的不動點,而後又利用了 平均阻尼 技術使得這一逼近收斂。平均阻尼本身也是一種很有用的一般性技術。很自然, 給定了一個函數 f 之後,我們就可以考慮另外一個函數,它在 x 處的值等於 x 和 f(x) 的平均值。


我們可以將平均阻尼的思想表述爲下面的過程 average-damp。利用它,我們可以重做前面的平方根過程。




上面過程 my-sqrt 是怎樣 把三種思想結合在同一個方法裏:不動點搜尋,平均阻尼,函數 y -> x/y。


當我們利用這些抽象描述計算過程時,其中的想法如何變得更加清晰明瞭。將一個計算過程形式化爲一個過程,一般說,存在很多不同的方式,有經驗的程序員知道如何選擇過程的形式,使其特別清晰且易理解,使該計算過程中有用的元素能表現爲一些相互分離的個體,並使它們還可能重新用於其它的應用


作爲重用的一個簡單實例,注意 x 的立方根就是函數 y-> x / (y*y) 的不動點。因此我們可以立刻將前面的平方根的過程推廣爲一個提取立方根的過程。



頓法

牛頓法: 如果 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。這樣,我們可以用下面的過程描述導數的概念。



與 average-damp 一樣,deriv 也是一個以過程爲參數,並且返回一個過程值的過程。有了 deriv 之後,牛頓法就可以表述爲一個求不動點的過程了:



舉個例子,爲了求 x 的平方根,我們可以用初始猜測值 1,通過牛頓法來找  y —> y*y - x 的零點。這樣就得到了求平方根的另一種形式:



抽象和第一級過程

上面看到兩種方式,它們都能夠將平方根計算表述爲某種更一般方法的實例。它們分別是:

  • 作爲不動點搜尋過程
  • 使用牛頓法
因爲牛頓法本身表述的也是一個不動點計算過程,所以我們實際看到了將平方根計算作爲不動點的兩種形式。每種方法都是從一個函數出發,找到這一函數在某種變換下的不動點。將這一具有普遍性的思想描述爲一個函數:



這個非常具有一般性的過程有一個計算某個函數的過程參數g一個變換g的過程,和一個初始猜測值,它返回經過這一變換後的函數的不動點。

我們可以利用這一抽象重新塑造上述的兩個平方根過程,如下:






複合過程,是一種至關重要的抽象機制,因爲它使我們能將一般性的計算方法,用這一程序設計語言裏的元素明確描述。現在我們看到,高階函數 能如何去操作這些一般性的方法,以便建立起進一步的抽象。

作爲編程者,應該對這類抽象保持高度敏感,設法從中識別出程序裏的基本抽象,基於它們去進一步構造,並推廣它們以創建爲例更加強大的抽象。

高階過程的重要性,就在於使我們能顯示的用程序設計語言的要素去描述這些抽象,使我們能像操作其他計算元素一樣去操作它們。


一般而言,程序設計語言總會對計算元素的可能使用方式強加上某些限制。帶有最少限制的元素被稱爲具有 第一級 的狀態。第一級元素的某些“權力或者特權”包括:

  • 可以用變量命名
  • 可以提供給過程作爲參數
  • 可以由過程作爲結果返回
  • 可以包含在數據結構中

Lisp 給了 過程 完全的第一級狀態。



發佈了33 篇原創文章 · 獲贊 0 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章