Self

Self語言，是一種基於原型的面向物件的程式設計語言，也是一個整合開發環境和執行環境，由David Ungar和Randy Smith，最初在1986年於施樂帕羅奧多研究中心設計。Self語言在Smalltalk的基礎上，採用“槽”取代了“變數”，從而徹底體現了一切都是物件的風格。在實現Self系統的過程中，設計研究人員發展出了一種動態自適應編譯技術。簡介.Self語言把在概念上精簡Smalltalk作為設計原則。它在把訊息作為最基本的操作的同時，取消了類的概念，只有物件的概念。它把物件的特性，理解為獲取或更改特性的這兩種方法，從而把特性的概念簡化為方法，並且透過訊息來讀槽和寫槽的方式，取代了變數及其賦值。Self提出了特質的概念，用實現了委託。儘管Self系統一次執行在一個程序中，但實際上可以分成兩個部分：Self虛擬機器和Self世界。Self世界是一個Self物件庫，Self物件包括資料物件和方法物件，方法物件的程式碼部份，是用一種指令非常簡單的位元組碼錶示的，位元組碼由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專案移至Sun微系統，並且在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程式語言設計有最主要影響者之一。基於原型程式設計.傳統的基於類的面嚮物件語言，基於了根深蒂固的二元性：例如，假設車輛類codice_1的物件有一個“名字”，和進行各種動作的能力，比如“開車上班”和“運送建材”。codice_2是類codice_1的特定物件，它的“名字”是“Bob's car”。在理論上，你可以向codice_2傳送訊息，告訴它去“運送建材”。這個例子展示了這種方式的一個問題：Bob的汽車，恰巧是一個跑車，在任何意義上都不能裝載和運送建材，但這是建模codice_1所必須擁有的能力。透過從codice_1建立特殊化的子類，可產生一個更有用的模型；比如建立跑車類codice_7和平板卡車類codice_8。只有codice_8的例項需要提供“運送建材”的機能；codice_7的例項不適合這種工作，它只需要“快速行駛”。但是，這種深入建模在設計期間，需要更多的洞察力，洞察那些可能只在引起了問題時才顯現出的事情。這個問題是在原型這個概念背後的動機因素之一。除非你能必然性的預測出一組物件和類，在遙遠未來時所要有的品質，你不能恰當的設計好一個類的層級。程式最終需要增加行為，實在是太頻繁了，而系統的很多節段將需要重新設計或重新構建，來以不同的方式迸發出物件。早期的面嚮物件語言如Smalltalk的實驗，顯示出這種問題反反覆覆的出現。系統趨向於增長到一定程度後，就變得非常僵化，因為在程式設計者的程式碼下的深層的基本類，簡直就像是逐漸變成了一個“錯誤”；沒有變更原來的類的容易方式，就會出現嚴重的問題。動態語言如Smalltalk，允許透過周知的按照類的方法進行這種變更；即透過改變類，基於它的物件就可以改變它們的行為。但是，進行這種變更必須非常小心，因為基於相同類的其他物件，可能把它當作“錯誤行為”：“錯誤”經常是依賴於場景的，這是問題的一種形式。進一步的說，在靜態語言如C++中，這裡的子類可以從超類分別的編譯，對超類的變更實際上可以破壞預編譯的子類方法；這是脆弱基類問題的另一種形式，也是的一種形式。在Self和其他基於原型的程式語言中，消除了在類和物件之間的這種二元性。不再有基於某種“類”的一個物件“例項”，在Self中，你可以複製一個現存的物件，並改變它。故而codice_2可以透過製作現存的codice_1物件的複本來建立，並增加“快速行駛”方法，建模它恰好是一輛保時捷911的事實。主要用來製作複本的基本物件叫做“原型”。這種技術被稱為是一種非常簡化的機制。如果一個現存的物件或物件的集合，被證明是個不適當的模型，程式設計者可以簡單的建立有正確行為的一個修改的物件，並轉而使用它。使用現存物件的程式碼不會改變。語法和語義.下面簡要描述Self語言的語法和語義。物件.包括：數、用codice_13包圍起來的字串物件，塊和一般的物件。物件文字用圓括號來界定。在圓括號內，物件描述構成自豎槓codice_14界定的一個槽列表，隨後是在這個物件被求值時要執行的程式碼。例如：(| 槽1. 槽2 | 一些程式碼 )槽有兩種：在Self中沒有單獨的賦值運算。其他面嚮物件語言中的訪問子方法對應資料槽，變異子方法對應賦值槽。假如codice_17物件中有個叫做codice_18的資料槽，則透過，可返回在這個槽中的值；如果它有對應的賦值槽codice_19，則透過，可設定資料槽codice_18的值為codice_21。任何槽都可以透過增加星號字尾，來製成父槽。星號不是槽名字的一部份，在將名字與訊息進行匹配時候忽略它。例如一個初始化了的可變的點可以定義為：(| parent* = traits point.x &lt;- 3 + 4.y &lt;- 5.一個物件的程式碼是以點號分隔的一序列的表示式。尾隨的點號是可選的。每個表示式有一系列的訊息傳送和文字組成。在一個物件的程式碼中最後的表示式，可以前導著指示返回的codice_22算符。一個真正的空物件，指示為codice_23或簡單的codice_24，它根本不接收任何訊息。訊息.透過訊息訪問槽的語法，類似於Smalltalk，有三類訊息可以獲得：所有訊息都返回結果，所以顯式指定的接收者和引數自身可以是其他訊息的結果。下面是Self版本的hello world程式例子：'Hello, World!' print.組合可以透過使用圓括號來進行強制。在缺乏明確組合的情況下，一元訊息具有最高優先順序，其次是二元訊息，而關鍵字訊息最低。一元訊息從左至右複合。二元訊息對於同一個算符從左至右結合，例如codice_28被解釋為codice_29，而對於不同的算符沒有結合性，例如codice_30是非法的，而必須顯式的寫為要麼codice_31要麼codice_32。關鍵字訊息的第一部份必須開始於小寫字母，而後續部份都必須開始於大寫字母。例如表示式：5 min: 4 Max: 7是一個單一的訊息codice_33，它被髮送給codice_34並具有引數codice_35和codice_36，而表示式：5 min: 4 max: 7涉及兩個訊息：第一個訊息codice_37被髮送給codice_35並接受codice_36作為它的引數，而接下來訊息codice_40被髮送給codice_34，並接受codice_42的結果作為它的引數。關鍵字訊息從右至左結合，例如：5 min: 6 min: 7 Max: 8 Max: 9 min: 10 Max: 11被解釋為：5 min: (6 min: 7 Max: 8 Max: (9 min: 10 Max: 11))由於很多訊息被髮送給當前訊息接收者codice_43，故而可以將codice_43作為隱含接收者而不需要顯式的寫出。方法.方法槽名字開始於一個冒號，它不是槽名字的一部分，在將名字與訊息進行匹配時候忽略它。引數槽總是隻讀的，並且不能對它們指定初始化者。下面例子是計算平方的方法物件：一個普通方法，是不嵌入到其他程式碼之中的方法，它只能存放在只讀槽中。普通方法總是有一個叫做codice_43的隱含的父引數槽。Self的普通方法等價於Smalltalk的方法。如果一個槽包含一個方法，在求值這個槽來響應發來的訊息的時候，這個方法物件被物件，它包含這個方法的引數槽和區域性槽；複製體的codice_43父槽，初始化為這個訊息的接收者；複製體如果有引數槽，將它們初始化為實際引數；在這個新的活動物件的上下文中，執行這個方法的程式碼。例如計算點的加法的一個方法：(| + arg = ( (clone x: x + arg x) y: y + arg y )可以被無歧義的分析，其含義同於：(| + = (| :arg | (clone x: ((x + (arg x)))) y: ((y + (arg y))) ).這裡出現了三個隱含接收者一元訊息codice_47、codice_48和codice_49。作為語法約定，引數名字可以直接寫在槽名字中對應關鍵字之後，它不再帶有字首冒號，從而隱含的宣告引數槽。例如下面的方法定義：(| ifTrue: False: = (| :b1. :b2 | b1 value ).可以等價的定義為：(| ifTrue: b1 False: b2 = ( b1 value ).返回算符codice_22的出現或缺席，不影響普通方法的行為，因為普通方法總是會返回它最終的表示式的值。塊.塊是Self的閉包，Self就像Smalltalk，使用“塊”用於控制流程和其他職責。塊文字的寫法，除了方括號替代了圓括號之外，類似於其他物件文字。例如嵌入在下列表達式中的塊：1 to: 5 * i By: 2 * j Do: [| :k | k print ]一個塊文字定義兩個物件：一個塊資料物件，和它包圍的一個塊方法物件。相應的，塊求值分為如下兩個階段：在塊中，返回算符codice_22導致從包含這個塊的普通方法中返回控制權，立即終止這個方法的活動，這個塊的活動，和在其間的所有活動。這種返回叫做“非區域性返回”，因為它可以穿越很多活動。普通方法求值的結果，是非區域性返回所返回的值。委託.在理論上，所有Self物件都是獨立實體，Self既沒有類也沒有元類。對任何特定物件的變更，都不影響任何其他物件，但是在某些情況下，卻需要它們有關聯。正常的一個物件，只能理解對應於它的區域性槽的訊息，但擁有一個或更多的指示父給父物件。Self採用這種方式，處理在基於類的語言中使用繼承來擔負的責任。委託還可以用來實現一些特徵，比如名稱空間和詞法作用域。透過下面的例子展示委託與傳統的類的不同之處：myObject parent: someOtherObject.這個句子透過改變與叫做codice_62的父槽關聯的值，在執行時間改變codice_63的“類”。不同於繼承或詞法作用域，委託物件可以在執行時間修改。特質.例如，假定在一個簡單的賬簿應用中，定義了一個物件叫做“銀行帳號”。這只是一個原型，它只在使用方式上特殊，因為它恰好是一個全功能的銀行帳號。為“Bob的賬戶”製作銀行帳號物件的複製品物件的一個簡單物件，它包含通常與一個類有關的專案。在這個例子中，“銀行賬戶”將沒有存款和取款方法，而是委託給一個父物件來做這些。採用這種方式，可以製作銀行帳號物件的很多複本，但是我們仍可以透過改變它所委託的特質物件中的槽，來改變它們全體的行為。Self世界.當處在於提示符下鍵入表示式的場景時，由叫做“大廳”的一個物件，引領使用者進入Self世界。當建立一個新物件的指令碼被讀入系統的時候，指令碼中的表示式都在大廳的上下文中求值。就是說大廳是這個指令碼中所有傳送給codice_43的訊息的接收者。要引用在指令碼中的某個現存的物件，必須透過傳送一個訊息到大廳才可以訪問到它。大廳的codice_65、codice_66和codice_67槽，是從大廳可以訪問的物件名稱空間的根。大廳的codice_68槽允許大廳自身透過名字來提及。路徑名字是一個一元選擇子的序列，它描述從大廳到這個物件的路徑。路徑名字也是可以在大廳的上下文中求值的表示式，它產出這個物件。例如，原型列表的完全路徑名字是codice_69。因為codice_66是父槽，它可以從路徑名字表達式中省略，生成簡短路徑名字codice_71。大廳的codice_65不是父槽，特質物件的名字必須開始於字首codice_65，因此列表的特質物件必須稱呼為codice_74。不是所有物件都有路徑名字，只有那些從大廳可以到達的物件才有，這些物件稱為“周知的”。大廳向用戶提供三類物件：在大廳的codice_89槽中，定義了系統中大多數物件所繼承的預設行為。新建物件.有兩個訊息與物件複製有關：考慮一個圖形使用者介面有關的例子：(desktop activeWindow) draw: (labelWidget copy label: 'Hello, World!').首先進行的是，它向桌面物件codice_91傳送訊息codice_92，從其擁有的一個視窗列表中返回活動視窗。按從內向外從左至右的次序，接著是，透過codice_77訊息製作標籤元件物件codice_94的一個複本，接著向它傳送一個訊息，將codice_95放入它的用作“標籤”的codice_96槽中。最後將返回的這個元件，傳送到這個活動視窗用於“繪製”的codice_97槽中。增加槽.在Self中的物件，可以透過包括新加的槽來修改。這可以透過被推薦使用的圖形程式設計環境來做，或者直接使用原語codice_98。原語與正常關鍵字訊息有相同的語法，但是它的名字開始於下劃線字元。給codice_98的引數是一個物件文字，它的槽將被複制進入接收者。例如，在大廳中新增叫做codice_100的物件，並初始化這裡舉出的它的叫做codice_101的槽，採用如下這樣的表示式：_AddSlots: (| newObject = (| entries &lt;- list copy ... |) |)因為codice_98原語未指定接收者，這裡的訊息隱含接收者codice_43是大廳，由它來理解產生初始值的訊息codice_104，codice_71是周知的原型物件。例子.在下面的例子中，將基於類語言中叫做codice_1的一個簡單的車輛類，重新構造為Self中的codice_107物件，從而能夠區分出在轎車和卡車之間共有的行為：_AddSlots: (| vehicle &lt;- (|parent* = traits clonable|) |).在大廳中建立了一個叫做codice_107的槽，它的值是一個物件文字，同時還建立了一個叫做codice_109的賦值槽。作為codice_107槽初始值的物件文字，也就是新建的叫做codice_107的物件，包括了一個單一的父槽codice_62，沒有相應的codice_113，它委託了codice_75物件，這個頂層的特質物件可以理解與複製有關的訊息。然後向這個新建物件繼續增加codice_18槽和相應的codice_19槽：vehicle _AddSlots: (| name &lt;- 'automobile'|).在大廳中從codice_107物件建立一個跑車物件codice_118，並接著向codice_118增加codice_107所沒有的用於“開車上班”的一個新的方法槽codice_121：_AddSlots: (| sportsCar &lt;- vehicle copy |).sportsCar _AddSlots: (| driveToWork = ("這個方法的程式碼") |).在大廳中從codice_118物件建立一個保時捷911物件codice_123，接著向新建物件codice_123傳送一個訊息改變它的codice_18槽的值：_AddSlots: (| porsche911 &lt;- sportsCar copy |).porsche911 name: 'Bobs Porsche'.物件codice_123與它的原型物件codice_118，仍有著完全相同的槽，但是其中的一個槽有著不同的值。環境.Self的一個特徵，是它基於了早期Smalltalk系統所用的某種虛擬機器系統。就是說，程式不是像C語言中那樣的獨立實體，而是需要它們的整體記憶體環境來執行。這要求應用程式被裝載入儲存記憶體的之中，這叫做“快照”或。這種方式的缺點，是映像有時很大並且笨重；但是除錯一個映像，經常被除錯一個傳統程式要簡單，因為執行時狀態更容易檢查和修改。在基於原始碼和基於映像的開發之間的不同，是類似於在面向類的和麵向原型的面向物件程式設計之間的區別。此外，環境是為了讓在系統之中的物件能快速和可持續的變更而定製的。重新構建一個“類”設計，就像從現存的祖先拖動出來方法放入新造的之中一樣容易。簡單任務像測試方法，可以透過製作複本來處理，拖動方法進入這個複本，接著變更它。不同於傳統系統，只有變更了的物件有新程式碼，不需要重建任何東西來測試它。如果這個方法有效，可以簡單的把它拖動回祖先之中。效能.Self的VM實現的效能，在某些測試之中大約是最佳化的C程式速度的一半。這是透過即時編譯技術達到的，它是在Self研究中首創並改進的，能夠使高階語言表現得這麼好。垃圾收集.Self的垃圾收集器使用分代垃圾回收，它按年齡分離物件。透過使用記憶體管理系統記錄頁面寫，可以維護一個寫屏障。這個技術給出了卓越的效能，儘管在執行一些時間之後，出現完全的垃圾收集，要花相當可觀的時間。最佳化.執行系統選擇性的扁平化呼叫結構。這給出適當的自身提速，但允許了對不同調用者型別的型別資訊和多版本的程式碼的大量快取。這去除了對做很多方法查詢的需要，並允許條件分支語句和硬編碼呼叫被插入，這經常能給出類似C語言的效能，而又不失去語言層面的通用性，但要建立在完全的垃圾收集系統之上。站外連結.