Self编程语言

**Self 語言**，是一種基於原型的物件導向的程式設計語言，也是一個整合式開發環境和執行環境，由 David Ungar 和 Randy Smith，最初在 1986 年於全錄帕羅奧多研究中心設計。Self 語言在 Smalltalk 的基礎上，去除了變數，從而徹底體現了一切都是物件的風格。在實現 Self 系統的過程中，設計研究人員發展出了一種動態自適應編譯技術。

## 簡介

Self 語言把在概念上精簡 Smalltalk 作為設計原則。它在把訊息作為最基本的操作的同時，取消了類的概念，只有物件的概念。它把物件的特性，理解為取得或更改特性的這兩種方法，從而把特性的概念簡化為方法，即取消了變數及其賦值，並以通過訊息來讀槽和寫槽的方式來取代之。Self 提出了特質的概念，用動態繫結實現了委託。

儘管 Self 系統一次執行在一個行程中，但實際上可以分成兩個部分：Self 虛擬機器和 Self 世界（Self world）。Self 世界是一個 Self 物件庫，Self 物件包括資料物件和方法物件，方法物件的代碼部份，是用一種指令非常簡單的位元組碼（bytecode）表示的，位元組碼由 Self 虛擬機器來解釋。當 Self 程式從終端、檔案或者圖形化使用者介面輸入到系統之中時，Self 系統把源程式解析轉化為 Self 世界裡的物件。

動態自適應編譯技術的採用，提高了 Self 代碼的執行效率。對經常執行的方法，虛擬機器將進一步把位元組碼轉化為本機代碼。Self 虛擬機器還提供了一些可供呼叫的原語，用來實現算術運算、物件複製、輸入輸出等。

Self 也擁有一個圖形化使用者介面 Morphic，Self 的編程環境，也是基於 Morphic 來實現的。Self 在精簡語言概念的同時，也把大量的工作轉交給環境來處理，語言中的反射機制也同環境密切相關。

## 歷史

在 1986 年，David Ungar 和 Randy Smith 在全錄帕羅奧多研究中心，提出了 Self 語言的最初設計，並在 1987 年的 OOPSLA'87 的論文《Self：簡單性的能力》中給出了描述，此文在 2006 年被評為 1986 年到 1996 年間三個最有影響的 OOPSLA 論文之一。

1987 年初，Craig Chambers、Elgin Lee 和 Martin Rinard，在 Smalltalk 上給出了 Self 的第一個實驗性直譯器。1987 年夏，Self 專案在史丹福大學正式開始，1988 年夏給出了第一個有效率的實現，並發布了 1.0 和 1.1 兩個版本。1991 年初，Self 專案移至昇陽電腦，並且在 1992 年發布了 2.0 版。1993 年 1 月，Self 3.0 版發布。

1995 年 7 月，Self 4.0 版發布。在這個版本中包括了一個全新的圖形使用者環境 Morphic。在 2016 年發行了 4.3 版本並可執行在 Mac OS X 和 Solaris 上。在 2010 年發行了版本 4.4，由最初團隊的某些人和獨立編程者形成的小組開發，它和所有後續版本可以執行在 Mac OS X 和 Linux 上。2014 年 1 月發行了 4.5 版本。2017 年 5 月發行了版本 2017.1。

Self 的發展基本已經停滯，但在發展 Self 過程中探索出的一些技術，在其他的系統中得到了應用。在 Self 的實現中採用的各種編譯最佳化技術，直接導致了 Java Hotspot 虛擬機器的產生；在 Smalltalk 的一個實現 Squeak 中，採用了 Self 圖形化使用者介面 Morphic 的設計方案，放棄了 Smalltalk-80 中採用的 MVC 的方案。Self 是對 JavaScript 程式語言設計有最主要影響者之一。

## 基於原型編程

傳統的類別為基的物件導向語言，基於了根深蒂固的二元性：

1. 類，定義物件的基本品質（quality）和行為（Behavior）。
2. 物件實例，是類的特定體現（manifestation）。

例如，假設類 `Vehicle`（車輛）的物件有一個「名字」，和進行各種動作的能力，比如「開車上班」和「運送建材」。`Bob's car` 是類 `Vehicle` 的特定物件（實例），它的「名字」是「Bob's car」。在理論上，你可以向 `Bob's car` 傳送訊息，告訴它去「運送建材」。

這個例子展示了這種方式的一個問題：Bob 的汽車，恰巧是一個跑車，在任何意義上都不能裝載和運送建材，但這是建模 `Vehicle` 所必須擁有的能力。通過從 `Vehicle` 建立特殊化的子類，可產生一個更有用的模型；比如建立類 `SportsCar`（跑車）和類 `FlatbedTruck`（平板卡車）。只有 `FlatbedTruck` 的實例需要提供「運送建材」的機能；`SportsCar` 的實例不適合這種工作，它只需要「快速行駛」。但是，這種深入建模在設計期間，需要更多的洞察力，洞察那些可能只在引起了問題時才顯現出的事情。

這個問題是在原型（prototype）這個概念背後的動機因素之一。除非你能必然性的預測出一組物件和類，在遙遠未來時所要有的品質，你不能恰當的設計好一個類的層級。程式最終需要增加行為，實在是太頻繁了，而系統的很多節段將需要重新設計（或重新構建），來以不同的方式迸發出物件。早期的物件導向語言如 Smalltalk 的實驗，顯示出這種問題反反覆覆的出現。系統趨向於增長到一定程度後，就變得非常僵化，因為在編程者的代碼下的深層的基本類，簡直就像是逐漸變成了一個「錯誤」；沒有變更原來的類的容易方式，就會出現嚴重的問題。

動態語言如 Smalltalk，允許通過周知的按照類的方法進行這種變更；即通過改變類，基於它的物件就可以改變它們的行為。但是，進行這種變更必須非常小心，因為基於相同類的其他物件，可能把它當作「錯誤行為」：「錯誤」經常是依賴於場景的，這是脆弱基礎類別問題的一種形式。進一步的說，在靜態語言如 C++ 中，這裡的子類可以從超類分別的編譯，對超類的變更實際上可以破壞預編譯的子類別方法；這是脆弱基礎類別問題的另一種形式，也是脆弱二進位介面問題的一種形式。

在 Self 和其他原型程式設計語言中，消除了在類和物件之間的這種二元性。不再有基於某種「類」的一個物件「實例」，在 Self 中，你可以複製一個現存的物件，並改變它。故而 `Bob's car` 可以通過製作現存的 `Vehicle` 物件的複本來建立，並增加「快速行駛」方法，建模它恰好是一輛保時捷 911 的事實。

主要用來製作複本的基本物件叫做「原型」。這種技術被稱為是一種非常簡化的機制。如果一個現存的物件或物件的集合，被證明是個不適當的模型，編程者可以簡單的建立有正確行為的一個修改的物件，並轉而使用它。使用現存物件的代碼不會改變。

## 語法和語意

下面簡要描述 Self 語言的語法和語意。

### 物件

文字（literal）包括：數、用 `'` 包圍起來的字串物件，塊和一般的物件。物件文字用圓括號來界定。在圓括號內，物件描述構成自豎槓 `|` 界定的一個槽列表，隨後是在這個物件被求值時要執行的代碼。例如：

槽（slot）是名字 - 值對，槽包含到其他物件的參照。槽列表由點號分隔的（可以為空的）一序列的槽描述符組成。在槽列表結束處的點號是可選的。槽描述符（descriptor）有兩種：

* `槽 <- 表达式`，指示將指名的資料槽初始化為求值表達式的結果，它有相同名字附加冒號的包含賦值原語的賦值槽，這兩個槽對應於其他語言中的一個讀寫變數。
* `槽 = 表达式`，指示將指名的資料槽初始化為求值表達式的結果，這個槽對應其他語言的一個唯讀變數。

在 Self 中沒有單獨的賦值運算。其他物件導向語言中的存取子方法對應資料槽，變異子方法對應賦值槽。假如 `myPerson` 物件中有個叫做 `name` 的資料槽，則通過 `myPerson name`，可返回在這個槽中的值；如果它有對應的賦值槽 `name:`，則通過 `myPerson name: 'foo'`，可設定資料槽 `name` 的值為 `'foo'`。

任何槽都可以通過增加星號字尾，來製成父槽。星號不是槽名字的一部份，在將名字與訊息進行匹配時候忽略它。例如一個初始化了的可變的點可以定義為：

一個物件的代碼是以點號分隔的一序列的表達式。尾隨的點號是可選的。每個表達式有一系列的訊息傳送和文字組成。在一個物件的代碼中最後的表達式，可以前導著指示返回的 `^` 算符。

一個真正的空物件，指示為 `(| |)` 或簡單的 `()`，它根本不接收任何訊息。

### 方法

方法（method）是除了參數槽及或局部槽之外，還包含代碼的物件。參數（argument）槽名字開始於一個冒號，它不是槽名字的一部分，在將名字與訊息進行匹配時候忽略它。參數槽總是唯讀的，並且不能對它們指定初始化者。下面例子是計算平方的方法物件：

一個普通方法（簡稱方法），是不嵌入到其他代碼之中的方法，它只能存放在唯讀槽中。普通方法總是有一個叫做 `self` 的隱含的父參數槽。Self 的普通方法等價於 Smalltalk 的方法。

如果一個槽包含一個方法，在求值這個槽來回應發來的訊息的時候，這個方法物件被淺層複製（clone），從而新建它的一個活動（activation）物件，它包含這個方法的參數槽和局部槽；複製體的 `self` 父槽，初始化為這個訊息的接收者；複製體如果有參數槽，將它們初始化為實際參數；在這個新的活動物件的上下文中，執行這個方法的代碼。

作為語法約定，參數名字可以直接寫在槽名字之後（不帶字首冒號），從而隱含的聲明參數槽。例如下面的方法定義：

可以等價的定義為：

返回算符 `^` 的出現或缺席，不影響普通方法的行為，因為普通方法總是會返回它最終的表達式的值。

### 訊息

通過訊息存取槽的語法，類似於 Smalltalk，有三類訊息可以獲得：

**一元**
: `接收者 槽名字`

**二元**
: `接收者 算符 参数`

**關鍵字**
: `接收者 关键字1: 参数1 关键字2: 参数2`

所有訊息都返回結果，所以顯式指定的接收者和參數自身可以是其他訊息的結果。下面是 Self 版本的 hello world 程式例子：

組合可以通過使用圓括號來進行強制。在缺乏明確組合的情況下，一元訊息具有最高優先級，其次是二元訊息，而關鍵字訊息最低。一元訊息從左至右複合。二元訊息對於同一個算符從左至右結合，例如 `3 + 4 + 7` 被解釋為 `(3 + 4) + 7`，而對於不同的算符沒有結合性，例如 `3 + 4 * 7` 是非法的，而必須顯式的寫為要麼 `(3 + 4) * 7` 要麼 `3 + (4 * 7)`。

關鍵字訊息的第一部份必須開始於小寫字母，而後續部份都必須開始於大寫字母。例如表達式：

是一個單一的訊息 `min:Max:`，它被傳送給 `5` 並具有參數 `4` 和 `7`，而表達式：

涉及兩個訊息：第一個訊息 `max:` 被傳送給 `4` 並接受 `7` 作為它的參數，而接下來訊息 `min:` 被傳送給 `5`，並接受 `4 max: 7` 的結果作為它的參數。關鍵字訊息從右至左結合，例如：

被解釋為：

由於很多訊息被傳送給當前訊息接收者 `self`，故而可以將 `self` 作為隱含接收者而不需要顯式的寫出。例如計算點的加法的一個方法：

可以被無歧義的分析，其含義同於：

這裡出現了三個隱含接收者一元訊息 `clone`、`x` 和 `y`。

### 塊

塊是 Self 的閉包，Self 就像 Smalltalk，使用「塊」用於控制流程和其他職責。塊文字的寫法，除了方括號替代了圓括號之外，類似於其他物件文字。例如嵌入在下列表達式中的塊：

一個塊文字定義兩個物件：一個塊資料物件，和它包圍的一個塊方法物件。

* 塊資料物件，它有包含塊方法物件的一個槽，其選擇子也就是槽名字，由參數數目決定，沒有參數是 `value`，一個參數是 `value:`，兩個參數是 `value:With:`，隨著參數增多 `With:` 個數也隨之遞增。此外，它還有一個叫 `parent*` 的父槽，指向含有塊物件都共享的行為的那個物件（`traits block`）。
* 塊方法物件，它含有這個塊的代碼。不同於普通方法物件，它不包含 `self` 槽，轉而有一個匿名父槽，它被初始化指向在詞法上處於外圍的塊或方法的活動物件。匿名的含義，是這個槽的名字在 Self 層面是不可見的，而不能顯式的存取。作為結果，在一個塊方法內傳送的隱含接收者訊息，被限定在這個塊所在表達式的詞法作用域之內，而非這個塊經過可能有的多次轉送，最終向它傳送適當的 `value` 訊息變體之時的那個作用域。

相應的，塊求值分為如下兩個階段：

* 在這個塊被求值的時候，即它被用作傳送訊息的參數，比如例子中 `to:By:Do:` 訊息的參數之時，建立塊物件（即塊資料物件）；這個塊被淺層複製（clone），並將指向這個塊在詞法上外圍的活動記錄，即當前的活動記錄的一個指標交給它匿名儲存。
* 在向這個塊傳送適當的 `value` 訊息變體的時候，求值這個塊方法；這個塊方法接著被淺層複製，並填充此複製體的方法槽，使用第一階段確定的指標來初始化匿名父槽，最後執行這個塊的代碼。

在塊中，返回算符 `^` 導致從包含這個塊的普通方法中返回控制權，立即終止這個方法的活動，這個塊的活動，和在其間的所有活動。這種返回叫做「非局部返回」，因為它可以穿越很多活動。普通方法求值的結果，是非局部返回所返回的值。

### 委託

在理論上，所有 Self 物件都是獨立實體，Self 既沒有類也沒有元類。對任何特定物件的變更，都不影響任何其他物件，但是在某些情況下，卻需要它們有關聯。正常的一個物件，只能理解對應於它的局部槽的訊息，但擁有一個或更多的指示父（parent）物件的槽，物件可以將任何自身不理解的訊息，委託（delegate）給父物件。

Self 採用這種方式，處理在類別為基的語言中使用繼承來擔負的責任。委託還可以用來實現一些特徵，比如命名空間和詞法作用域。通過下面的例子展示委託與傳統的類的不同之處：

這個句子通過改變與叫做 `parent` 的父槽關聯的值，在執行時間改變 `myObject` 的「類」。不同於繼承或詞法作用域，委託物件可以在執行時間修改。

### 特質

例如，假定在一個簡單的帳簿應用中，定義了一個物件叫做「銀行帳號」（bank account）。通常建立的這個物件，具有內部的方法，比如說「存款」（deposit）和「取款」（withdraw），和任何它所需要的資料槽，比如說「餘額」（balance）。這只是一個原型，它只在使用方式上特殊，因為它恰好是一個全功能的銀行帳號。

為「Bob 的帳戶」製作銀行帳號物件的複製品（clone），將建立一個新物件，它在起初時完全同於原型。在這種情況下，將複製（copy）包括方法和任何資料的槽。但更常用的解決方案，是首先建立叫做它的特質（trait）物件的一個簡單物件，它包含通常與一個類有關的專案。

在這個例子中，「銀行帳戶」將沒有存款和取款方法，而是委託給一個父物件來做這些。採用這種方式，可以製作銀行帳號物件的很多複本，但是我們仍可以通過改變它所委託的特質物件中的槽，來改變它們全體的行為。

### Self 世界

當處在於提示符下鍵入表達式的場景時，由叫做「大廳」（lobby）的一個物件，引領使用者進入 Self 世界。當建立一個新物件的指令碼被讀入系統的時候，指令碼中的表達式都在大廳的上下文中求值。就是說大廳是這個指令碼中所有傳送給 `self` 的訊息的接收者。

要參照在指令碼中的某個現存的物件，必須通過傳送一個訊息到大廳才可以存取到它。大廳的 `traits`、`globals` 和 `mixins` 槽，是從大廳可以存取的物件命名空間的根。大廳的 `lobby` 槽允許大廳自身通過名字來提及。路徑名字是一個一元選擇子的序列，它描述從大廳到這個物件的路徑。路徑名字也是可以在大廳的上下文中求值的表達式，它產出這個物件。

例如，原型列表的完全路徑名字是 `globals list`。因為 `globals` 是父槽，它可以從路徑名字表達式中省略，生成簡短路徑名字 `list`。大廳的 `traits` 不是父槽，特質物件的名字必須開始於字首 `traits`，因此列表的特質物件必須稱呼為 `traits list`。

不是所有物件都有路徑名字，只有那些從大廳可以到達的物件才有，這些物件稱為「周知的」。大廳向使用者提供三類物件：

* 特質（traits）物件，封裝共享行為的物件，典型的每個原型物件，都有一個關聯的同名特質物件，用來描述它的行為的共享部份。任何 Self 實現都需要為整數、浮點數、字串和塊提供特質物件。Sefl 世界中多數具體物件衍生自兩種個頂層特質物件：不唯一性物件承襲自 `traits clonable`，而唯一性物件承襲自 `traits oddball`。唯一性的物件通過返回自身，來回應訊息 `copy`，並使用同一性來測試相等。
* 全域（globals）物件，即不唯一性的原型物件，和「每種只有一個」的獨特（`oddball`）物件。一些物件比如 `true`、`false` 和 `nil` 是唯一性的，在系統中它們只需要有一個。因為一個 `oddball` 不需要在它的很多實例間共享它的行為，它不需要有分立的特質物件和原型物件。很多 `oddball` 物件從 `traits oddball` 繼承 `copy` 方法，它返回物件自身而非一個新複本。
* 混入（mixins）物件，即小而無父物件的行為束（bundle），是持有共享行為的一種物件，通常用來混入那些有其他父物件的物件之中。混入物件的一個例子是 `mixins identity`。兩個物件測試相等，通常基於在一個共同的域（domain）內是否有相同的值。例如，在數的域內 `3.0 = 3`，即使它們不是相同的物件甚至不是同種類的物件。但是在一些域中，兩個物件相等若且唯若它們是相同的物件，例如兩個行程即使有相同的狀態也不被當作是相等除非它們是同一個。在這種情況上，使用同一性比較來實現相等測試，並混入 `mixins identity` 來得到想要的行為。

在大廳的 `defaultBehavior` 槽中，定義了系統中大多數物件所繼承的預設行為。

### 新建物件

有兩個訊息與物件複製有關：

* `clone`，淺層複製，返回包含著與最初物件完全相同的槽和代碼的一個新物件。它用在物件內部，客戶應當使用 `copy`。
* `copy`，複製接收者，可能具有嵌入的複製或初始化。

考慮一個圖形化使用者介面有關的例子：

首先進行的是 `desktop activeWindow`，它從 `desktop`（桌面）物件擁有的一個窗口列表中，返回 `activeWindow`（活動窗口）。按從內向外從左至右的次序，接著是 `labelWidget copy label: 'Hello, World!'`，通過 `copy` 訊息製作 `labelWidget`（標籤組件）物件的一個複本，接著向它傳送一個訊息，將 `Hello, World!` 放入它的叫做 `label`（標籤）的槽中。最後將返回的這個組件，傳送到這個活動窗口的 `draw`（繪製）槽中。

### 增加槽

在 Self 中的物件，可以通過包括新加的槽來修改。這可以通過被推薦使用的圖形編程環境來做，或者直接使用原語 `_AddSlots:`。原語與正常關鍵字訊息有相同的語法，但是它的名字開始於底線字元。給 `_AddSlots:` 的參數是一個物件文字，它的槽將被複製進入接收者。例如，在大廳中新增叫做 `newObject` 的物件，並初始化這裡舉出的它的叫做 `entries` 的槽，採用如下這樣的表達式：

因為 `_AddSlots:` 原語未指定接收者，這裡的訊息隱含接收者 `self` 是大廳，由它來理解產生初始值的訊息 `list copy`，`list` 是周知的原型物件。

## 例子

在下面的例子中，將類別為基語言中叫做 `Vehicle`（車輛）的一個簡單類，重新構造為 Self 中的 `vehicle` 物件，從而能夠區分出在轎車和卡車之間共有的行為：

在大廳中建立了一個叫做 `vehicle` 的槽，它的值是一個物件文字，同時還建立了一個叫做 `vehicle:` 的賦值槽。作為 `vehicle` 槽初始值的物件文字，也就是新建的叫做 `vehicle` 的物件，包括了一個單一的父槽 `parent`，沒有相應的 `parent:`，它委託了 `traits clonable` 物件，這個頂層的特質物件可以理解與複製有關的訊息。

然後向這個新建物件繼續增加 `name` 槽和相應的 `name:` 槽：

在大廳中從 `vehicle` 物件建立一個 `sportsCar`（跑車）物件，並接著向 `sportsCar` 增加 `vehicle` 沒有的一個新的方法槽 `driveToWork`（開車上班）：

在大廳中從 `sportsCar` 物件建立一個 `porsche911`（保時捷 911）物件，接著向新建物件 `porsche911` 傳送一個訊息改變它的 `name` 槽的值：

物件 `porsche911` 與它的原型物件 `sportsCar`，仍有著完全相同的槽，但是其中的一個槽有著不同的值。

## 環境

Self 的一個特徵，是它基於了早期 Smalltalk 系統所用的某種虛擬機器系統。就是說，程式不是像 C 語言中那樣的獨立實體，而是需要它們的整體記憶體環境來執行。這要求應用程式被裝載入儲存記憶體的大塊（chunk）之中，這叫做「快照」或映像。這種方式的缺點，是映像有時很大並且笨重；但是除錯一個映像，經常被除錯一個傳統程式要簡單，因為執行時狀態更容易檢查和修改。在基於原始碼和基於映像的開發之間的不同，是類似於在類別導向的和面向原型的物件導向程式設計之間的區別。

此外，環境是為了讓在系統之中的物件能快速和可持續的變更而客製化的。重新構建一個「類」設計，就像從現存的祖先拖動出來方法放入新造的之中一樣容易。簡單任務像測試方法，可以通過製作複本來處理，拖動方法進入這個複本，接著變更它。不同於傳統系統，只有變更了的物件有新代碼，不需要重建任何東西來測試它。如果這個方法有效，可以簡單的把它拖動回祖先之中。

### 效能

Self 的 VM 實現的效能，在某些測試之中大約是最佳化的 C 程式速度的一半。這是通過即時編譯技術達到的，它是在 Self 研究中首創並改進的，能夠使高階語言表現得這麼好。

### 垃圾收集

Self 的垃圾收集器使用分代垃圾回收，它按年齡分離物件。通過使用記憶體管理系統記錄頁面寫，可以維護一個寫屏障。這個技術給出了卓越的效能，儘管在執行一些時間之後，出現完全的垃圾收集，要花相當可觀的時間。

### 最佳化

執行系統選擇性的扁平化呼叫結構。這給出適當的自身提速，但允許了對不同呼叫者類型的類型資訊和多版本的代碼的大量快取。這去除了對做很多方法尋找的需要，並允許條件分支語句和寫死呼叫被插入，這經常能給出類似 C 語言的效能，而又不失去語言層面的通用性，但要建立在完全的垃圾收集系統之上。

## 參照

## 延伸閱讀

* Published papers on Self（頁面存檔備份，存於網際網路檔案館）
* Chambers, C., The Design and Implementation of the SELF Compiler, an Optimizing Compiler for Object-Oriented Programming Languages, Stanford University, 1992
* Series of four articles "Environment and the programming language Self"（頁面存檔備份，存於網際網路檔案館）

## 站外部連結接

* 官方網站
* GitHub 上的 self 頁面
* Prototype-Based Application Construction Using SELF 4.0（頁面存檔備份，存於網際網路檔案館）
* Automated Refactoring application on sourceforge.net, written for and in Self（頁面存檔備份，存於網際網路檔案館）
* Gordon's Page on Self（頁面存檔備份，存於網際網路檔案館）
* dSelf: distributed extension to the delegation and language Self（頁面存檔備份，存於網際網路檔案館）