Essential SICP Primer

綜述
本書以Lisp語言本身爲例,完整而辨證的講述了“計算機程序”的各種形而上形而下的問題:

  • 程序語言本身的要素(原語、組合手段、抽象手段)
  • 程序的計算模型(代換模型、環境模型,迭代、遞歸)
  • 程序的世界觀(對象式、函數式)
  • 程序如何繁殖進化(元語言抽象)
  • 程序如何執行(編譯、解釋)

同時闡述了程序設計中常用的幾大關鍵技術:

  • 寄存器與堆棧的使用(目前多數計算機的基本抽象)
  • 數據導向與通用型計算(涉及高階過程,數據與過程的統一)
  • 併發程序設計(模型與時序的衝突)
  • 歷史記憶法(即緩存)
  • 應用序與正則序,及背後的惰性求值/延時求值方法論
  • 迭代與尾遞歸的轉換

後兩種看似只是一種優化技術,實際上不止如此,它們牽扯到程序的合法性,運行結果的可預期性

還有幾種特殊的程序設計技術:

  • 模擬時間分叉的非確定性計算
  • 模擬邏輯推理的邏輯程序設計
  • 模擬數學公式的約束系統設計

一、語言要素
每一種強有力的語言都提供了三種機制:

  • 基本表達形式,用於表示語言所關心的最簡單的個體
  • 組合的方法,通過它們可以從較簡單的東西出發構造出複合的元素
  • 抽象的方法,通過它們可以爲複合對象命名,並將它們當作單元去操作

二、數據與過程的統一
第一級元素的特權:

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

Lisp給了“過程”完全的第一級狀態,而一般而言,我們可以將數據定義爲:

  • 一組適當的構造函數(必選)和選擇函數(必選)及改變函數(可選)
  • 爲使這些函數成爲一套合法表示,它們就必須滿足的一組特定條件

這樣,數據與過程在Lisp中就完全統一了

三、併發、時間與通信
併發的基本現象是共享狀態在不同進程間的同步,或迫使進程間通信所產生的事件按照某種特定的順序進行;

從本質上看,在併發控制中,任何時間概念都必然與通信有內在的聯繫;

有意思的是,時間與通信之間的這種聯繫也出現在相對論裏,在那裏的光速(可能用於同步事件的最快信號)是與時間和空間有關的基本常量;

在處理時間和狀態時,我們在計算模型領域所遭遇的複雜性,事實上可能就是物理世界中最基本的複雜性的一種反映

四、對象模型與函數式模型
從一個複雜過程中的一部分的觀點出發,其它的部分看起來正在隨時間變化,它們有着隱蔽的隨時間變化的局部狀態;

如果我們希望去寫程序,在計算機裏用某種結構去模擬現實世界中的這類自然分解,那麼就會做出一些不是函數式的對象--它們必須隨着時間不斷變化;

我們用局部狀態變量去模擬狀態,用對這些變量的賦值模擬狀態的變化;

在這樣做的時候,就是在用計算執行中的時間去模擬我們所在的世界裏的時間,也就是把“對象”弄進了計算機。

用對象來做模擬是威力強大的,也很直觀,這一情況的主要根源,就在於它非常符合我們對自己身處其中並與之交流的世界的看法;

然而,正如我們已經反覆看到的這種模型也產生了對於事件的順序的依賴,以及同步多個進程的棘手問題

避免這些問題的可能性推動着“函數式程序設計語言”的開發,這類語言里根本不提供賦值或者變動對象,在這樣的語言裏,所有過程實現的都是它們參數上的定義良好的數學函數,其行爲不會變化;

物理世界中也有這樣的例子,當我們觀察一個正在移動的粒子時,我們說該粒子的位置(狀態)正在變化,然而,從粒子的世界線的觀點看,這裏根本就不涉及任何變化。

然而,如果我們貼近觀察,就會看到與時間有關的問題也潛入了函數式模型中,原因在於函數式模型與時間無關的特性是將提供時態、時序的責任推給用戶方實現的,當用戶方無法提供時態時,又要重新引入函數式風格致力消除的同一個問題。

我們可以將這一世界模擬爲一集相互分離的,受時間約束的,有局部狀態的,相互交流的對象,或者也可以將世界看作一個大函數,是單一的,無時間的,無狀態的統一體

對象模型對世界的近似在於將其分割爲獨立的片斷,函數式模型則不是沿着對象間的邊界去做模塊化。

當對象間不共享的狀態遠遠大於它所共享的狀態時,對象模型就特別好用。

這種對象觀點失效的一個地方是量子力學,在那裏將物體看作獨立的粒子就會導致悖論和混亂。

將對象觀點和函數觀點統一起來可能與程序設計關係不大,而是與基本認識論有關。

每種觀點都有其強有力的優勢,但就其自身而言,又沒有一種方式能夠完全令人滿意,我們還在期待着一個大統一的出現。

五、元語言抽象與通用機器
這裏的深刻思想是,任一求值器都能模擬其它的求值器;

這樣,有關“原則上說什麼可以計算”的概念(忽略掉所有有關時間和空間的實踐性問題)就是與語言或計算機無關的了;

它反映的是一個有關“可計算性”的基本概念;

這一思想第一次是由圖靈闡述的;

圖靈給出了一種簡單的計算模型--現在被稱爲圖靈機--並聲稱,任何“有效過程”都可以描述爲這種機器的一個程序;

圖靈而後實現了一臺通用機器,即一臺圖靈機,其行爲就像是所有圖靈機程序的求值器。

六、編譯與解釋
編譯可以大大提高程序執行的效率,解釋則爲程序開發和排除錯誤提供了一個更強大的環境,因爲被執行的源代碼在運行期間都是可用的,可用去檢查和修改,此外,由於整個基本操作的庫都在那裏,我們可以在排除錯誤的過程中構造新程序,隨時把它們加入系統中;

由於看到了編譯和解釋的互補優勢,現代程序開發環境很推崇一種混合的策略,使得解釋性程序和編譯性程序可以互相調用;

這就使程序員可以編譯那些自己認爲已經排除了錯誤的部分,從而取得編譯方式的效率優勢,而讓那些正在進行交互式開發和排錯的,還在不斷變化的程序部分的執行仍然維持在解釋模式中;

還可以使程序員根據實際問題選擇最合適的語言。


---------------------------------------------

 

1,John Locke:有關人類理解的隨筆,1690

心智的活動,除了盡力產生各種簡單的認識外,主要表現在如下三個方面:

1)將若干簡單的認識組合爲一個複合認識,由此產生出各種複雜的認識;

2)將兩個認識放在一起對照,不管它們如何簡單或者複雜,在這樣做時並不將它們合而爲一;由此得到有關它們的相互關係的認識;

3)將有關認識與那些在實際中和它們同在的所有其它認識隔離開,這就是抽象,所有具有普遍性的認識都是這樣得到的。

呵呵,和麪向對象者愛引用的差不多:
在大英百科全書關於“分類學理論”中提出:

人類在認識和理解現實世界的過程中,普遍運用着三個構造法則:

區分對象及其屬性,例如,區分一棵樹和樹的大小或空間位置。

區分整體對象及其組成部分,例如,區分一棵樹和樹枝。

不同對象類的形成及區分,例如,所有樹的類和所有石頭的類的形成和區分 

2,(+ (* 3 5) (- 10 6) )

雖然與人們的習慣相背,但卻幾乎是對解釋器來說最容易實現的一種語法,形式完全統一;

沒有優先級,括號是唯一的優先級;

任何複雜的表達式都可以由最簡單的表達式組合(Composite)而成;

在主流高級語言中,只能通過函數對象來模擬得到類似的表達式

 

3,甚高級語言

說明性描述和行動性描述有着內在聯繫,就像數學和計算機科學有着內在聯繫一樣;

有一個當前在程序設計語言設計領域中很重要的問題,那就是所謂的甚高級語言,在這種語言裏,編程就是寫說明性的語句;

這裏的想法是將解釋器做的足夠複雜,程序員描述了需要“做什麼”的知識後,這種解釋器就能自動產生出“如何做”的知識;

一般而言這是不可能做到的,但在這一領域已經取得了巨大進步;

DSL有一定的聯繫

 

1,線性迭代(尾遞歸),線性遞歸,樹型遞歸

2,lambda

匿名過程,直接以過程體表示過程,可用於以更加自然的方式來表示表達式

3,第一級元素的特權

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

Lisp給了“過程”完全的第一級狀態,C++則給了函數指針、函數對象完全的第一級狀態

4,數據抽象

將數據對象的表示或說實現,與對數據對象的使用分開

 

1,閉包

是否可理解爲Lisp對Composite Pattern的顯式支持

2,基礎通用結構

list,tree;tree可以看作list的composite

3,基礎通用操作

參數形式上的統一,使定義通用操作成爲可能;返回值形式上的統一,使定義操作序列成爲可能(Pipe Pattern)

統一的參數和返回值形式是list(C++裏則是iterator的區間)

數據的序列用list表示,操作序列是否也可以用list表示?或許前文已經提到了我忘記了,或許後文還會說明;C++標準庫尚未提供對操作的composite的支持,boost裏有所涉及,但在Lisp裏面,估計是很自然的支持

目前涉及的通用操作已經有filter,map,accumulate,foreach等;filter和map可用在Pipe中間,但accumulate的返回值不保證是list,所以可能只能用在Pipe的末端;foreach乾脆就沒有返回值

———————————————————— 

1,語言要素

在描述一種語言時,應將注意力集中到語言的基本原語,它的組合手段,以及它的抽象手段,這是最重要的;

2,強健設計的語言層次

分層設計:一個複雜的系統應該通過一系列的層次構造出來,每個層次上所用的語言都提供了一些基本元素、組合手段、還有對該層次的細節做抽象的手段,即每個層次都爲表述系統的特徵提供了一套獨特詞彙,以及一套修改這一系統的方式;

正交設計:分層是因爲目前的語言提供的連接手段是“調用”,如果語言能夠提供“調用”之外的其它手段,如“織入”,則可能在分層的體系結構之外出現正交的體系結構;

3,Huffman編碼與二叉樹

待解決問題與所選數據結構的完美搭配

4,begin

即序列求值,C++中則是逗號表達式

5,樹結構的缺點

葉子只能屬於一個分支

6,函數式程序設計與命令式程序設計

不用任何賦值的程序設計稱爲函數式程序設計;與之相對應的,廣泛採用賦值的程序設計被稱爲命令式程序設計

7,引用透明性與別名

如果一個語言支持在表達式裏“同一的東西可以相互替換”的概念,這樣替換不會改變有關表達式的值,這個語言就稱爲是具有“引用透明性”;一個計算對象可以通過多個名字訪問的現象稱爲“別名”

8,同一與等價

“同一”在實現中往往是指向同一塊存儲的多個別名;“等價”則往往是指向多塊存儲的不同對象,但它們在計算中可以相互替換而不影響表達式的值;同一比等價更爲嚴格

9,引用對象與值對象

在系統計算中需要必須是“同一”對象的,往往用引用對象來實現,此時對象有全局唯一ID,缺省即存儲地址;

在系統計算中需要“等價”對象即可的,往往用值對象來實現,此時對象的存儲地址無關緊要,這類對象常見有貨幣等

10,有狀態與無狀態

內部狀態隨時間變化的,稱爲有狀態,反之爲無狀態;

時間是本質問題,有狀態對象在併發系統中存在嚴重缺陷

有狀態對象往往需要“同一性”,無狀態對象往往“等價”即可

11,過程與數據

真實的情況是,在一個可以將過程當作對象的語言裏,在“過程”和“數據”之間並沒有本質性的差異,因此我們可以自由選擇自己所需的語法糖衣,以便按自己選定的風格去做程序設計

 

1,併發

有共享資源的系統中,不可避免的問題

串行化只是解決併發的一種方法,並且會帶來不可避免的死鎖問題

2,流

概念上不同於表,實現上可以看作是採用了“延時求值”的表

3,狀態

可以表示爲值的“沒有時間”的流

4,流的應用

替換迭代模型,替換局部狀態,無窮流,表列(即流的流的流...)

5,流與狀態

無狀態的流可構造出有狀態的系統,原因在於將提供時態的責任推給了用戶方

6,對象與函數式

一種將世界模擬爲一集相互分離的,受時間約束的,有局部狀態的,相互交流的對象

一種將世界看作一個大函數,是單一的,無時間的,無狀態的統一體

 

1,應用序與正則序

惰性求值/延時求值不止帶來性能上的優化,更帶來行爲本質上的變化

2,非確定性計算

將選擇與回溯機制隱藏在語言內部,可輕鬆解決諸如21點之類的問題,因爲“描述即解”

3,規則

一條規則就是一個邏輯蘊含:如果對所有模式變量的一個賦值滿足規則的體,那麼它就滿足其結論;規則的體可以看作一組條件的組合,規則的結論可以看作對滿足這組條件的實例進行的描述

4,合一

模式匹配的一種推廣,即爲了找出查詢時應該使用哪條規則,而找出規則結論與查詢條件能夠模式匹配的那條規則

5,邏輯程序設計的目標

爲程序員提供一種技術,它能將計算問題分解爲兩個相互分離的問題:“什麼”需要計算,以及“如何”進行這一計算;通常“如何”進行計算便是一組規則,“什麼”需要計算便是規則的結論,從使用者的角度來說,對規則的“描述即解”,SQL應該是一種典型的應用

6,not

邏輯程序設計語言裏的not反映了一種所謂的“封閉世界假說”,它認爲所有有關的知識都已經包含在所用的數據庫裏了

 

1,receive返回多個值

採用了多個容器做參數的方式

2,垃圾收集

停止並複製法,標記並清除法

3,尾遞歸

與惰性求值一樣,尾遞歸的優化並不止是一種優化,而可能改變運行時行爲,如使用常量空間,否則可能耗盡系統空間

4,編譯與解釋

編譯可以大大提高程序執行的效率,解釋則爲程序開發和排除錯誤提供了一個更強大的環境,因爲被執行的源代碼在運行期間都是可用的,可用去檢查和修改,此外,由於整個基本操作的庫都在那裏,我們可以在排除錯誤的過程中構造新程序,隨時把它們加入系統中;

由於看到了編譯和解釋的互補優勢,現代程序開發環境很推崇一種混合的策略,使得解釋性程序和編譯性程序可以互相調用;這就使程序員可以編譯那些自己認爲已經排除了錯誤的部分,從而取得編譯方式的效率優勢,而讓那些正在進行交互式開發和排錯的,還在不斷變化的程序部分的執行仍然維持在解釋模式中;還可以使程序員根據實際問題選擇最合適的語言

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章