Smalltalk

Smalltalk是一種動態類型、反射式的面向對象程式語言。Smalltalk由艾倫·凱、Dan Ingalls、Ted Kaehler、Adele Goldberg等於1970年代在施樂帕羅奧多研究中心開始開發。

Smalltalk對其它眾多的程序設計語言的產生起到了極大的推動作用，特別是Objective-C、CLOS、Python和Ruby等。1990年代湧現的許多軟體開發思想都得益於Smalltalk，例如設計模式、敏捷編程和代碼重構等。

## 概述

Smalltalk和許多程序設計語言不同，它不僅僅是一門語言。下面從幾個不同的角度來解釋Smalltalk。

- 一種面向對象的程序設計語言：它是一種面向對象的語言，包含語言的語法和語義。一些編譯器可以透過Smalltalk源程序產生可執行文件。這些編譯器通常產生一種能在虛擬機上運行的二進位代碼。Smalltalk語言本身非常精煉。
- 一種程序設計環境：這裡指的是一種提供許多物件的系統，而不是某種特殊的開發環境。和許多語言不同（包括C++），Smalltalk附帶有一個巨大的、相當標準的類庫。這些類使得開發Smalltalk程序的效率非常高。在其它語言（例如Ada、C和Pascal）中，通常被作為語言的一部分的功能（例如條件判斷，循環等），在Smalltalk由特定的類提供。
- 一個應用開發環境（ADE）：由於Smalltalk的歷史原因，它具有一個非常優秀的高度集成、開放的應用開發環境。由於開發環境中的瀏覽器、監視器以及調試器，都由同樣的源程序衍生出來的，不同的版本之間也具有相當好的兼容性。此外，這些工具的源程序都可以在ADE直接存取。

## 歷史

<image>

<image>

<image>

最早的Smalltalk原型由艾倫·凱於1970年代初提出。類（來自Simula 67）、海龜繪圖（來自LOGO）以及圖形使用者界面（來自Sketchpad等先驅系統）等概念的有機組合，構成了Smalltalk的最初的藍圖。

在1971年到1975年之間，艾倫·凱在Xerox PARC的小組，在Xerox Alto電腦上，設計並實現了第一個真正的Smalltalk語言系統，編譯器由Dan Ingalls負責主要實作。這個系統被稱為Smalltalk-71與Smalltalk-72，具有以下幾個技術創新：

- 語言完全基於消息交換和Simula 67的類的概念。
- 語言沒有固定的語法，語法分析由類本身完成。

開發環境的革新相當迅速。雖然當時的位圖顯示器十分昂貴，但是艾倫·凱卻說服了PARC，讓他使用這些位圖顯示器，這使得艾倫·凱和他的小組，能夠實現不同大小和字體的文字，使用多窗口環境，以及一些對圖像處理的高端支持。Smalltalk-72影響了演員模型的發展，它的語法和執行模型，與現代的Smalltalk變體有著顯著的差異。

在1975到1976年間，艾倫·凱小組認識到應當對執行效率和規模進行優化。於是他們在許多重要方面重新設計了Smalltalk系統，被稱為Smalltalk-76，它在語言上：

- 引入了繼承和子類的概念。
- 確定了語言的語法，這使得編譯器能夠產生高效、可執行、精煉的二進位代碼。
- 拉里·泰斯勒設計了類瀏覽器，這極大地提高了Smalltalk程式設計師的編程效率。

前述的所有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中使用的特殊字符。
- 取消了原始方法直接存取記憶體的能力。取而代之的是引入一系列的原始方法提供相應的功能。
- 引入了元類的概念。
- 引入MVC（模型-視圖-控制器）系統以方便交互式應用軟體的開發。

Smalltalk-80是在PARC之外能獲得到的第一個語言變體，最初作為Smalltalk-80版本1，給與了少數公司（惠普、蘋果公司、泰克和DEC）和大學（UC Berkeley），用於同行評審和在它們自己的平台上實現。後來在1983年普遍可獲得的實現，叫做Smalltalk-80版本2，發行為虛擬機規定和映像（具有對象定義的獨立於平台的文件）。

1988年Xerox PARC為了將Smalltalk推向市場而成立了分拆公司ParcPlace Systems。ANSI Smalltalk自從1998年來是標準的語言參考。

兩個當前流行的Smalltalk實現變體，是這些最初Smalltalk-80映像的後代。Squeak是開源實現，它經由Apple Smalltalk，派生自Smalltalk-80版本1.03。VisualWorks經由Smalltalk-80 2.5和ObjectWorks（二者都是ParcPlace Systems的產品），派生自Smalltalk-80版本2。

## 面向對象編程

<image>

如同其他面向對象語言，Smalltalk-80（而非Smalltalk-72）的中心概念是「對象」。一個對象總是一個「類」的一個「實例」。類是描述它們的實例的屬性和行為的「藍圖」。例如，一個GUI窗口類，可以聲明窗口擁有的屬性，比如標籤、位置和窗口是否可見。這個類還可以聲明其實例支持的操作，比如打開、關閉、移動和隱藏。每個特定窗口對象，對這些屬性都有自己的值，它們每個都能進行它的類定義的操作。

Smalltalk對象確切的可以做三件事：

- 持有狀態（引用到其他對象）。
- 接收消息自本身或其他對象。
- 在處理一個消息的過程中，發送消息至本身或其他對象。

一個對象持有的狀態總是私有於這個對象。其他對象只能通過發動請求（消息）至這個對象，來讓它做出查詢或變更這個狀態。任何消息可以發送給任何對象：當接收到一個消息的時候，接收者確定這個消息是否合適。Alan Kay評論說，儘管關注於對象，消息才是Smalltalk中最重要的概念：「最大的想法是消息傳遞，它是Smalltalk/Squeak核心的全部意義所在（它是我們在Xerox PARC階段從未真正完成的某種東西）。」

不同於多數其他語言，Smalltalk對象可以在系統運行的同時進行修改。現場編碼和飛速應用補丁，是Smalltalk的主導編程方法論，並且是它高效的主要原因。

Smalltalk是「純」面向對象程式語言，這意味著，不像C++和Java，在作為對象的值和作為原始類型的值之間沒有區別。在Smalltalk中，原始值比如整數、布爾值和字符，也是對象，這麼說的意義在於它們也是相應類的實例，而且要發送消息來調用在它們上的運算。編程者可以通過子類，改變或擴展實現原始值的類，使得可以向它們的實例定義新行為，例如實現一個新的控制結構，甚至使得它們現有行為得以改變。這個事實被總結成常聽到的一句短語：「在Smalltalk中，所有東西都是對象」，它可以更精確的表達為：「所有的值都是對象」，因為變量不是。

因為所有的值都是對象，類也是對象。每個類都是這個類的元類的一個實例。元類都是`Metaclass`（元類類）的實例，它也是對象，並且是`Metaclass class`（元類元類）的實例。代碼塊是Smalltalk表達匿名函數的方式，它也是對象。

## Hello, World!例子

Hello, World!程序，實質上被所有電腦語言的課本用作要學習的第一個程序，它展示了這個語言的最基本語法和環境。對於Smalltalk，這個程序可極其簡單的書寫。下列代碼中，消息`show:`被發送給對象`Transcript`，具有字串文字`'Hello, World!'`作為它的實際參數。調用`show:`方法，導致它的實際參數，即字串文字`'Hello, World!'`，顯示在叫做「副本」（Transcript）的終端窗口：

```smalltalk
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的一個代碼片段，展示了一個方法的所有基本標準語法元素：

```smalltalk
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方法中被寫為文字 (電腦編程)值。

#### 數和字符

下列是數的某些可能例子：

```console
42
-42
123.45
1.2345e2
2r10010010
16rA000
```

最後兩個項目分別是二進位和十六進位數。在`r`前的數是底數或基數。基數不必須是二的冪；例如`36rSMALLTALK`是一個有效的數值，等價於十進位的`80738163270632`。

字符書寫時帶有前導的美元符：

```smalltalk
$A
```

#### 字串

字串是包圍在單引號內的字符序列：

```smalltalk
'Hello, world!'
```

要在一個字串中包括一個引號，使用另一個引號來轉義：

```smalltalk
'I said, ''Hello, world!'' to them.'
```

雙引號不需要轉義，因為單引號界定字串：

```smalltalk
'I said, "Hello, world!" to them.'
```

兩個相等的字串（字串相等，如果它們包含完全相同的字符）可以是駐留在記憶體不同位置中的不同對象。

#### 符號

除了字串，Smalltalk有一類叫做符號 (編程)（`Symbol`）的字符序列對象。符號保證是唯一的，沒有作為不同對象的兩個相等的符號。因此，符號非常易於比較，並經常用於語言構造中，比如用作消息選擇子。

符號被寫為`#`跟隨著字串文字。比如：

```smalltalk
#'foo'
```

如果一個序列不包含空白或標點字符，還可以寫為：

```smalltalk
#foo
```

#### 陣列

例如定義了四個整數的一個陣列：

```smalltalk
#(1 2 3 4)
```

很多實現支持下列字節陣列（`ByteArray`）的文字語法，例如定義了四個整數的字節陣列：

```Javascript
#[1 2 3 4]
```

#### 其他

最後卻重要的是塊（匿名函數文字）:

```smalltalk
[... 一些smalltalk代码 ...]
```

很多Smalltalk方言為其他對象實現了額外的語法，但是上述的是所有方言都本質上支持的。

### 變量聲明

在各種Smalltalk中共同使用的有兩種變量：實例變量和臨時變量。其他變量和有關術語依賴於特定實現，例如VisualWorks有類共享變量和名字空間共享變量，而Squeak和很多其他實現，有類變量、池變量和全局變量。

在Smalltalk中臨時變量聲明是在方法（見後）內聲明的變量。它們聲明在方法的頂部，作為由豎槓包圍的空格分隔的名字。例如：

```smalltalk
| index |
```

聲明一個臨時變量名叫`index`，可以包含初始值`nil`。

多個變量可以在一組豎槓內聲明：

```smalltalk
| index vowels |
```

聲明了兩個變量：`index`和`vowels`。所有變量都要初始化。字串的索引變量，初始化為`null`字符或初始為`0`的`ByteArray`，此外的所有變量初始化為`nil`。

按命名約定，實例變量、臨時變量、方法或塊的參數，應當以小寫字母開頭，指示它們具有私有作用域，它們合稱為局部變量。而全局變量、類變量、池字典、類名字，應當以大寫字母開頭，它們合稱為共享變量。

### 賦值

變量通過`:=`語法來指定一個值。比如：

```smalltalk
vowels := 'aeiou'
```

指定字串`'aeiou'`至前面聲明的`vowels`變量。這個字串是個對象（在單引號之間的字符序列是文字字串的語法），在編譯時間由編譯器創建。

在最初的Parc Place映像中，現在下劃線（`_`）的字形，在那時是左向箭頭（`←`）字形（就像1963年版本ASCII代碼中那樣）。Smalltalk最初接受左向箭頭，作為唯一的賦值算符。一些現代代碼仍然包含充當賦值的下劃線，會讓人想起這種最初的用法。多數現代的Smalltalk實現接受要麼下劃線，要麼冒號等號語法。

### 消息

消息是Smalltalk中最基礎的語言構造。所有控制結構都實現為消息發送。Smalltalk預設的採用動態分派和單一分派策略，這是相對於其他一些面向對象語言使用的多分派而言的。

#### 一元消息

下列例子是發送消息`factorial`至數值`42`：

```smalltalk
42 factorial
```

在這種情況下，`42`叫做這個消息的「接收者」，而`factorial`是消息的選擇子。接收者通過返回一個值來相應這個消息（這個情形中是`42`的階乘）。同其他事物一樣，消息的結果可以賦值給一個變量：

```smalltalk
aRatherBigNumber := 42 factorial
```

上面的`factorial`是「一元」消息，因為只涉及了一個對象，即接收者。

#### 關鍵字消息

消息可以承載額外的對象作為實際參數，比如：

```smalltalk
2 raisedTo: 4
```

在這個表達式中，涉及了兩個變量：`2`作為接收者而`4`作為消息的實際參數。消息結果，或用Smalltalk的說法，回答被認定為`16`。這種消息叫做「關鍵字」消息。消息可以有多個實際參數，使用如下語法:

```smalltalk
'hello world' indexOf: $o startingAt: 6
```

它的回答是在接收者字串中字符`o`的索引，從索引`6`開始查找。這個消息的選擇子是`indexOf:startingAt:`，構成自兩個部份或關鍵字。

這種關鍵字和實際參數的交織意圖改進代碼的可讀性，因為實際參數由前導於它們的關鍵字來解釋。例如，要建立一個矩形的表達式使用C++或Java類語法可以寫為：

```java
new Rectangle(100, 200);
```

不清楚這些實際參數分別是什麼。與之相反，在Smalltalk中，這個代碼可以寫為：

```smalltalk
Rectangle width: 100 height: 200
```

這個情況下接收者是`Rectangle`類，回答是這個類的具有指定寬度和高度的一個實例。

#### 二元消息

最後，多數特殊（非字母）字符可以被用作所謂的「二元消息」。這些允許了數學和邏輯算符以傳統形式書寫：

```smalltalk
3 + 4
```

它發送消息`+`給接收者`3`，具有`4`作為實際參數傳遞（回答將是`7`）。類似的：

```smalltalk
3 > 4
```

將消息`>`發送給`3`具有實際參數`4`（回答將是`false`）。

注意，Smalltalk-80語言自身，不包含著這些算符的含義。上述的結果，都只是這些消息的接收者（這裡是數值實例），為了響應消息`+`和`>`而定義並返回的。這個機制的副作用是運算符重載，消息`>`可以被其他對象所理解，允許使用形如`a > b`的表達式來比較它們。

### 表達式

一元消息可以一個接一個的寫成方法鏈式調用：

```smalltalk
3 factorial factorial log
```

它發送`factorial`到`3`，接著發送`factorial`到前面的結果`6`，接著發送`log`到前面的結果`720`，產生最終的結果`2.85733`。

一個表達式可以包括多次消息發送。在這個情況下，表達式依據一個簡單的優先級次序來分析。一元消息有最高的優先級，隨後是二元消息，最後是關鍵字消息。例如：

```smalltalk
3 factorial + 4 factorial between: 10 and: 100
```

被求值如下：

- `3`接收消息`factorial`並回答`6`
- `4`接收消息`factorial`並回答`24`
- `6`接收消息`+`具有`24`作為實際參數並回答`30`
- `30`接收消息`between:and:`具有`10`和`100`作為實際參數並回答`true`

最後的消息發送的回答，是整個表達式的結果。

#### 組合

在需要的時候使用圓括號可以改變求值的次序。例如：

```smalltalk
(3 factorial + 4) factorial between: 10 and: 100
```

將改變表達式含義，首先計算`3 factorial + 4`產生`10`。接著`10`接收第二個`factorial`消息，產生`3628800`。`3628800`接著接收`between:and:`，回答`false`。

注意由於二元消息的含義，不是硬性規定入Smalltalk-80語法的，它們全部都被認為有相等的優先級，並簡單的從左至右來求值。因此，使用二元消息的Smalltalk表達式的含義，可能不同於傳統釋義：

```smalltalk
3 + 4 * 5
```

被求值為`(3 + 4) * 5`，產生`35`。要得到預期回答`23`，必須使用圓括號來顯式的定義運算次序：

```smalltalk
3 + (4 * 5)
```

#### 複合

以點號分隔的表示式按順序執行。注意在變量定義和隨後的表達式之間沒有點號。一個表達式序列的值，是最後的表達式的值。除了最後的表達式之外，所有的表達式的值都被忽略。注意點號是分隔符而並非終結符，因此最終的點號是可選的。

下列（假想）例子中，書寫了一序列的表達式，每個都用點號分隔。這個例子首先建立類`Window`的一個新實例，存儲它在一個變量中，接著向它發送兩個消息：

```smalltalk
| window |
window := Window new.
window label: 'Hello'.
window open
```

#### 級聯

如果像上述例子這樣，將一序列消息都發送給相同的接收者，它們也可以寫為方法級聯調用，具有用分號分隔的單獨消息：

```smalltalk
Window new
  label: 'Hello';
  open
```

這種將前面例子的重新為一個單一表達式，避免了對將新窗口存儲在臨時變量的需要。依據平常的優先級規則，首先向`Window`類發送一元消息`new`，接著向`new`回答的那個對象，發送`label:`和`open`。

可以使用`yourself`消息來返回一個級聯消息的接收者。

### 代碼塊

塊是頭等對象。代碼塊即匿名函數，可以被表達一個文字值（它是一個對象，因為所有值都是對象）。這是通過方括號達成的：

```smalltalk
[ :params | <消息表达式> ]
```

這裡的`:params`是代碼可以接受的形式參數的列表。結果的塊對象可以形成一個閉包：它可以在任何時間訪問它外圍的詞法作用域內的變量。這意味著下列Smalltalk代碼：

```smalltalk
[:x | x + 1]
```

可以理解為：$f(x) = x + 1$，或用λ演算表達為：$\lambda x$ : $x + 1$。

塊可以通過發送給它們`value`消息來執行。塊有一個參數用`value:`，有2個參數使用`value:value:`，以此類推直到4個參數，對多於4個參數使用`valueWithArguments:`並將參數作為陣列傳遞。例如下面的表達式：

```smalltalk
[:x | x + 1] value: 3
```

可以被求值為：$f(3) = 3 + 1$，或用λ演算表達為：$(\lambda x. x + 1)\,3 \,\underset{\beta}\rightarrow \, 3+1$。

塊返回（常稱為回答）其主體的最後一個表達式的值，除非有一個由顯式的`^`指示的返回，這時返回這個返回表達式的值。在塊內部的返回，充當了一種逃出（escape）機制。在一個嵌套的塊表達中的返回表達式，將終止在字面上包圍的方法。

塊的文字表示是一種創新，它一方面允許特定代碼有更重大的可讀性；它允許涉及迭代的演算法一更清晰和簡潔的方式編碼。典型的在某些語言中使用循環寫成的代碼，可以在Smalltalk中使用塊簡潔的書寫，有時在單一一行之內。更加重要的，塊允許使用消息和多態來表達控制結構，因為塊推延了計算，而多態可以用來選擇交替者（alternative）。所以在Smalltalk 80中，`if…then…else`被書寫和實現為：

```smalltalk
expr ifTrue: [ expr为真时求值的语句 ] ifFalse: [ expr为假时求值的语句 ]
```

再舉一例，向一個搜集發送消息`select:`：

```smalltalk
positiveAmounts := allAmounts select: [:anAmount | anAmount isPositive]
```

注意這與函數式編程有關，這裡的計算模式被抽象成了高階函數。`select:`等價於在一個適當的函子上的高階函數filter。

## 控制結構

在Smalltalk中控制結構沒有特殊的語法。它們轉而實現為發送到對象上的消息。以條件執行為例，布爾類`Boolean`定義了`ifTrue:`、`ifFalse:`、`ifTrue:ifFalse:`和`ifFalse:ifTrue:`方法。比如向一個布爾對象，發送`ifTrue:`消息，並傳遞一個代碼塊作為實際參數，這個塊被執行若且唯若布爾接收者為真。下面用一個例子來展示：

```smalltalk
result := a > b
  ifTrue: [ 'greater' ]
  ifFalse: [ 'less or equal' ]
```

塊也被用來實現，使用者定義控制結構、枚舉器、訪問者、異常處理、可插拔的行為和很多其他模式。

### 迭代

下面例子，從一個字串中過濾出其中所含有的元音字符：

```smalltalk
| aString vowels |
aString := 'This is a string'.
vowels := aString select: [:aCharacter | aCharacter isVowel].
```

在最後一行，向字串對象`aString`發送一個`select:`消息，它具有一個代碼塊`[:aCharacter | aCharacter isVowel]`作為實際參數。這個代碼塊，表示一個測試，代碼塊文字將被用作一個謂詞函數，它回答`true`，若且唯若這個字串的一個元素`aCharacter`，應當被包括在滿足這個測試的字符搜集之中。

字串類`String`響應`select:`消息，要調用的`select:`方法，定義並實現在搜集類`Collection`中

```smalltalk
select: aBlock 
  "Answer a new instance of a Collection containing all the elements
   in the receiver which, when passed to aBlock, answer true"
  | newCollection |
  newCollection := self copyEmpty.
  self do:
    [:element | (aBlock value: element) ifTrue: [newCollection add: element]].
  ^newCollection
```

它將給`select:`的實際參數選擇塊，傳送給形式參數`aBlock`；然後將綁定了選擇塊的`aBlock`嵌入到迭代塊的代碼之中，再把這個迭代塊作為向字串自身發送的`do:`消息的實際參數，從而將這個字串所包含的每個字符，都作為實際參數傳送給這個迭代塊，而各做一次求值。在求值迭代塊的時候，通過`value:`消息，將迭代元素傳送給`aBlock`所綁定的選擇塊，它回答一個布爾值；接著向它發送`ifTrue:`消息，如果這個布爾值是對象`true`，則將這個字符增加到要返回的字串中。

字串類`String`響應`do:`消息，要調用的`do:`方法，定義在可迭代類`Iterable`中

```smalltalk
do: aBlock
  "Enumerate each object of the receiver, passing them to aBlock"
  self subclassResponsibility
```

而實現在可序列化搜集類`SequenceableCollection`中

```smalltalk
do: aBlock
  "Evaluate aBlock for all the elements in the sequenceable collection"
  1 to: self size do: [:i | aBlock value: (self at: i)]
```

```smalltalk
do: aBlock
  "Evaluate the receiver for each element in aBlock"
  | i |
  i := start. step > 0 
    ifTrue: [[i <= stop] whileTrue: [aBlock value: i. i := i + step]]
    ifFalse: [[i >= stop] whileTrue: [aBlock value: i. i := i + step]]
```

這個類是`Iterable`類的子類和`String`類的超類。

### 異常處理

Smalltalk的異常處理機制，`Exception`類及其子類比如`Error`類，類似於CLOS的異常處理樣式，使用塊作為處理器：

```smalltalk
[ 一些运算. 
  Error signal: 'an error occurred'.
  另一些运算
] on: Error do: [ :ex | 
  处理器代码. 
  ex return ]
```

異常處理器的`ex`實際參數，提供對掛起運算的狀態的訪問，比如它的棧幀、行號、接收者和實際參數等，並且通過發送`ex proceed`、`ex reject`、`ex restart`或`ex return`之一，還可用來控制計算怎樣繼續。

## 類

類通過實例變量定義它的實例的結構，通過方法定義它的實例的行為。每個方法都有叫做選擇子的一個名字，它在這個類之內是唯一性的。

### 定義

下面是個平凡的類定義：

```smalltalk
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`消息的時候發生。

```smalltalk
publish
  Transcript show: 'Hello World!'
```

下列名字為`#quadMultiply:and:`的方法，演示了接收多個實際參數並返回一個值：

```smalltalk
quadMultiply: i1 and: i2
  "这个方法将给定的两个数相乘并对结果乘以4."
  | mul |
  mul := i1 * i2.
  ^mul * 4
```

執行任何前導了`^`（脫字符或`↑`）的表達式，都導致這個方法於這一點退出，並返回這個表達式的值。終止而沒有顯式返回某個表達式的一個方法，將隱含的返回自身。

### 實例化

為一個類新建一個實例，要用到`new`方法，它定義在`Behavior`類中。下列例子代碼：

```smalltalk
MessagePublisher new
```

建立並返回`MessagePublisher`類的一個新實例。它典型的會被賦值到一個變量：

```smalltalk
publisher := MessagePublisher new
```

但是也可以向一個臨時的匿名對象發送一個消息：

```smalltalk
MessagePublisher new publish
```

### 類方法

類方法就是在一個類的元類中定義的方法。

比如搜集類`Collection`的類方法中，實例創建方法有`with:`、`with:with:`

```smalltalk
Collection class >> with: firstObject with: secondObject 
  "Answer a collection whose only elements are the parameters
   in the order they were passed"
    ^(self new)
      add: firstObject;
      add: secondObject;
      yourself
```

一直到`with:with:with:with:with:with:`方法。在下面的例子中，將`with:with:`用於有序搜集類`OrderedCollection`，它是`SequenceableCollection`類的子類，故而能最終上溯至超類搜集類：

```smalltalk
| 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）系統，承載著在運行時間擴展自身的能力。

因為類是對象，可以向它們提問比如：「你實現了哪些方法？」或「你定義了什麼欄位/槽/實例變量？」。所以通過能應用於系統中的任何對象的普通的代碼，對象可以輕易的檢查、複製、（去）序列化，諸如此類。

Smalltalk-80還提供計算性反射，有能力觀察系統的計算狀態。在派生自最初Smalltalk-80的語言中，一個方法的當前活動（activation），可以作為通過偽變量命名的一個對象來訪問，這個偽變量是作為六個保留字之一的`thisContext`。通過發送消息至`thisContext`，一個方法活動可以提問比如：「誰給你發送了這個消息？」。這些設施使得有可能實現協程，或類似Prolog的回溯，而不需要修改虛擬機。異常系統也是使用這個設施實現的。這個設施更有趣的用法之一，是在Seaside (軟體) web框架之中，它通過為每個編輯的頁面存儲續體，並在它們之間切換來導航一個web站點，緩解了編程者處理Web瀏覽器的返回按鈕的複雜性。使用Seaside編程web伺服器，可以使用更常規的編程風格來完成。

Smalltalk如何使用反射的一個例子，是處理錯誤的機制。當一個對象被發送了一個它沒有實現的消息的時候，虛擬機發送給這個對象`doesNotUnderstand:`消息，具有這個消息的實化作為實際參數。這個消息（它是另一個對象，是`Message`的實例），包含這個消息的選擇子和它的實際參數的一個`Array`。在交互式Smalltalk系統中，`doesNotUnderstand:`的預設實現，是打開一個錯誤窗口（一個`Notifier`）向使用者報告錯誤。通過它和反射設施，使用者可以檢查錯誤在其中發生的上下文，重新定義犯錯的代碼，並繼續，這一切都在這個系統之中，使用Smalltalk-80的反射設施。

通過建立只理解（實現）`doesNotUnderstand:`的一個類，可以建立一個實例，經由它的`doesNotUnderstand:`方法能攔截發送給它的任何消息。這種實例可以叫做透明代理（proxy）。可以使用這種代理來實現很多設施，比如分布式Smalltalk，這裡的消息在多個Smalltalk系統之間交換，和資料庫接口，這裡的對象透明的從資料庫中排除錯誤，還有promise等。分布式Smalltalk的設計影響了如CORBA這樣的系統。

## 基於映像的持久存儲

多數流行的編程系統，將靜態的程序代碼（以類定義、函數或過程的形式），分離於動態的或運行時間的程序狀態（比如對象或其他形式的程序數據）。它們在程序啟動的時候裝載程序代碼，而任何先前的程序狀態必須從配置文件或其他數據源顯式的重新建立。程序（和編程者）未顯式保存的設置，在每次重啟時都必須再次設立。傳統的程序在每次程序保存一個文件、退出和重載的時候，還失去很多有用的文檔資訊。這會失去細節比如回退歷史或光標位置。基於映像的系統不會因為電腦關閉或OS更新，而強制失去所有這些東西。

但是很多Smalltalk系統，不區分程序數據（對象）和代碼（類）。事實上，類也是對象。因此，多數Smalltalk系統，存儲整個程序狀態（包括類和非類對象二者）在一個系統映像文件之中。這個映像可以接著由Smalltalk虛擬機裝載，將類Smalltalk系統恢復成先前的狀態。這是受到了FLEX的啟發，它是Alan Kay創建的語言並描述於他的科學碩士畢業論文中。

Smalltalk映像類似於（可重啟的）核心轉儲，並可以提供與核心轉儲相同的功能，比如延遲或遠程調試，具有對出錯時刻的程序狀態的完全訪問。將應用代碼建模為某種形式的數據的其他語言比如Lisp，也經常使用基於映像的持久存儲。這種持久存儲的方法，對於快速開發是強力的，因為所有開發資訊（比如程序的解析樹），都保存而利用於調試。但是它作為一個真實的持久存儲機制，也有一個嚴重的缺點。首先，開發者可能經常想要隱藏實現細節，並使它們在運行時間不可獲得。出於法律和維護的原因，允許任何人在運行時間修改程序，對於在運行時間環境不暴露原始碼的編譯後的系統，不可避免的介入複雜性和潛在的錯誤。其次，儘管持久存儲機制易於使用，它缺乏多數多用戶系統需要的真正持久存儲能力。最明顯的是進行同多個使用者並行訪問相同的資料庫的事務。

## 實現列表

### OpenSmaltalk

OpenSmaltalk VM（OS VM）是Smalltalk運行時環境的著名實現，很多現代Smalltalk VM基於或派生自它。OS VM自身是從一組Smalltalk原始碼文件（它們叫做VMMaker），轉譯成原生C語言原始碼（通過使用叫做Slang的轉譯器），它依次再針對特定平台和硬體架構來編譯，實際上確使Smalltalk映像的跨平台執行。原始碼可以在GitHub上獲得並在MIT許可證下發布。OS VM的知名派生者有：

- Squeak，一個開源Smalltalk。
- Pharo Smalltalk，一個開源跨平台語言。
- Croquet VM，Croquet OS的一個與Squeak有關的Smalltalk VM。
- Cuis-Smalltalk，一個開源的小型、簡潔和適用的Smalltalk。

### JavaScript VM

- PharoJS，一個開源的從Smalltalk到Javascript的轉譯器，擴展了Pharo環境。
- SqueakJS，用於web的一個OpenSmalltalk兼容的VM，還能運行老舊的Squeak應用如Etoys (程式語言)或Scratch。

### 其他

- Amber Smalltalk，通過轉譯運行在JavaScript上。
- Cincom Smalltalk，包含下列產品：VisualWorks、ObjectStudio和WebVelocity。
- Visual Smalltalk Enterprise，一個家族，包括Smalltalk/V。
- Smalltalk/X，由Claus Gittinger開發。
- F-Script (程式語言)，在2009年寫的只用於macOS的實現。
- Gemstone (資料庫)，GemTalk系統出品。
- GNU Smalltalk，Smalltalk的無頭（缺少GUI）實現。
- StepTalk，GNUstep腳本框架，它在Objective-C運行時上使用Smalltalk語言。
- VisualAge Smalltalk。
- VAST平台（VA Smalltalk），Instantiations公司開發。
- Little Smalltalk。
- Dolphin Smalltalk，Object Arts出品。
- Smalltalk MT，Object Connect出品的Windows平台Smalltalk。
- Pocket Smalltalk，運行於Palm Pilot。
- SmallJ，一個開源的基於Java的Smalltalk，派生自SmallWorld。
- Etoys (程式語言)，用於學習的可視編程系統。
- Strongtalk，提供可選的強類型。
- TruffleSqueak，用於GraalVM的一個Squeak/Smalltalk VM和Polyglot編程環境（更多基於GraalVM的Smalltalk實現可見於官網)

## 外部連結

- Free Online Smalltalk Books
- Smalltalk Zoo
- Smalltalk-80 Bluebook implementations in C++: by dbanay and rochus-keller on github

## 基本資料

- 編程范型：面向對象，反射式
- 發行時間：1972年（開發始於1969年）
- 設計者：Alan Kay、Dan Ingalls、Adele Goldberg
- 開發者：Alan Kay、Dan Ingalls、Adele Goldberg、Ted Kaehler、Diana Merry、Scott Wallace、Peter Deutsch、Xerox PARC其他人
- 軟體版本週期：Smalltalk-80版本2（1983年）
- 類型系統：強、動態
- 作用域：詞法（靜態）
- 平台：Xerox Alto
- 軟體實現：Amber Smalltalk, Dolphin Smalltalk, GemStone/S, GNU Smalltalk, Pharo, Smalltalk/X, Squeak, VisualAge, VisualWorks
- dialects：Self, GNU Smalltalk
- 受影響於：Lisp, Simula, Euler, IMP, Planner, Logo,Sketchpad, ARPAnet,
- 施影響於：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
- 作業系統：跨平台