Smalltalk是一種動態型別、反射式的物件導向程式語言。Smalltalk由艾倫·凱、Dan Ingalls、Ted Kaehler、Adele Goldberg等於1970年代在施樂帕羅奧多研究中心開始開發。
編程範型 | 物件導向,反射式 |
---|---|
設計者 | Alan Kay、Dan Ingalls、Adele Goldberg |
實作者 | Alan Kay、Dan Ingalls、Adele Goldberg、Ted Kaehler、Diana Merry、Scott Wallace、Peter Deutsch、Xerox PARC其他人 |
釋出時間 | 1972年 | (開發始於1969年)
目前版本 |
|
型態系統 | 強、動態 |
作用域 | 詞法(靜態) |
系統平台 | Xerox Alto[2][3] |
作業系統 | 跨平台 |
主要實作產品 | |
Amber, Dolphin, GemStone/S, GNU Smalltalk, Pharo, Smalltalk/X, Squeak, VisualAge, VisualWorks | |
衍生副語言 | |
Self, GNU Smalltalk | |
啟發語言 | |
Lisp,[4] Simula,[4] Euler,[4] IMP,[4] Planner,[4] Logo[5],Sketchpad,[4] ARPAnet,[4] Burroughs B5000[4] | |
影響語言 | |
AppleScript, CLOS, Dart, Dylan, Erlang, Etoys, Falcon, Go, Groovy, Io, Ioke, Java, Lasso, Logtalk, Newspeak, NewtonScript, Object REXX, Objective-C, PHP 5, Perl 6, Python, Ruby, Scala, Scratch, Self | |
Smalltalk對其它眾多的程式語言的產生起到了極大的推動作用,特別是Objective-C、CLOS、Python和Ruby等。1990年代湧現的許多軟件開發思想都得益於Smalltalk,例如設計模式、敏捷編程和代碼重構[6]等。
概述
Smalltalk和許多程式語言不同,它不僅僅是一門語言。下面從幾個不同的角度來解釋Smalltalk。
- 一種物件導向的程式語言:它是一種物件導向的語言,包含語言的語法和語意。一些編譯器可以透過Smalltalk源程式產生可執行檔案。這些編譯器通常產生一種能在虛擬機器上執行的二進制代碼。Smalltalk語言本身非常精煉。
- 一種程式設計環境:這裏指的是一種提供許多物件的系統,而不是某種特殊的開發環境。和許多語言不同(包括C++),Smalltalk附帶有一個巨大的、相當標準的類別館。這些類使得開發Smalltalk程式的效率非常高。在其它語言(例如Ada、C和Pascal)中,通常被作為語言的一部分的功能(例如條件判斷,迴圈等),在Smalltalk由特定的類提供。
- 一個應用開發環境(ADE):由於Smalltalk的歷史原因,它具有一個非常優秀的高度整合、開放的應用開發環境。由於開發環境中的瀏覽器、監視器以及除錯器,都由同樣的源程式衍生出來的,不同的版本之間也具有相當好的相容性。此外,這些工具的源程式都可以在ADE直接存取。
歷史
最早的Smalltalk原型由艾倫·凱於1970年代初提出。類(來自Simula 67)、海龜繪圖(來自LOGO)以及圖形化使用者介面(來自Sketchpad等先驅系統)等概念的有機組合,構成了Smalltalk的最初的藍圖[5]。
在1971年到1975年之間,艾倫·凱在Xerox PARC的小組,在Xerox Alto電腦上,設計並實現了第一個真正的Smalltalk語言系統,編譯器由Dan Ingalls負責主要實作。這個系統被稱為Smalltalk-71與Smalltalk-72,具有以下幾個技術創新:
開發環境的革新相當迅速。雖然當時的點陣圖顯示器十分昂貴,但是艾倫·凱卻說服了PARC,讓他使用這些點陣圖顯示器,這使得艾倫·凱和他的小組,能夠實現不同大小和字型的文字,使用多窗口環境,以及一些對圖像處理的高端支援。Smalltalk-72影響了演員模型的發展[7],它的語法和執行模型,與現代的Smalltalk變體有着顯著的差異。
在1975到1976年間,艾倫·凱小組認識到應當對執行效率和規模進行最佳化。於是他們在許多重要方面重新設計了Smalltalk系統,被稱為Smalltalk-76,它在語言上:
前述的所有Smalltalk系統,都是在特殊的硬件上實現的,直到1977年至1978年,Bruce Horn和Ted Kaehler把Smalltalk-76移植到Xerox NoteTaker上,它是由Intel 8086處理器和自訂顯示器所組成的硬件環境。雖然這種硬件環境只生產了10台,但是它證明了在通常的處理器上實現Smalltalk的可能性。
在1979至1980年,部分受NoteTaker專案的影響,Smalltalk小組的注意力轉移到Smalltalk的銷售可行性上。小組設計並實現了新一代的Smalltalk系統,這次修改的目標着重於在標準硬件上的移植性等方面,被稱為Smalltalk-80,它包括:
- 採取ASCII碼字元集,摒棄了原先在Smalltalk-72和Smalltalk-76中使用的特殊字元。
- 取消了原始方法直接存取主記憶體的能力。取而代之的是引入一系列的原始方法提供相應的功能。
- 引入了元類的概念[9]。
- 引入MVC(模型-視圖-控制器)系統以方便互動式應用軟件的開發。
Smalltalk-80是在PARC之外能獲得到的第一個語言變體,最初作為Smalltalk-80版本1,給與了少數公司(惠普、蘋果公司、泰克和DEC)和大學(UC Berkeley),用於同行評審和在它們自己的平台上實現。後來在1983年普遍可獲得的實現,叫做Smalltalk-80版本2,發行為虛擬機器規定和映像(具有對象定義的獨立於平台的檔案)[10]。
1988年Xerox PARC為了將Smalltalk推向市場而成立了分拆公司ParcPlace Systems。ANSI Smalltalk自從1998年來是標準的語言參考[11]。
兩個當前流行的Smalltalk實現變體,是這些最初Smalltalk-80映像的後代。Squeak是開源實現,它經由Apple Smalltalk[12],衍生自Smalltalk-80版本1.03[13]。VisualWorks經由Smalltalk-80 2.5和ObjectWorks(二者都是ParcPlace Systems的產品),衍生自Smalltalk-80版本2[10]。
物件導向程式設計
如同其他物件導向語言,Smalltalk-80(而非Smalltalk-72)的中心概念是「對象」 。一個對象總是一個「類」的一個「實例」。類是描述它們的實例的屬性和行為的「藍圖」。例如,一個GUI窗口類,可以聲明窗口擁有的屬性,比如標籤、位置和窗口是否可見。這個類還可以聲明其實例支援的操作,比如打開、關閉、移動和隱藏。每個特定窗口對象,對這些屬性都有自己的值,它們每個都能進行它的類別定義的操作。
Smalltalk對象確切的可以做三件事:
- 持有狀態(參照到其他對象)。
- 接收訊息自本身或其他對象。
- 在處理一個訊息的過程中,傳送訊息至本身或其他對象。
一個對象持有的狀態總是私有於這個對象。其他對象只能通過發動請求(訊息)至這個對象,來讓它做出查詢或變更這個狀態。任何訊息可以傳送給任何對象:當接收到一個訊息的時候,接收者確定這個訊息是否合適。Alan Kay評論說,儘管關注於對象,訊息才是Smalltalk中最重要的概念:「最大的想法是訊息傳遞,它是Smalltalk/Squeak核心的全部意義所在(它是我們在Xerox PARC階段從未真正完成的某種東西)。」[14]
不同於多數其他語言,Smalltalk對象可以在系統執行的同時進行修改。現場編碼和飛速應用修補程式,是Smalltalk的主導編程方法論,並且是它高效的主要原因。
Smalltalk是「純」物件導向程式設計語言,這意味着,不像C++和Java,在作為對象的值和作為原始類型的值之間沒有區別。在Smalltalk中,原始值比如整數、布林值和字元,也是對象,這麼說的意義在於它們也是相應類別的實例,而且要傳送訊息來呼叫在它們上的運算。編程者可以通過子類,改變或擴充實現原始值的類,使得可以向它們的實例定義新行為,例如實現一個新的控制結構,甚至使得它們現有行為得以改變。這個事實被總結成常聽到的一句短語:「在Smalltalk中,所有東西都是對象」,它可以更精確的表達為:「所有的值都是對象」,因為變數不是。
因為所有的值都是對象,類也是對象。每個類都是這個類的元類的一個實例。元類都是Metaclass
(元類類)的實例,它也是對象,並且是Metaclass class
(元類元類)的實例。代碼塊是Smalltalk表達匿名函數的方式,它也是對象[15]。
Hello, World!例子
Hello, World!程式,實質上被所有電腦語言的課本用作要學習的第一個程式,它展示了這個語言的最基本語法和環境。對於Smalltalk,這個程式可極其簡單的書寫。下列代碼中,訊息show:
被傳送給對象Transcript
,具有字串文字'Hello, World!'
作為它的實際參數。呼叫show:
方法,導致它的實際參數,即字串文字'Hello, World!'
,顯示在叫做「副本」(Transcript)的終端窗口:
Transcript show: 'Hello, World!'.
注意需要打開Transcript窗口,來看到這個例子的結果。
語法
Smalltalk-80語法是相當極簡主義的,只基於了一小把的聲明和保留字。事實上,Smalltalk中只保留了六個「關鍵字」:true
、false
、nil
、self
、super
和thisContext
。它們的準確術語是「偽變數」,是服從變數識別碼規則的識別碼,但指示了編程者所不能變更的繫結。true
、false
和nil
偽變數是單例實例。self
和super
,在響應一個訊息而啟用的方法中,指稱這個訊息的接收者;但是傳送給super
的訊息,在這個方法的定義類的超類中尋找方法,而非這個接收者的類中,這允許子類中的方法呼叫在超類中的同名方法。thisContext
指稱當前的活動記錄。
內建的語言構造只有訊息傳送、賦值、方法返回和某些對象的文字語法。從它最初作為給所有年齡兒童的語言開始,標準的Smalltalk語法以更像英語,而非主流編碼語言的方式使用標點符號。語言餘下部份,包括用於條件求值和迭代的控制結構,都由標準Smalltalk類別館實現在內建構造之上。出於效能上的原因,實現可以辨識並特殊處理某些這種訊息,但這只是最佳化而並未硬性規定入語言語法。
諺語「Smalltalk語法適合一張明信片」,所提及的是Ralph Johnson的一個代碼片段,展示了一個方法的所有基本標準語法元素[16]:
exampleWithNumber: x
| y |
true & false not & (nil isNil) ifFalse: [self halt].
y := self size + super size.
#($a #a 'a' 1 1.0)
do: [ :each |
Transcript
show: (each class name);
show: (each printString);
show: ' ' ].
^x < y
下列例子詮釋了最常用的對象,可以在Smalltalk-80方法中被寫為文字值。
下列是數的某些可能例子:
42
-42
123.45
1.2345e2
2r10010010
16rA000
最後兩個專案分別是二進制和十六進制數。在r
前的數是底數或基數。基數不必須是二的冪;例如36rSMALLTALK
是一個有效的數值,等價於十進制的80738163270632
。
字元書寫時帶有前導的美元符:
$A
字串是包圍在單引號內的字元序列:
'Hello, world!'
要在一個字串中包括一個引號,使用另一個引號來跳脫:
'I said, ''Hello, world!'' to them.'
雙引號不需要跳脫,因為單引號界定字串:
'I said, "Hello, world!" to them.'
兩個相等的字串(字串相等,如果它們包含完全相同的字元)可以是駐留在主記憶體不同位置中的不同對象。
除了字串,Smalltalk有一類叫做符號(Symbol
)的字元序列對象。符號保證是唯一的,沒有作為不同對象的兩個相等的符號。因此,符號非常易於比較,並經常用於語言構造中,比如用作訊息選擇子。
符號被寫為#
跟隨着字串文字。比如:
#'foo'
如果一個序列不包含空白或標點字元,還可以寫為:
#foo
例如定義了四個整數的一個陣列:
#(1 2 3 4)
很多實現支援下列位元組陣列(ByteArray
)的文字語法,例如定義了四個整數的位元組陣列:
#[1 2 3 4]
最後卻重要的是塊(匿名函數文字):
[... 一些smalltalk代码 ...]
很多Smalltalk方言為其他對象實現了額外的語法,但是上述的是所有方言都本質上支援的。
在各種Smalltalk中共同使用的有兩種變數:實例變數和臨時變數。其他變數和有關術語依賴於特定實現,例如VisualWorks有類共用變數和名字空間共用變數,而Squeak和很多其他實現,有類別變數、池變數和全域變數。
在Smalltalk中臨時變數聲明是在方法(見後)內聲明的變數。它們聲明在方法的頂部,作為由豎槓包圍的空格分隔的名字。例如:
| index |
聲明一個臨時變數名叫index
,可以包含初始值nil
。
多個變數可以在一組豎槓內聲明:
| index vowels |
聲明了兩個變數:index
和vowels
。所有變數都要初始化。字串的索引變數,初始化為null
字元或初始為0
的ByteArray
,此外的所有變數初始化為nil
。
按命名約定,實例變數、臨時變數、方法或塊的參數,應當以小寫字母開頭,指示它們具有私有作用域,它們合稱為局部變數。而全域變數、類別變數、池字典、類名字,應當以大寫字母開頭,它們合稱為共用變數。
變數通過:=
語法來指定一個值。比如:
vowels := 'aeiou'
指定字串'aeiou'
至前面聲明的vowels
變數。這個字串是個對象(在單引號之間的字元序列是文字字串的語法),在編譯時間由編譯器建立。
在最初的Parc Place映像中,現在底線(_
)的字形,在那時是左向箭頭(←
)字形(就像1963年版本ASCII代碼中那樣)。Smalltalk最初接受左向箭頭,作為唯一的賦值算符。一些現代代碼仍然包含充當賦值的底線,會讓人想起這種最初的用法。多數現代的Smalltalk實現接受要麼底線,要麼冒號等號語法。
訊息是Smalltalk中最基礎的語言構造。所有控制結構都實現為訊息傳送。Smalltalk預設的採用動態分派和單一分派策略,這是相對於其他一些物件導向語言使用的多分派而言的。
下列例子是傳送訊息factorial
至數值42
:
42 factorial
在這種情況下,42
叫做這個訊息的「接收者」,而factorial
是訊息的選擇子。接收者通過返回一個值來相應這個訊息(這個情形中是42
的階乘)。同其他事物一樣,訊息的結果可以賦值給一個變數:
aRatherBigNumber := 42 factorial
上面的factorial
是「一元」訊息,因為只涉及了一個對象,即接收者。
訊息可以承載額外的對象作為實際參數,比如:
2 raisedTo: 4
在這個表達式中,涉及了兩個變數:2
作為接收者而4
作為訊息的實際參數。訊息結果,或用Smalltalk的說法,回答被認定為16
。這種訊息叫做「關鍵字」訊息。訊息可以有多個實際參數,使用如下語法:
'hello world' indexOf: $o startingAt: 6
它的回答是在接收者字串中字元o
的索引,從索引6
開始尋找。這個訊息的選擇子是indexOf:startingAt:
,構成自兩個部份或關鍵字。
這種關鍵字和實際參數的交織意圖改進代碼的可讀性,因為實際參數由前導於它們的關鍵字來解釋。例如,要建立一個矩形的表達式使用C++或Java類別語法可以寫為:
new Rectangle(100, 200);
不清楚這些實際參數分別是什麼。與之相反,在Smalltalk中,這個代碼可以寫為:
Rectangle width: 100 height: 200
這個情況下接收者是Rectangle
類,回答是這個類的具有指定寬度和高度的一個實例。
最後,多數特殊(非字母)字元可以被用作所謂的「二元訊息」。這些允許了數學和邏輯算符以傳統形式書寫:
3 + 4
它傳送訊息+
給接收者3
,具有4
作為實際參數傳遞(回答將是7
)。類似的:
3 > 4
將訊息>
傳送給3
具有實際參數4
(回答將是false
)。
注意,Smalltalk-80語言自身,不包含着這些算符的含義。上述的結果,都只是這些訊息的接收者(這裏是數值實例),為了響應訊息+
和>
而定義並返回的。這個機制的副作用是運算子多載,訊息>
可以被其他對象所理解,允許使用形如a > b
的表達式來比較它們。
一元訊息可以一個接一個的寫成方法鏈式呼叫:
3 factorial factorial log
它傳送factorial
到3
,接着傳送factorial
到前面的結果6
,接着傳送log
到前面的結果720
,產生最終的結果2.85733
。
一個表達式可以包括多次訊息傳送。在這個情況下,表達式依據一個簡單的優先級次序來分析。一元訊息有最高的優先級,隨後是二元訊息,最後是關鍵字訊息。例如:
3 factorial + 4 factorial between: 10 and: 100
被求值如下:
3
接收訊息factorial
並回答6
4
接收訊息factorial
並回答24
6
接收訊息+
具有24
作為實際參數並回答30
30
接收訊息between:and:
具有10
和100
作為實際參數並回答true
最後的訊息傳送的回答,是整個表達式的結果。
在需要的時候使用圓括號可以改變求值的次序。例如:
(3 factorial + 4) factorial between: 10 and: 100
將改變表達式含義,首先計算3 factorial + 4
產生10
。接着10
接收第二個factorial
訊息,產生3628800
。3628800
接着接收between:and:
,回答false
。
注意由於二元訊息的含義,不是硬性規定入Smalltalk-80語法的,它們全部都被認為有相等的優先級,並簡單的從左至右來求值。因此,使用二元訊息的Smalltalk表達式的含義,可能不同於傳統釋義:
3 + 4 * 5
被求值為(3 + 4) * 5
,產生35
。要得到預期回答23
,必須使用圓括號來顯式的定義運算次序:
3 + (4 * 5)
以點號分隔的表示式按順序執行。注意在變數定義和隨後的表達式之間沒有點號。一個表達式序列的值,是最後的表達式的值。除了最後的表達式之外,所有的表達式的值都被忽略。注意點號是分隔符而並非終結符,因此最終的點號是可選的。
下列(假想)例子中,書寫了一序列的表達式,每個都用點號分隔。這個例子首先建立類Window
的一個新實例,儲存它在一個變數中,接着向它傳送兩個訊息:
| window |
window := Window new.
window label: 'Hello'.
window open
如果像上述例子這樣,將一序列訊息都傳送給相同的接收者,它們也可以寫為方法級聯呼叫,具有用分號分隔的單獨訊息:
Window new
label: 'Hello';
open
這種將前面例子的重新為一個單一表達式,避免了對將新窗口儲存在臨時變數的需要。依據平常的優先級規則,首先向Window
類傳送一元訊息new
,接着向new
回答的那個對象,傳送label:
和open
。
可以使用yourself
訊息來返回一個級聯訊息的接收者。
塊是頭等對象。代碼塊即匿名函數,可以被表達一個文字值(它是一個對象,因為所有值都是對象)。這是通過方括號達成的:
[ :params | <消息表达式> ]
這裏的:params
是代碼可以接受的形式參數的列表。結果的塊對象可以形成一個閉包:它可以在任何時間訪問它外圍的詞法作用域內的變數。這意味着下列Smalltalk代碼:
[:x | x + 1]
可以理解為:,或用λ演算表達為: : 。
塊可以通過傳送給它們value
訊息來執行。塊有一個參數用value:
,有2個參數使用value:value:
,以此類推直到4個參數,對多於4個參數使用valueWithArguments:
並將參數作為陣列傳遞。例如下面的表達式:
[:x | x + 1] value: 3
可以被求值為:,或用λ演算表達為:。
塊返回(常稱為回答)其主體的最後一個表達式的值,除非有一個由顯式的^
指示的返回,這時返回這個返回表達式的值。在塊內部的返回,充當了一種逃出(escape)機制。在一個巢狀的塊表達中的返回表達式,將終止在字面上包圍的方法。
塊的文字表示是一種創新,它一方面允許特定代碼有更重大的可讀性;它允許涉及迭代的演算法一更清晰和簡潔的方式編碼。典型的在某些語言中使用迴圈寫成的代碼,可以在Smalltalk中使用塊簡潔的書寫,有時在單一一行之內。更加重要的,塊允許使用訊息和多型來表達控制結構,因為塊推延了計算,而多型可以用來選擇交替者(alternative)。所以在Smalltalk 80中,if…then…else
被書寫和實現為:
expr ifTrue: [ expr为真时求值的语句 ] ifFalse: [ expr为假时求值的语句 ]
再舉一例,向一個搜集傳送訊息select:
:
positiveAmounts := allAmounts select: [:anAmount | anAmount isPositive]
注意這與函數式程式設計有關,這裏的計算模式被抽象成了高階函數。select:
等價於在一個適當的函子上的高階函數filter[17]。
控制結構
在Smalltalk中控制結構沒有特殊的語法。它們轉而實現為傳送到對象上的訊息。以條件執行為例,布林類Boolean
定義了ifTrue:
、ifFalse:
、ifTrue:ifFalse:
和ifFalse:ifTrue:
方法。比如向一個布林對象,傳送ifTrue:
訊息,並傳遞一個代碼塊作為實際參數,這個塊被執行若且唯若布林接收者為真。下面用一個例子來展示:
result := a > b
ifTrue: [ 'greater' ]
ifFalse: [ 'less or equal' ]
| aString vowels |
aString := 'This is a string'.
vowels := aString select: [:aCharacter | aCharacter isVowel].
在最後一行,向字串對象aString
傳送一個select:
訊息,它具有一個代碼塊[:aCharacter | aCharacter isVowel]
作為實際參數。這個代碼塊,表示一個測試,代碼塊文字將被用作一個謂詞函數,它回答true
,若且唯若這個字串的一個元素aCharacter
,應當被包括在滿足這個測試的字元搜集之中。
字串類String
響應select:
訊息,要呼叫的select:
方法,定義並實現在搜集類Collection
中[18];它將給select:
的實際參數選擇塊,傳送給形式參數aBlock
;然後將繫結了選擇塊的aBlock
嵌入到迭代塊的代碼之中,再把這個迭代塊作為向字串自身傳送的do:
訊息的實際參數,從而將這個字串所包含的每個字元,都作為實際參數傳送給這個迭代塊,而各做一次求值。在求值迭代塊的時候,通過value:
訊息,將迭代元素傳送給aBlock
所繫結的選擇塊,它回答一個布林值;接着向它傳送ifTrue:
訊息,如果這個布林值是對象true
,則將這個字元增加到要返回的字串中。
字串類String
響應do:
訊息,要呼叫的do:
方法,定義在可迭代類Iterable
中[19],而實現在可序列化搜集類SequenceableCollection
中[20],這個類是Iterable
類的子類和String
類的超類。
Smalltalk的例外處理機制,Exception
類及其子類比如Error
類,類似於CLOS的例外處理樣式,使用塊作為處理器:
[ 一些运算.
Error signal: 'an error occurred'.
另一些运算
] on: Error do: [ :ex |
处理器代码.
ex return ]
例外處理器的ex
實際參數,提供對掛起運算的狀態的訪問,比如它的堆疊幀、行號、接收者和實際參數等,並且通過傳送ex proceed
、ex reject
、ex restart
或ex return
之一,還可用來控制計算怎樣繼續。
類
類通過實例變數定義它的實例的結構,通過方法定義它的實例的行為。每個方法都有叫做選擇子的一個名字,它在這個類之內是唯一性的。
下面是個平凡的類別定義[21]:
Object subclass: #MessagePublisher
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Smalltalk Examples'
多數這種定義經常由編程環境來填充。這裏的類別定義是給Object
類的一個訊息,用來建立它叫做MessagePublisher
的一個子類。
在Smalltalk中類是頭等對象,它可以就像任何其他對象一樣接收訊息,並可以在執行時間動態的建立。Object
類在收到這個subclass:instanceVariableNames:classVariableNames:poolDictionaries:category:
訊息之時,原則上首先在其元類Object class
中尋找對應方法,未果而上溯繼承鏈在其超類Class
類中找到對應方法實現。
在Smalltalk中,實例變數是這個實例的私有變數,它可以在定義它們的類的任何實例方法中,還有在它的子類中定義的方法中,通過名字來訪問。在需要於一個類的所有實例、這個類本身和它的子類之間,共用某個數據的時候,要使用類別變數,它是由這個類和它的所有實例共用的私有變數。池變數是在可以沒有繼承關聯的多個類之間共用的變數。池變數最初儲存在池字典中,現在它們應當被定義為專門的類(SharedPool
的子類)的類別變數。
category
指示有關的類的「群組」,在現代Smalltalk版本如Pharo中被替代為package
,包系統是利用簡單的命名約定,組織Squeak和Pharo原始碼的簡單而輕量級的方式。
在一個類所對應的元類中定義的實例變數叫做類別實例變數,每個類都有自己私有的類別實例變數,子類將繼承這些類別實例變數,但是子類會擁有這些變數的它們自己的私有複本,類和它們的子類不分享類別實例變數。例如,可以定義一個類別實例變數叫做count
來跟蹤一個給定的類有多少實例。
類不能直接訪問它的實例的實例變數,而實例不能訪問它們的類的類別實例變數。如果需要的話必須定義變異子與訪問子。
當一個對象接收到一個訊息的時候,呼叫匹配這個訊息名字的一個方法。所有方法都是公開的和虛擬的(也就是動態尋找的)。方法被分組入指示它們意圖的協定(protocol)之中。
為一個類增加方法涉及到Behavior
類,其中的compile:
方法,編譯一個方法原始碼並返回一個CompiledMethod
類別的實例;addSelector:withMethod:
方法,將給定的一個編譯過的方法增加到方法字典中。在Behavior
類的子類ClassDescription
類中,compile:classified:
方法,編譯一個方法的原始碼,並為這個方法指派上給定的歸類(對於方法稱為協定)。
對象負責在執行時間動態的確定,執行哪個方法來響應一個訊息,儘管在很多語言中,這可能是(有時或總是)在編譯時間靜態確定的。下列代碼定義一個方法publish
,並且這個定義將在這個對象收到publish
訊息的時候發生。
publish
Transcript show: 'Hello World!'
下列名字為#quadMultiply:and:
的方法,演示了接收多個實際參數並返回一個值:
quadMultiply: i1 and: i2
"这个方法将给定的两个数相乘并对结果乘以4."
| mul |
mul := i1 * i2.
^mul * 4
執行任何前導了^
(脫字元或↑
)的表達式,都導致這個方法於這一點退出,並返回這個表達式的值。終止而沒有顯式返回某個表達式的一個方法,將隱含的返回自身。
為一個類新建一個實例,要用到new
方法,它定義在Behavior
類中。下列例子代碼:
MessagePublisher new
建立並返回MessagePublisher
類的一個新實例。它典型的會被賦值到一個變數:
publisher := MessagePublisher new
但是也可以向一個臨時的匿名對象傳送一個訊息:
MessagePublisher new publish
比如搜集類Collection
的類別方法中,實例建立方法有with:
、with:with:
[22]、一直到with:with:with:with:with:with:
方法。在下面的例子中,將with:with:
用於有序搜集類OrderedCollection
,它是SequenceableCollection
類的子類,故而能最終上溯至超類搜集類:
| rectangles aPoint collisions |
rectangles := OrderedCollection
with: (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這種有元模型的語言的一個特徵。元模型是描述這個語言的模型,開發者可以使用元模型來做事,比如遊歷、檢查和修改一個對象的分析樹,或找到特定種類的結構的所有實例(例如在元模型中Method
類的所有實例)。
Smalltalk-80是完全的反射式系統,用Smalltalk-80語言實現。Smalltalk-80提供了結構性和計算性反射二者。Smalltalk是結構性反射式系統,其結構是由Smalltalk-80對象定義的。定義這個系統的類和方法也是對象,並且完全是它們所有助力定義的系統的一部份。Smalltalk編譯器將文字原始碼編譯成方法對象,典型是的CompiledMethod
的實例。通過把它們儲存入一個類的方法字典,而增加到這個類。類層級的定義類的那部份,可以向系統增加新類。這個系統是通過執行建立或定義類和方法的Smalltalk-80代碼來擴充的。Smalltalk-80系統是個現場(living)系統,承載着在執行時間擴充自身的能力。
因為類是對象,可以向它們提問比如:「你實現了哪些方法?」或「你定義了什麼欄位/槽/實例變數?」。所以通過能應用於系統中的任何對象的普通的代碼,對象可以輕易的檢查、複製、(去)序列化,諸如此類[23]。
Smalltalk-80還提供計算性反射,有能力觀察系統的計算狀態。在衍生自最初Smalltalk-80的語言中,一個方法的當前活動(activation),可以作為通過偽變數命名的一個對象來訪問,這個偽變數是作為六個保留字之一的thisContext
。通過傳送訊息至thisContext
,一個方法活動可以提問比如:「誰給你傳送了這個訊息?」。這些設施使得有可能實現協程,或類似Prolog的回溯,而不需要修改虛擬機器。異常系統也是使用這個設施實現的。這個設施更有趣的用法之一,是在Seaside web框架之中,它通過為每個編輯的頁面儲存續體,並在它們之間切換來導航一個web站點,緩解了編程者處理Web瀏覽器的返回按鈕的複雜性。使用Seaside編程web伺服器,可以使用更常規的編程風格來完成[24]。
Smalltalk如何使用反射的一個例子,是處理錯誤的機制。當一個對象被傳送了一個它沒有實現的訊息的時候,虛擬機器傳送給這個對象doesNotUnderstand:
訊息,具有這個訊息的實化作為實際參數。這個訊息(它是另一個對象,是Message
的實例),包含這個訊息的選擇子和它的實際參數的一個Array
。在互動式Smalltalk系統中,doesNotUnderstand:
的預設實現,是打開一個錯誤窗口(一個Notifier
)向用戶報告錯誤。通過它和反射設施,用戶可以檢查錯誤在其中發生的上下文,重新定義犯錯的代碼,並繼續,這一切都在這個系統之中,使用Smalltalk-80的反射設施[25][26]。
通過建立只理解(實現)doesNotUnderstand:
的一個類,可以建立一個實例,經由它的doesNotUnderstand:
方法能攔截傳送給它的任何訊息。這種實例可以叫做透明代理(proxy)[27]。可以使用這種代理來實現很多設施,比如分散式Smalltalk,這裏的訊息在多個Smalltalk系統之間交換,和資料庫介面,這裏的對象透明的從資料庫中排除錯誤,還有promise等。分散式Smalltalk的設計影響了如CORBA這樣的系統。
基於映像的持久儲存
多數流行的編程系統,將靜態的程式碼(以類別定義、函數或過程的形式),分離於動態的或執行時間的程式狀態(比如對象或其他形式的程式數據)。它們在程式啟動的時候裝載程式碼,而任何先前的程式狀態必須從設定檔或其他數據源顯式的重新建立。程式(和編程者)未顯式儲存的設置,在每次重新啟動時都必須再次設立。傳統的程式在每次程式儲存一個檔案、退出和多載的時候,還失去很多有用的文件資訊。這會失去細節比如回退歷史或游標位置。基於映像的系統不會因為電腦關閉或OS更新,而強制失去所有這些東西。
但是很多Smalltalk系統,不區分程式數據(對象)和代碼(類)。事實上,類也是對象。因此,多數Smalltalk系統,儲存整個程式狀態(包括類和非類對象二者)在一個映像檔案之中。這個映像可以接着由Smalltalk虛擬機器裝載,將類Smalltalk系統恢復成先前的狀態[28]。這是受到了FLEX的啟發,它是Alan Kay建立的語言並描述於他的科學碩士畢業論文中[29]。
Smalltalk映像類似於(可重新啟動的)核心轉儲,並可以提供與核心轉儲相同的功能,比如延遲或遠端除錯,具有對出錯時刻的程式狀態的完全訪問。將應用代碼建模為某種形式的數據的其他語言比如Lisp,也經常使用基於映像的持久儲存。這種持久儲存的方法,對於快速開發是強力的,因為所有開發資訊(比如程式的解析樹),都儲存而利用於除錯。但是它作為一個真實的持久儲存機制,也有一個嚴重的缺點。首先,開發者可能經常想要隱藏實現細節,並使它們在執行時間不可獲得。出於法律和維護的原因,允許任何人在執行時間修改程式,對於在執行時間環境不暴露原始碼的編譯後的系統,不可避免的介入複雜性和潛在的錯誤。其次,儘管持久儲存機制易於使用,它缺乏多數多用戶系統需要的真正持久儲存能力。最明顯的是進行同多個用戶並列訪問相同的資料庫的事務[30]。
實現列表
OpenSmaltalk VM(OS VM)是Smalltalk執行時環境的著名實現,很多現代Smalltalk VM基於或衍生自它[31]。OS VM自身是從一組Smalltalk原始碼檔案(它們叫做VMMaker),轉譯成原生C語言原始碼(通過使用叫做Slang的轉譯器[32][33]),它依次再針對特定平台和硬件架構來編譯,實際上確使Smalltalk映像的跨平台執行。原始碼可以在GitHub上獲得並在MIT許可證下發佈。OS VM的知名衍生者有:
- Amber Smalltalk,通過轉譯執行在JavaScript上。
- Cincom Smalltalk,包含下列產品:VisualWorks、ObjectStudio和WebVelocity。
- Visual Smalltalk Enterprise,一個家族,包括Smalltalk/V。
- Smalltalk/X[38],由Claus Gittinger開發。
- F-Script,在2009年寫的只用於macOS的實現。
- GemStone/S,GemTalk系統出品。
- GNU Smalltalk,Smalltalk的無頭(缺少GUI)實現。
- StepTalk,GNUstep指令碼框架,它在Objective-C執行時上使用Smalltalk語言。
- VisualAge Smalltalk。
- VAST平台(VA Smalltalk)[39],Instantiations公司開發。
- Little Smalltalk。
- Dolphin Smalltalk,Object Arts出品。
- Smalltalk MT,Object Connect出品的Windows平台Smalltalk。
- Pocket Smalltalk,執行於Palm Pilot。
- SmallJ[40],一個開源的基於Java的Smalltalk,衍生自SmallWorld[41]。
- Etoys,用於學習的可視編程系統。
- Strongtalk,提供可選的強型別。
- TruffleSqueak[42],用於GraalVM的一個Squeak/Smalltalk VM和Polyglot編程環境(更多基於GraalVM的Smalltalk實現可見於官網[43])
參見
- Seaside
- GLASS
參照
延伸閱讀
外部連結
Wikiwand in your browser!
Seamless Wikipedia browsing. On steroids.
Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.
Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.