Loading AI tools
来自维基百科,自由的百科全书
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:簡單性的能力》中給出了描述[3],此文在2006年被評為1986年到1996年間三個最有影響的OOPSLA論文之一[4]。
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[5],由最初團隊的某些人和獨立編程者形成的小組開發,它和所有後續版本可以執行在Mac OS X和Linux上。2014年1月發行了4.5版本[6]。2017年5月發行了版本2017.1。
Self的發展基本已經停滯,但在發展Self過程中探索出的一些技術,在其他的系統中得到了應用。在Self的實現中採用的各種編譯最佳化技術,直接導致了Java Hotspot虛擬機器的產生;在Smalltalk的一個實現Squeak中,採用了Self圖形化使用者介面Morphic的設計方案,放棄了Smalltalk-80中採用的MVC的方案。Self是對JavaScript程式語言設計有最主要影響者之一[7]。
傳統的類別為基的物件導向語言,基於了根深蒂固的二元性:
例如,假設車輛類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)包括:數、用'
包圍起來的字串物件,塊和一般的物件。物件文字用圓括號來界定。在圓括號內,物件描述構成自豎槓|
界定的一個槽列表,隨後是在這個物件被求值時要執行的代碼。例如:
(| 槽1. 槽2 | 一些代码 )
槽(slot)是名字-值對,槽包含到其他物件的參照。槽列表由點號分隔的(可以為空的)一序列的槽描述符組成。在槽列表結束處的點號是可選的。槽描述符(descriptor)有兩種:
槽 <- 表达式
,指示將指名的資料槽初始化為求值表達式的結果,它有相同名字附加冒號的包含賦值原語的賦值槽,這兩個槽對應於其他語言中的一個讀寫變數。槽 = 表达式
,指示將指名的資料槽初始化為求值表達式的結果,這個槽對應其他語言的一個唯讀變數。在Self中沒有單獨的賦值運算。其他物件導向語言中的訪問子方法對應資料槽,變異子方法對應賦值槽。假如myPerson
物件中有個叫做name
的資料槽,則通過myPerson name
,可返回在這個槽中的值;如果它有對應的賦值槽name:
,則通過myPerson name: 'foo'
,可設定資料槽name
的值為'foo'
。
任何槽都可以通過增加星號字尾,來製成父槽。星號不是槽名字的一部份,在將名字與訊息進行匹配時候忽略它。例如一個初始化了的可變的點可以定義為:
(| parent* = traits point.
x <- 3 + 4.
y <- 5.
|)
一個物件的代碼是以點號分隔的一序列的表達式。尾隨的點號是可選的。每個表達式有一系列的訊息傳送和文字組成。在一個物件的代碼中最後的表達式,可以前導著指示返回的^
算符。
一個真正的空物件,指示為(| |)
或簡單的()
,它根本不接收任何訊息。
通過訊息訪問槽的語法,類似於Smalltalk,有三類訊息可以獲得:
接收者 槽名字
接收者 算符 参数
接收者 关键字1: 参数1 关键字2: 参数2
所有訊息都返回結果,所以顯式指定的接收者和參數自身可以是其他訊息的結果。下面是Self版本的hello world程式例子:
'Hello, World!' print.
組合可以通過使用圓括號來進行強制。在缺乏明確組合的情況下,一元訊息具有最高優先級,其次是二元訊息,而關鍵字訊息最低。一元訊息從左至右複合。二元訊息對於同一個算符從左至右結合,例如3 + 4 + 7
被解釋為(3 + 4) + 7
,而對於不同的算符沒有結合性,例如3 + 4 * 7
是非法的,而必須顯式的寫為要麼(3 + 4) * 7
要麼3 + (4 * 7)
。
關鍵字訊息的第一部份必須開始於小寫字母,而後續部份都必須開始於大寫字母。例如表達式:
5 min: 4 Max: 7
是一個單一的訊息min:Max:
,它被傳送給5
並具有參數4
和7
,而表達式:
5 min: 4 max: 7
涉及兩個訊息:第一個訊息max:
被傳送給4
並接受7
作為它的參數,而接下來訊息min:
被傳送給5
,並接受4 max: 7
的結果作為它的參數。關鍵字訊息從右至左結合,例如:
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))
由於很多訊息被傳送給當前訊息接收者self
,故而可以將self
作為隱含接收者而不需要顯式的寫出。
方法(method)是除了參數槽及或局部槽之外,還包含代碼的物件。參數(argument)槽名字開始於一個冒號,它不是槽名字的一部分,在將名字與訊息進行匹配時候忽略它。參數槽總是唯讀的,並且不能對它們指定初始化者。下面例子是計算平方的方法物件:
(| :arg | arg * arg )
一個普通方法(簡稱方法),是不嵌入到其他代碼之中的方法,它只能存放在唯讀槽中。普通方法總是有一個叫做self
的隱含的父參數槽。Self的普通方法等價於Smalltalk的方法。
如果一個槽包含一個方法,在求值這個槽來回應發來的訊息的時候,這個方法物件被淺層複製(clone),從而新建它的一個活動(activation)物件,它包含這個方法的參數槽和局部槽;複製體的self
父槽,初始化為這個訊息的接收者;複製體如果有參數槽,將它們初始化為實際參數;在這個新的活動物件的上下文中,執行這個方法的代碼。例如計算點的加法的一個方法:
(| + arg =
( (clone x: x + arg x) y: y + arg y )
|)
可以被無歧義的分析,其含義同於:
(| + =
(| :arg | (clone x: ((x + (arg x)))) y: ((y + (arg y))) ).
|)
這裡出現了三個隱含接收者一元訊息clone
、x
和y
。
作為語法約定,參數名字可以直接寫在槽名字中對應關鍵字之後,它不再帶有字首冒號,從而隱含的聲明參數槽。例如下面的方法定義:
(| ifTrue: False: =
(| :b1. :b2 | b1 value ).
|)
可以等價的定義為:
(| ifTrue: b1 False: b2 =
( b1 value ).
|)
返回算符^
的出現或缺席,不影響普通方法的行為,因為普通方法總是會返回它最終的表達式的值。
塊是Self的閉包,Self就像Smalltalk,使用「塊」用於控制流程和其他職責。塊文字的寫法,除了方括號替代了圓括號之外,類似於其他物件文字。例如嵌入在下列表達式中的塊:
1 to: 5 * i By: 2 * j Do: [| :k | k print ]
一個塊文字定義兩個物件:一個塊資料物件,和它包圍的一個塊方法物件。
value
,一個參數是value:
,兩個參數是value:With:
,隨著參數增多With:
個數也隨之遞增。此外,它還有一個叫parent*
的父槽,指向含有塊物件都共享的行為的那個物件(traits block
)。self
槽,轉而有一個匿名父槽,它被初始化指向在詞法上處於外圍的塊或方法的活動物件。匿名的含義,是這個槽的名字在Self層面是不可見的,而不能顯式的訪問。作為結果,在一個塊方法內傳送的隱含接收者訊息,被限定在這個塊所在表達式的詞法作用域之內,而非這個塊經過可能有的多次轉送,最終向它傳送適當的value
訊息變體之時的那個作用域。相應的,塊求值分為如下兩個階段:
to:By:Do:
訊息的參數之時,建立塊物件(即塊資料物件);這個塊被淺層複製(clone),並將指向這個塊在詞法上外圍的活動記錄,即當前的活動記錄的一個指標交給它匿名儲存。value
訊息變體的時候,求值這個塊方法;這個塊方法接著被淺層複製,並填充此複製體的方法槽,使用第一階段確定的指標來初始化匿名父槽,最後執行這個塊的代碼。在塊中,返回算符^
導致從包含這個塊的普通方法中返回控制權,立即終止這個方法的活動,這個塊的活動,和在其間的所有活動。這種返回叫做「非局部返回」,因為它可以穿越很多活動。普通方法求值的結果,是非局部返回所返回的值。
在理論上,所有Self物件都是獨立實體,Self既沒有類也沒有元類。對任何特定物件的變更,都不影響任何其他物件,但是在某些情況下,卻需要它們有關聯。正常的一個物件,只能理解對應於它的局部槽的訊息,但擁有一個或更多的指示父(parent)物件的槽,物件可以將任何自身不理解的訊息,委託(delegate)給父物件。
Self採用這種方式,處理在類別為基的語言中使用繼承來擔負的責任。委託還可以用來實現一些特徵,比如命名空間和詞法作用域。通過下面的例子展示委託與傳統的類的不同之處:
myObject parent: someOtherObject.
這個句子通過改變與叫做parent
的父槽關聯的值,在執行時間改變myObject
的「類」。不同於繼承或詞法作用域,委託物件可以在執行時間修改。
例如,假定在一個簡單的帳簿應用中,定義了一個物件叫做「銀行帳號」(bank account)。通常建立的這個物件,具有內部的方法,比如說「存款」(deposit)和「取款」(withdraw),和任何它所需要的資料槽,比如說「餘額」(balance)。這只是一個原型,它只在使用方式上特殊,因為它恰好是一個全功能的銀行帳號。
為「Bob的帳戶」製作銀行帳號物件的複製品(clone),將建立一個新物件,它在起初時完全同於原型。在這種情況下,將複製(copy)包括方法和任何資料的槽。但更常用的解決方案,是首先建立叫做它的特質(trait)物件的一個簡單物件,它包含通常與一個類有關的專案。
在這個例子中,「銀行帳戶」將沒有存款和取款方法,而是委託給一個父物件來做這些。採用這種方式,可以製作銀行帳號物件的很多複本,但是我們仍可以通過改變它所委託的特質物件中的槽,來改變它們全體的行為。
當處在於提示符下鍵入表達式的場景時,由叫做「大廳」(lobby)的一個物件,引領使用者進入Self世界。當建立一個新物件的指令碼被讀入系統的時候,指令碼中的表達式都在大廳的上下文中求值。就是說大廳是這個指令碼中所有傳送給self
的訊息的接收者。
要參照在指令碼中的某個現存的物件,必須通過傳送一個訊息到大廳才可以訪問到它。大廳的traits
、globals
和mixins
槽,是從大廳可以訪問的物件命名空間的根。大廳的lobby
槽允許大廳自身通過名字來提及。路徑名字是一個一元選擇子的序列,它描述從大廳到這個物件的路徑。路徑名字也是可以在大廳的上下文中求值的表達式,它產出這個物件。
例如,原型列表的完全路徑名字是globals list
。因為globals
是父槽,它可以從路徑名字表達式中省略,生成簡短路徑名字list
。大廳的traits
不是父槽,特質物件的名字必須開始於字首traits
,因此列表的特質物件必須稱呼為traits list
。
不是所有物件都有路徑名字,只有那些從大廳可以到達的物件才有,這些物件稱為「周知的」。大廳向使用者提供三類物件:
traits clonable
,而唯一性物件承襲自traits oddball
。唯一性的物件通過返回自身,來回應訊息copy
,並使用同一性來測試相等。oddball
)物件。一些物件比如true
、false
和nil
是唯一性的,在系統中它們只需要有一個。因為一個oddball
不需要在它的很多實例間共享它的行為,它不需要有分立的特質物件和原型物件。很多oddball
物件從traits oddball
繼承copy
方法,它返回物件自身而非一個新複本。mixins identity
。兩個物件測試相等,通常基於在一個共同的域(domain)內是否有相同的值。例如,在數的域內3.0 = 3
,即使它們不是相同的物件甚至不是同種類的物件。但是在一些域中,兩個物件相等若且唯若它們是相同的物件,例如兩個行程即使有相同的狀態也不被當作是相等除非它們是同一個。在這種情況上,使用同一性比較來實現相等測試,並混入mixins identity
來得到想要的行為。在大廳的defaultBehavior
槽中,定義了系統中大多數物件所繼承的預設行為。
有兩個訊息與物件複製有關:
clone
,淺層複製,返回包含著與最初物件完全相同的槽和代碼的一個新物件。它用在物件內部,客戶應當使用copy
。copy
,複製接收者,可能具有嵌入的複製或初始化。考慮一個圖形化使用者介面有關的例子:
(desktop activeWindow) draw: (labelWidget copy label: 'Hello, World!').
首先進行的是desktop activeWindow
,它向桌面物件desktop
傳送訊息activeWindow
,從其擁有的一個窗口列表中返回活動窗口。按從內向外從左至右的次序,接著是labelWidget copy label: 'Hello, World!'
,通過copy
訊息製作標籤組件物件labelWidget
的一個複本,接著向它傳送一個訊息,將Hello, World!
放入它的用作「標籤」的label
槽中。最後將返回的這個組件,傳送到這個活動窗口用於「繪製」的draw
槽中。
在Self中的物件,可以通過包括新加的槽來修改。這可以通過被推薦使用的圖形編程環境來做,或者直接使用原語_AddSlots:
。原語與正常關鍵字訊息有相同的語法,但是它的名字開始於底線字元。給_AddSlots:
的參數是一個物件文字,它的槽將被複製進入接收者。例如,在大廳中新增叫做newObject
的物件,並初始化這裡舉出的它的叫做entries
的槽,採用如下這樣的表達式:
_AddSlots: (| newObject = (| entries <- list copy …… |) |)
因為_AddSlots:
原語未指定接收者,這裡的訊息隱含接收者self
是大廳,由它來理解產生初始值的訊息list copy
,list
是周知的原型物件。
在下面的例子中,將類別為基語言中叫做Vehicle
的一個簡單的車輛類,重新構造為Self中的vehicle
物件,從而能夠區分出在轎車和卡車之間共有的行為:
_AddSlots: (| vehicle <- (|parent* = traits clonable|) |).
在大廳中建立了一個叫做vehicle
的槽,它的值是一個物件文字,同時還建立了一個叫做vehicle:
的賦值槽。作為vehicle
槽初始值的物件文字,也就是新建的叫做vehicle
的物件,包括了一個單一的父槽parent
,沒有相應的parent:
,它委託了traits clonable
物件,這個頂層的特質物件可以理解與複製有關的訊息。
然後向這個新建物件繼續增加name
槽和相應的name:
槽:
vehicle _AddSlots: (| name <- 'automobile'|).
在大廳中從vehicle
物件建立一個跑車物件sportsCar
,並接著向sportsCar
增加vehicle
所沒有的用於「開車上班」的一個新的方法槽driveToWork
:
_AddSlots: (| sportsCar <- vehicle copy |).
sportsCar _AddSlots: (| driveToWork = ("这个方法的代码") |).
在大廳中從sportsCar
物件建立一個保時捷911物件porsche911
,接著向新建物件porsche911
傳送一個訊息改變它的name
槽的值:
_AddSlots: (| porsche911 <- sportsCar copy |).
porsche911 name: 'Bobs Porsche'.
物件porsche911
與它的原型物件sportsCar
,仍有著完全相同的槽,但是其中的一個槽有著不同的值。
Self的一個特徵,是它基於了早期Smalltalk系統所用的某種虛擬機器系統。就是說,程式不是像C語言中那樣的獨立實體,而是需要它們的整體主記憶體環境來執行。這要求應用程式被裝載入儲存主記憶體的大塊(chunk)之中,這叫做「快照」或映像。這種方式的缺點,是映像有時很大並且笨重;但是除錯一個映像,經常被除錯一個傳統程式要簡單,因為執行時狀態更容易檢查和修改。在基於原始碼和基於映像的開發之間的不同,是類似於在類別導向的和面向原型的物件導向程式設計之間的區別。
此外,環境是為了讓在系統之中的物件能快速和可持續的變更而客製化的。重新構建一個「類」設計,就像從現存的祖先拖動出來方法放入新造的之中一樣容易。簡單任務像測試方法,可以通過製作複本來處理,拖動方法進入這個複本,接著變更它。不同於傳統系統,只有變更了的物件有新代碼,不需要重建任何東西來測試它。如果這個方法有效,可以簡單的把它拖動回祖先之中。
Self的VM實現的效能,在某些測試之中大約是最佳化的C程式速度的一半[8]。這是通過即時編譯技術達到的,它是在Self研究中首創並改進的,能夠使高階語言表現得這麼好。
Self的垃圾收集器使用分代垃圾回收,它按年齡分離物件。通過使用主記憶體管理系統記錄頁面寫,可以維護一個寫屏障。這個技術給出了卓越的效能,儘管在執行一些時間之後,出現完全的垃圾收集,要花相當可觀的時間。
執行系統選擇性的扁平化呼叫結構。這給出適當的自身提速,但允許了對不同呼叫者類型的類型資訊和多版本的代碼的大量快取。這去除了對做很多方法尋找的需要,並允許條件分支語句和寫死呼叫被插入,這經常能給出類似C語言的效能,而又不失去語言層面的通用性,但要建立在完全的垃圾收集系統之上[9]。
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.