Smalltalk

Smalltalk是一種動態型別、反射式的面向物件程式語言。Smalltalk由艾倫·凱、Dan Ingalls、Ted Kaehler、Adele Goldberg等於1970年代在施樂帕羅奧多研究中心開始開發。Smalltalk對其它眾多的程式設計語言的產生起到了極大的推動作用，特別是Objective-C、CLOS、Python和Ruby等。1990年代湧現的許多軟體開發思想都得益於Smalltalk，例如設計模式、敏捷程式設計和程式碼重構等。概述.Smalltalk和許多程式設計語言不同，它不僅僅是一門語言。下面從幾個不同的角度來解釋Smalltalk。歷史.最早的Smalltalk原型由艾倫·凱於1970年代初提出。類等概念的有機組合，構成了Smalltalk的最初的藍圖。在1971年到1975年之間，艾倫·凱在Xerox PARC的小組，在Xerox Alto計算機上，設計並實現了第一個真正的Smalltalk語言系統，編譯器由Dan Ingalls負責主要實作。這個系統被稱為Smalltalk-71與Smalltalk-72，具有以下幾個技術創新：開發環境的革新相當迅速。雖然當時的點陣圖顯示器十分昂貴，但是艾倫·凱卻說服了PARC，讓他使用這些點陣圖顯示器，這使得艾倫·凱和他的小組，能夠實現不同大小和字型的文字，使用多視窗環境，以及一些對影象處理的高階支援。Smalltalk-72影響了演員模型的發展，它的語法和執行模型，與現代的Smalltalk變體有著顯著的差異。在1975到1976年間，艾倫·凱小組認識到應當對執行效率和規模進行最佳化。於是他們在許多重要方面重新設計了Smalltalk系統，被稱為Smalltalk-76，它在語言上：前述的所有Smalltalk系統，都是在特殊的硬體上實現的，直到1977年至1978年，Bruce Horn和Ted Kaehler把Smalltalk-76移植到上，它是由Intel 8086處理器和自定顯示器所組成的硬體環境。雖然這種硬體環境只生產了10臺，但是它證明了在通常的處理器上實現Smalltalk的可能性。在1979至1980年，部分受NoteTaker專案的影響，Smalltalk小組的注意力轉移到Smalltalk的銷售可行性上。小組設計並實現了新一代的Smalltalk系統，這次修改的目標著重於在標準硬體上的移植性等方面，被稱為Smalltalk-80，它包括：Smalltalk-80是在PARC之外能獲得到的第一個語言變體，最初作為Smalltalk-80版本1，給與了少數公司。1988年Xerox PARC為了將Smalltalk推向市場而成立了分拆公司ParcPlace Systems。ANSI Smalltalk自從1998年來是標準的語言參考。兩個當前流行的Smalltalk實現變體，是這些最初Smalltalk-80映像的後代。Squeak是開源實現，它經由Apple Smalltalk，派生自Smalltalk-80版本1.03。經由Smalltalk-80 2.5和ObjectWorks，派生自Smalltalk-80版本2。面向物件程式設計.如同其他面嚮物件語言，Smalltalk-80的中心概念是“物件” 。一個物件總是一個“類”的一個“例項”。類是描述它們的例項的屬性和行為的“藍圖”。例如，一個GUI視窗類，可以宣告視窗擁有的屬性，比如標籤、位置和視窗是否可見。這個類還可以宣告其例項支援的操作，比如開啟、關閉、移動和隱藏。每個特定視窗物件，對這些屬性都有自己的值，它們每個都能進行它的類定義的操作。Smalltalk物件確切的可以做三件事：一個物件持有的狀態總是私有於這個物件。其他物件只能透過發動請求。”不同於多數其他語言，Smalltalk物件可以在系統執行的同時進行修改。現場編碼和飛速應用補丁，是Smalltalk的主導程式設計方法論，並且是它高效的主要原因。Smalltalk是“純”面向物件程式語言，這意味著，不像C++和Java，在作為物件的值和作為原始型別的值之間沒有區別。在Smalltalk中，原始值比如整數、布林值和字元，也是物件，這麼說的意義在於它們也是相應類的例項，而且要傳送訊息來呼叫在它們上的運算。程式設計者可以透過子類，改變或擴充套件實現原始值的類，使得可以向它們的例項定義新行為，例如實現一個新的控制結構，甚至使得它們現有行為得以改變。這個事實被總結成常聽到的一句短語：“在Smalltalk中，所有東西都是物件”，它可以更精確的表達為：“所有的值都是物件”，因為變數不是。因為所有的值都是物件，類也是物件。每個類都是這個類的元類的一個例項。元類都是codice_1的例項。程式碼塊是Smalltalk表達匿名函式的方式，它也是物件。Hello, World!例子.Hello, World!程式，實質上被所有計算機語言的課本用作要學習的第一個程式，它展示了這個語言的最基本語法和環境。對於Smalltalk，這個程式可極其簡單的書寫。下列程式碼中，訊息codice_3被髮送給物件codice_4，具有字串文字codice_5作為它的實際引數。呼叫codice_3方法，導致它的實際引數，即字串文字codice_5，顯示在叫做“副本”的終端視窗：Transcript show: 'Hello, World!'.注意需要開啟Transcript視窗，來看到這個例子的結果。語法.Smalltalk-80語法是相當極簡主義的，只基於了一小把的宣告和保留字。事實上，Smalltalk中只保留了六個“關鍵字”：codice_8、codice_9、codice_10、codice_11、codice_12和codice_13。它們的準確術語是“偽變數”，是服從變數識別符號規則的識別符號，但指示了程式設計者所不能變更的繫結。codice_8、codice_9和codice_10偽變數是單例例項。codice_11和codice_12，在響應一個訊息而啟用的方法中，指稱這個訊息的接收者；但是傳送給codice_12的訊息，在這個方法的定義類的超類中查詢方法，而非這個接收者的類中，這允許子類中的方法呼叫在超類中的同名方法。codice_13指稱當前的活動記錄。內建的語言構造只有訊息傳送、賦值、方法返回和某些物件的文字語法。從它最初作為給所有年齡兒童的語言開始，標準的Smalltalk語法以更像英語，而非主流編碼語言的方式使用標點符號。語言餘下部份，包括用於條件求值和迭代的控制結構，都由標準Smalltalk類庫實現在內建構造之上。出於效能上的原因，實現可以識別並特殊處理某些這種訊息，但這只是最佳化而並未硬性規定入語言語法。諺語“Smalltalk語法適合一張明信片”，所提及的是的一個程式碼片段，展示了一個方法的所有基本標準語法元素：exampleWithNumber: x| y |true &amp; false not &amp; (nil isNil) ifFalse: [self halt].y := self size + super size.#($a #a 'a' 1 1.0)do: [ :each |Transcriptshow: (each class name);show: (each printString);show: ' ' ].^x  bifTrue: [ 'greater' ]ifFalse: [ 'less or equal' ]塊也被用來實現，使用者定義控制結構、列舉器、訪問者、異常處理、可插拔的行為和很多其他模式。迭代.下面例子，從一個字串中過濾出其中所含有的母音字元：aString := 'This is a string'.vowels := aString select: [:aCharacter | aCharacter isVowel].在最後一行，向字串物件codice_120傳送一個codice_112訊息，它具有一個程式碼塊codice_122作為實際引數。這個程式碼塊，表示一個測試，程式碼塊文字將被用作一個謂詞函式，它回答codice_8，當且僅當這個字串的一個元素codice_124，應當被包括在滿足這個測試的字元蒐集之中。字串類codice_125響應codice_112訊息，要呼叫的codice_112方法，定義並實現在蒐集類codice_128中；它將給codice_112的實際引數選擇塊，傳送給形式引數codice_130；然後將綁定了選擇塊的codice_130嵌入到迭代塊的程式碼之中，再把這個迭代塊作為向字串自身傳送的codice_132訊息的實際引數，從而將這個字串所包含的每個字元，都作為實際引數傳送給這個迭代塊，而各做一次求值。在求值迭代塊的時候，透過codice_107訊息，將迭代元素傳送給codice_130所繫結的選擇塊，它回答一個布林值；接著向它傳送codice_115訊息，如果這個布林值是物件codice_8，則將這個字元增加到要返回的字串中。字串類codice_125響應codice_132訊息，要呼叫的codice_132方法，定義在可迭代類codice_140中，而實現在可序列化蒐集類codice_141中，這個類是codice_140類的子類和codice_125類的超類。異常處理.Smalltalk的異常處理機制，codice_144類及其子類比如codice_145類，類似於CLOS的異常處理樣式，使用塊作為處理器：[ 一些運算.Error signal: 'an error occurred'.另一些運算] on: Error do: [ :ex |處理器程式碼.ex return ]異常處理器的codice_146實際引數，提供對掛起運算的狀態的訪問，比如它的棧幀、行號、接收者和實際引數等，並且透過傳送codice_147、codice_148、codice_149或codice_150之一，還可用來控制計算怎樣繼續。類.類透過例項變數定義它的例項的結構，透過方法定義它的例項的行為。每個方法都有叫做選擇子的一個名字，它在這個類之內是唯一性的。定義.下面是個平凡的類定義：Object subclass: #MessagePublisherinstanceVariableNames: "classVariableNames: "poolDictionaries: "category: 'Smalltalk Examples'多數這種定義經常由程式設計環境來填充。這裡的類定義是給codice_151類的一個訊息，用來建立它叫做codice_152的一個子類。在Smalltalk中類是頭等物件，它可以就像任何其他物件一樣接收訊息，並可以在執行時間動態的建立。codice_151類在收到這個codice_154訊息之時，原則上首先在其元類codice_155中查詢對應方法，未果而上溯繼承鏈在其超類codice_156類中找到對應方法實現。在Smalltalk中，是這個例項的私有變數，它可以在定義它們的類的任何例項方法中，還有在它的子類中定義的方法中，透過名字來訪問。在需要於一個類的所有例項、這個類本身和它的子類之間，共享某個資料的時候，要使用，它是由這個類和它的所有例項共享的私有變數。池變數是在可以沒有繼承關聯的多個類之間共享的變數。池變數最初儲存在池字典中，現在它們應當被定義為專門的類的類變數。codice_158指示有關的類的“群組”，在現代Smalltalk版本如Pharo中被替代為codice_159，包系統是利用簡單的命名約定，組織Squeak和Pharo原始碼的簡單而輕量級的方式。在一個類所對應的元類中定義的例項變數叫做類例項變數，每個類都有自己私有的類例項變數，子類將繼承這些類例項變數，但是子類會擁有這些變數的它們自己的私有複本，類和它們的子類不分享類例項變數。例如，可以定義一個類例項變數叫做codice_160來跟蹤一個給定的類有多少例項。類不能直接訪問它的例項的例項變數，而例項不能訪問它們的類的類例項變數。如果需要的話必須定義變異子與訪問子。方法.當一個物件接收到一個訊息的時候，呼叫匹配這個訊息名字的一個方法。所有方法都是公開的和虛擬的之中。為一個類增加方法涉及到codice_161類，其中的codice_162方法，編譯一個方法原始碼並返回一個codice_163類的例項；codice_164方法，將給定的一個編譯過的方法增加到方法字典中。在codice_161類的子類codice_166類中，codice_167方法，編譯一個方法的原始碼，併為這個方法指派上給定的歸類。物件負責在執行時間動態的確定，執行哪個方法來響應一個訊息，儘管在很多語言中，這可能是在編譯時間靜態確定的。下列程式碼定義一個方法codice_168，並且這個定義將在這個物件收到codice_168訊息的時候發生。publishTranscript show: 'Hello World!'下列名字為codice_170的方法，演示了接收多個實際引數並返回一個值：quadMultiply: i1 and: i2"這個方法將給定的兩個數相乘並對結果乘以4."| mul |mul := i1 * i2.^mul * 4執行任何前導了codice_110的表示式，都導致這個方法於這一點退出，並返回這個表示式的值。終止而沒有顯式返回某個表示式的一個方法，將隱含的返回自身。例項化.為一個類新建一個例項，要用到codice_100方法，它定義在codice_161類中。下列例子程式碼：MessagePublisher new建立並返回codice_152類的一個新例項。它典型的會被賦值到一個變數：publisher := MessagePublisher new但是也可以向一個臨時的匿名物件傳送一個訊息：MessagePublisher new publish類方法.類方法就是在一個類的元類中定義的方法。比如蒐集類codice_128的類方法中，例項建立方法有codice_177、codice_178、一直到codice_179方法。在下面的例子中，將codice_178用於有序蒐集類codice_181，它是codice_141類的子類，故而能最終上溯至超類蒐集類：rectangles := OrderedCollectionwith: (Rectangle left: 0 right: 10 top: 100 bottom: 200)with: (Rectangle left: 10 right: 10 top: 110 bottom: 210).aPoint := Point x: 20 y: 20.collisions := rectangles select: [:aRect | aRect containsPoint: aPoint].反射.反射是一個計算機科學術語，適用於有能力檢查它們自己的結構的軟體程式，例如檢查它們的分析樹或輸入和輸出引數的資料型別。反射是動態、互動式語言比如Smalltalk和Lisp的一個特徵。具有反射的互動式程式維護所有記憶體內物件的狀態，包括程式碼物件自身，這是在解析/編譯期間生成的，並且是在程式設計上可訪問和修改的。反射也是Smalltalk這種有元模型的語言的一個特徵。元模型是描述這個語言的模型，開發者可以使用元模型來做事，比如遊歷、檢查和修改一個物件的分析樹，或找到特定種類的結構的所有例項。Smalltalk-80是完全的反射式系統，用Smalltalk-80語言實現。Smalltalk-80提供了結構性和計算性反射二者。Smalltalk是結構性反射式系統，其結構是由Smalltalk-80物件定義的。定義這個系統的類和方法也是物件，並且完全是它們所有助力定義的系統的一部份。Smalltalk編譯器將文字原始碼編譯成方法物件，典型是的codice_163的例項。透過把它們儲存入一個類的方法字典，而增加到這個類。類層級的定義類的那部份，可以向系統增加新類。這個系統是透過執行建立或定義類和方法的Smalltalk-80程式碼來擴充套件的。Smalltalk-80系統是個現場系統，承載著在執行時間擴充套件自身的能力。因為類是物件，可以向它們提問比如：“你實現了哪些方法？”或“你定義了什麼欄位/槽/例項變數？”。所以透過能應用於系統中的任何物件的普通的程式碼，物件可以輕易的檢查、複製、序列化，諸如此類。Smalltalk-80還提供計算性反射，有能力觀察系統的計算狀態。在派生自最初Smalltalk-80的語言中，一個方法的當前活動，可以作為透過偽變數命名的一個物件來訪問，這個偽變數是作為六個保留字之一的codice_13。透過傳送訊息至codice_13，一個方法活動可以提問比如：“誰給你傳送了這個訊息？”。這些設施使得有可能實現協程，或類似Prolog的回溯，而不需要修改虛擬機器。異常系統也是使用這個設施實現的。這個設施更有趣的用法之一，是在 web框架之中，它透過為每個編輯的頁面儲存續體，並在它們之間切換來導航一個web站點，緩解了程式設計者處理Web瀏覽器的返回按鈕的複雜性。使用Seaside程式設計web伺服器，可以使用更常規的程式設計風格來完成。Smalltalk如何使用反射的一個例子，是處理錯誤的機制。當一個物件被髮送了一個它沒有實現的訊息的時候，虛擬機發送給這個物件codice_187訊息，具有這個訊息的實化作為實際引數。這個訊息向用戶報告錯誤。透過它和反射設施，使用者可以檢查錯誤在其中發生的上下文，重新定義犯錯的程式碼，並繼續，這一切都在這個系統之中，使用Smalltalk-80的反射設施。透過建立只理解。可以使用這種代理來實現很多設施，比如分散式Smalltalk，這裡的訊息在多個Smalltalk系統之間交換，和資料庫介面，這裡的物件透明的從資料庫中排除錯誤，還有promise等。分散式Smalltalk的設計影響了如CORBA這樣的系統。基於映像的持久儲存.多數流行的程式設計系統，將靜態的程式程式碼未顯式儲存的設定，在每次重啟時都必須再次設立。傳統的程式在每次程式儲存一個檔案、退出和過載的時候，還失去很多有用的文件資訊。這會失去細節比如回退歷史或游標位置。基於映像的系統不會因為計算機關閉或OS更新，而強制失去所有這些東西。但是很多Smalltalk系統，不區分程式資料在一個檔案之中。這個映像可以接著由Smalltalk虛擬機器裝載，將類Smalltalk系統恢復成先前的狀態。這是受到了FLEX的啟發，它是Alan Kay建立的語言並描述於他的科學碩士畢業論文中。Smalltalk映像類似於，都儲存而利用於除錯。但是它作為一個真實的持久儲存機制，也有一個嚴重的缺點。首先，開發者可能經常想要隱藏實現細節，並使它們在執行時間不可獲得。出於法律和維護的原因，允許任何人在執行時間修改程式，對於在執行時間環境不暴露原始碼的編譯後的系統，不可避免的介入複雜性和潛在的錯誤。其次，儘管持久儲存機制易於使用，它缺乏多數多使用者系統需要的真正持久儲存能力。最明顯的是進行同多個使用者並行訪問相同的資料庫的事務。實現列表.OpenSmaltalk.OpenSmaltalk VM，它依次再針對特定平臺和硬體架構來編譯，實際上確使Smalltalk映像的跨平臺執行。原始碼可以在GitHub上獲得並在MIT許可證下發布。OS VM的知名派生者有：JavaScript VM.其他.