Simula,一種編譯式的程式語言,由奧利-約翰·達爾與克利斯登·奈加特,在1960年代於挪威奧斯陸的挪威計算中心,開發出來了Simula I與Simula 67兩代。它承繼了ALGOL 60作為基礎,被認為是第一個物件導向程式設計的程式語言。
編程範型 | 多範式: 指令式, 程序式, 結構化, 物件導向, 並行 |
---|---|
設計者 | 奧利-約翰·達爾 |
實作者 | 克利斯登·奈加特 |
釋出時間 | 1962年 1967年 (Simula 67) | (Simula I)
目前版本 |
|
型態系統 | 靜態、名稱式 |
作用域 | 詞法 |
實作語言 | 主要為ALGOL 60(有一些Simscript成份) |
作業系統 | 類Unix系統、Windows、z/OS、TOPS-10、MVS |
主要實作產品 | |
Portable Simula Revisited[2], GNU Cim[3] | |
啟發語言 | |
ALGOL 60, Simscript[4] | |
影響語言 | |
Smalltalk[5]、CLU[6]、C++、BETA、Object Pascal、Modula-3、Java |
Simula 67介入了對象、類、子類(後來慣稱為子類繼承超類)、虛過程[8],還有協程、離散事件模擬和特徵性的垃圾收集[9]。Simula的影響經常被低估[10],Smalltalk[5]、CLU[6]、C++、Object Pascal、Modula-3、Java和後來的很多程式語言,受到了Simula 67的啟發。BETA是Simula的現代後繼者。
歷史
1957年,克利斯登·奈加特開始在NDRE的Ferranti Mercury電腦上寫模擬器,為此他需要更強大的程式語言。1962年1月,奧利-約翰·達爾開始跟他合作,他們受到ALGOL 60的啟發。1962年5月,發展出第一份模擬器程式語言提議,語言取名為SIMULA,後改稱為Simula I。此時,克利斯登·奈加特受到Sperry Rand公司邀請,去協助他們開發UNIVAC 1107電腦。UNIVAC軟件部門的主管鮑伯·貝莫,力邀克利斯登·奈加特前往國際資訊處理協會(IFIP)舉行的第二次國際會議上,發表了論文「SIMULA-擴展ALGOL到離散事件網絡的描述」[11]。
1963年8月,挪威計算中心(NCC)購買到UNIVAC 1107,在UNIVAC的合約同意下,奧利-約翰·達爾在這台電腦上安裝以凱斯西儲 1107 ALGOL 60的編譯器來實作的Simula I。1965年1月,Simula I終於可以在UNIVAC 1107上完全的運作。Simula I在1968年被移植到了Burroughs B5500電腦,以及後來蘇聯的URAL-16電腦。
1965年,東尼·霍爾首次提出「記錄類」(record class)構造的概念[12],1966年,克利斯登·奈加特與奧利-約翰·達爾二人,將Simula I的具有「准並列」性質的「進程」,擴展成了具有了記錄類性質的廣義進程,隨即改稱為「物件」[13]。1967年5月,奈加特和達爾在奧斯陸舉辦的IFIP工作小組討論區中,發表了關於類與子類聲明的論文,形成Simula 67的第一份定義檔案[14]。
1968年召開的會議,組成了SIMULA標準小組,並發表了第一份官方Simula標準檔案「SIMULA 67通用基礎語言」[15]。在1960年代後期和1970年代早期,Simula 67主要實現於四個系統之上:挪威計算中心的UNIVAC 1100系列,挪威計算中心的IBM System/360和System/370,奧斯陸大學在Kjeller聯合安裝的CDC 3000系列,和瑞典國防研究所(FOA)的DEC TOPS-10。
當前的Simula 67業界標準,是在1986年修訂的「標準SIMULA」[7],它在1987年被接受為瑞典國家標準[16]。它有四個主要實現:Simula AS、Lund Simula、GNU Cim[3]和Portable Simula Revisited[2]。
Simula影響了Smalltalk[5],和後來的物件導向程式設計語言[10]。C++語言和Java語言的創始人,都認可自己受到了Simula的重要影響[17]。
語法和語意
Simula 67包含通用演算法語言ALGOL 60的多數特徵作為自己的子集[7],它是大小寫不敏感的。Simula 67的中心概念是對象,對象是自我容納(self-contained)的一段程式即一個塊實例,它擁有由一個類聲明定義的自己的局部數據和行動(action)。類是一種過程[18],它有能力引起在它被呼叫後仍存活的一個塊實例,而這些實例就叫做這個類的對象。為了操縱對象和相互關聯對象,語言介入了鏈結串列處理設施。
Simula 67為了將整個程式執行組織為:對象所屬的諸「行動階段」的一個序列,而將其所必需的基本功能特徵,包含在作為「系統類」的「環境類」之中。在環境類中有被稱為「標準系統類」的模擬器類Simulation
,它定義了充當系統時間軸的「定序集合」(sequencing set)對象SQS
、行程類Process
和事件通告類EVENT_NOTICE
,定序集合的成員是事件通告對象,它通過其PROC
特性提及一個行程對象;事件通告表示了對應這個行程對象的下一個行動階段的一個事件,被排程在系統時間EVTIME
時發生。
塊是成序列的聲明,跟隨着成序列的陳述式,並被包圍在關鍵字begin
和end
之間。塊自身是一種陳述式[19],在Simula 67中,它包括子塊和有字首(prefixed)塊,在語法上,子塊就是無字首的ALGOL 60的塊,即匿名塊和過程主體。
聲明擔負定義在程式中用到的量(quantity)的特定屬性,並給它們關聯上識別碼,這包括:簡單變數、陣列、switch
(標籤列表)、過程、類和外來聲明。塊中的每個陳述式,都可以是塊或複合陳述式。複合陳述式與塊相比在語法上沒有聲明。
所有的塊,自動地介入名稱目錄(nomenclature)的一個新的層級:在這個塊內出現的識別碼,可以通過合適的聲明,而被指定為局部於所論及的這個塊;這個識別碼在這個塊裏面的所表示的實體,不存在於它的外面;這個識別碼在這個塊外面的所表示的任何實體,在這個塊裏面是不可見的;在Simula 67中,可通過連接或遠端訪問使它成為可見的。
除了表示標籤的識別碼之外,一個識別碼,如果出現在一個塊中,而且並非聲明於這個塊中,則它非局部於這個塊。因為塊中的陳述式自身可以是一個塊,局部和非局部於一個塊的概念,必須遞歸地去理解。
塊是一種形式描述或模式,關乎匯聚的數據結構和關聯的演算法以及行動[20]。當一個塊執行的時候,生成這個塊的一個動態實例[21]。一個塊實例,可以被認為是它的形式描述的文字複本。在電腦中,一個塊實例可以採用某一種形式的主記憶體區域,它包含需要的動態塊資訊,並包括空間來持有局部於這個塊的變數的內容[22]。
塊的任何一個內部的塊,仍然是一個模式,但是在其中出現的非局部識別碼,標定了局部於在字面上外在包圍(textually enclosing)的塊實例的專案[23]。非局部於內部的塊的識別碼繫結,對這個內部的塊的任何隨後的動態實例,保持有效[24]。
塊實例中的局部變數,標識了分配給塊實例的主記憶體片段。在進入一個塊之中的時候,任何局部於這個塊即在其中聲明的變數,都會被初始化。局部於一個塊的一個變數,是一個主記憶體裝置,其內容依據這個變數的類型,要麼是一個值,要麼是一個參照。
值類型可特徵化為直接關聯着一個可能值的集合,即這個類型的「值範圍」。參照概念,對應於一個名字或一個「指標」的直觀想法。在Simula 67中有兩種參照類型,對象參照類型和文字參照。對於值類型不關聯着參照概念。提供了參照值的機制,還反映了機器的定址可能性;在特定簡單情況下,一個參照可以被實現為一個儲存的值的主記憶體地址。
過程與塊相比,它有一個名字,可以在程式的一些不同地方提及,並且在呼叫時可以給出參數[25]。過程和塊都不可能建立到它或它內部的參照[25];對於一個給定塊,有可能生成它的一些可以共存和互動的實例[22],例如遞歸過程的實例。
過程的參數傳送模態,除了有傳值呼叫和傳名呼叫[26],在Simula 67中,又增加了傳參照呼叫。過程的預設的傳送模態,在Simula 67中,對於值類型的參數是傳值呼叫,對於所有其他類型的參數是傳參照呼叫;故而在過程聲明的參數規定中,增加了以name
為前導的名字部份,用來指定所述及的參數採用傳名呼叫。
在過程主體內傳名呼叫的形式參數的每次出現,都引起對實際參數的一次求值。在Simula 67中,這個求值發生在過程陳述式的上下文中,就是說不會出現識別碼衝突,因為過程主體和它的變數此時是不可見的。過程呼叫的執行,在有參數的情況下要經歷如下步驟:建立形式參數塊實例;求值對應於傳值呼叫或傳參照呼叫的實際參數,並將其結果賦值給形式參數塊實例的對應變數;過程主體被初始化並開始執行。
真正(proper)過程,不定義特定類型的函數指定式的值,在Simula 67中,它被稱為具有普遍(universal)類型,任何類型都從屬(subordinate)於普遍類型。
類別宣告定義一個程式(數據和行動)模式,而符合(conforming)這個模式的對象被稱為「屬於相同的類」。不同於Smalltalk 80等物件導向程式設計語言,Simula 67的類和後來的Modula-3的對象類型,不進行實例變數與類別變數的區分。
對於一個給定對象,類別宣告中形式參數、在虛擬部份中規定的虛擬量,和聲明為局部於類主體(body)的量,叫做這個對象的特性(attribute)。一個特性的聲明或規定叫做特性定義。在1986年修訂的語言標準中,通過保護規定可以限制類特性識別碼的可見性。
類別宣告中的形式參數,不適用傳名呼叫,這是為了保證實際參數在類所引起的對象存活期間不能消失[27]。在規定部份中需要對每個形式參數進行規定,這些參數被當作局部於類主體的變數。類主體通常是一個塊,即使它如語法形式所允許的那樣是塊以外的一個陳述式,也表現得如同一個塊。一個分裂(split)主體表現為一個塊,在其中符號inner
表示一個虛設(dummy)陳述式。
Simula 67的類主體中除了有定義特性的聲明,還可以有定義行動的陳述式。如果在一個類別宣告中有行動定義,則符合這個模式的行動,可以由屬於這個類的所有對象執行。如果在類別宣告中沒有指定行動,則定義了一類純數據結構,很多後來的物件導向程式設計語言就是這樣,將對象作為記錄或結構的擴充。例如:
class Order(number); integer number;
begin
integer numberOfUnits, arrivalDate;
real processingTime;
end;
屬於特定類的對象,通過對象表達式中的對象生成式new
來生成,並製作出到它的參照。例如,想要多少就能生成多少屬於這個Order
類的一個新對象:
new Order(103);
屬於一個對象的行動,可以都在就一個過程而言的一個序列中執行。這些行動,還可以作為一系列的獨立子序列或「行動階段」(active phase)來執行。在一個給定對象的兩個行動階段之間,可以出現任何數目的其他對象的行動階段。
一個類可以被用作到另一個類別宣告的「字首」,從而將字首所定義的性質,建造入這個新的類別宣告定義的對象之中。具有字首類C
和類識別碼D
的一個類別宣告,定義了類C
的一個子類D
。屬於子類D
的對象,由自身是類C
的對象的「字首部份」,和類D
的類別宣告主體所描述的「主體部份」組成。這兩部份串接而形成了一個複合對象,它可以用串接成的類別宣告來正式的描述,串接的過程被認為先於程式執行而發生。
類只能在定義它的塊層級中用作字首。字首類自身也可以有字首。類不能出現在自己的字首序列中。子類被稱為內部(inner)於字首類,而字首類被稱為外部(outer)於子類,但不可將子類稱為字首類的內部類。這不同於後來的物件導向程式設計語言中的稱謂:「衍生類別」擴充「基礎類」,或「子類」繼承「超類」。例如:
Order class SingleOrder;
begin
real setupTime, finishingTime, weight;
end;
SingleOrder class Plate;
begin
real length, width;
end;
屬於Order
類的子類如SingleOrder
類和Plate
類的新對象,都含有Order
類別定義的數據,再加上在各種類別宣告中定義的補充數據。例如,屬於Plate
類的對象含有Order
類、SingleOrder
類和Plate
類別定義的所有數據。
一個識別碼在一個塊中的出現,如果並未處在遠端識別碼之中或在有重新定義的內部的塊之中,則被稱為「未託付」(uncommitted)出現。對於子類的聲明及其字首類串接成的類別宣告,在局部於子類的特性識別碼,和局部於其字首類的特性識別碼,二者的未託付出現之間的可能衝突,將憑藉對涉及到的局部於子類的特性識別碼的適合的系統性變更來避免,但是對應虛擬量的識別碼不變更。
對於字首類和子類別宣告串接成的類別宣告,它的形式參數列,由字首類的形式參數列,和附加的子類別宣告的參數列二者合併而成。它的值部份、規定部份和虛擬部份,是字首類和子類別宣告相應各部份的併集。結果的虛擬部份不能包含某個識別碼的多於一次出現。
對於類主體,最初的begin
和隨後的聲明被稱為「塊頭部」,從第一個可執行陳述式直到inner
之前的陳述式被稱為「初始運算」,在inner
之後的陳述式和最終的end
被稱為「複合尾部」。如果字首類主體是分裂主體,用字首類的塊頭部複本替代子類的塊頭部中的begin
,將字首類的初始運算複本插入到子類的塊頭部之後,用字首類的複合尾部複本替代子類的複合尾部中的end
。如果字首類主體不是分裂主體,它被解釋為如同將;inner
插入到最終的end
之前。
設Cn
是具有字首序列C1, C2, ……, Cn-1
的一個類,這裏類Ck
(k = 1, 2, ……, n)
的下標k
叫做字首層級,X
是屬於類Cn
的一個對象,則X
是一個複合對象;非正式的說,串接機制有如下結果:
- 對象
X
擁有的特性集合,是在序列C1, C2, ……, Cn
中諸類所定義特性集合的併集。在類Ck
(1 <= k <= n)
中定義的特性,被稱為定義在字首層級k
。 - 對象
X
擁有的「運算規則」,由來自這些類主體的諸陳述式構成,它們所屬的字首層級遵循規定的次序。來自類Ck
的陳述式,被稱為屬於對象X
的字首層級k
。 - 在對象
X
的字首層級k
的陳述式,能訪問在對象X
的等於或外部於k
的字首層級上定義的它的所有特性,但是如果外部的類Ci
(i < k)
中存在衝突定義,則導致對應特性不可見而不能直接訪問。這些不可見特性仍有訪問方式,例如通過使用過程或this Ci
。 - 在對象
X
的字首層級k
的陳述式,不能立即訪問在對象X
的內部於k
的字首層級上定義的它的那些特性,除非通過虛擬量。 - 在字首層級
k
的分裂主體中,符號inner
,表示對象X
的屬於內部於k
的字首層級的運算規則的諸陳述式,或者在k = n
時表示虛設陳述式。如果序列C1, ……, Cn-1
都沒有分裂主體,則在對象X
的運算規則中諸陳述式所屬的字首層級遵循遞增次序。
用作字首的系統類,與其字首鏈的所有的類一起,被當作聲明於最小的包圍其字面上出現的塊中。因此重新聲明可以出現在一個程式的內部的塊層級中。
有字首塊的一個實例,是由塊字首生成的一個單一的對象,與這個主體塊的實例,串接而成一個複合對象,其串接定義採用類似於類的串接規則。塊字首的類的形式參數,按塊字首的實際參數所示初始化。對有字首塊有兩個限制:一個類如果在其中通過使用this
來參照類自身,則它作為塊字首是非法的。塊字首的類識別碼所提及的類,必須局部於最小的包圍這個有字首塊的塊。
當屬於各種類的很多對象,作為同一個程式的各個部份而共存的時候,需要能夠對個體對象指定名字,為此介入了叫做「參照」的新的基本類型;還要能相互關聯對象,比如通過二叉樹和各種其他類型的列表結構。Simula 67將迴圈雙向鏈結串列叫做「集合」,它將集合類Simset
作為「標準系統類」介入為環境類的一部份。
對於某個類的一個對象,有一個唯一的關聯的「對象參照」標定這個對象,並且對於任何類C
,都有一個關聯的對象參照類型ref(C)
。這種類型的量,被稱為由類C
限定;它的值,要麼是一個對象,要麼是表示「沒有對象」的特殊值none
。
限定(qualification)將值的範圍,限制至包括在限定類中的這些類的對象。不管何種限定,值的範圍都包括值none
。一個對象參照的限定,是一個類識別碼,它因此服從聲明的作用域和可見性規則。限定關聯於所提及的類別宣告所局部於的那個塊的一個實例。這蘊涵着特定的關於限定有效性的檢查,不能單獨在程式正文的基礎上進行,因此這種測試必須在程式執行期間完成。
參照是有限定的,蘊涵着一個給定參照所提及的對象,只能屬於這個限定中提到的類,或屬於這個限定類的子類。如果一個對象參照類型的限定,是另一個對象參照類型的限定類的子類,則前者被稱為從屬於後者。例如:
ref(Order) next, previous;
對象表達式是獲得對一個對象的參照的一種規則。對象表達式的值,是被參照的對象或none
。運算子:-
讀作「指稱」(denote),它指示將一個參照,指派(assign)到處在「參照指派符」左側的參照類型的變數或過程識別碼。例如:
next :- new Plate(50); previous :- next; next :- none;
而運算子:=
讀作「成為」(become),指示將一個值,指派到處在賦值符左側的值類型的變數或過程識別碼,或者將一個文字值,指派到在賦值符左側者所參照的那個文字幀(frame)。
一個對象的一個特定特性,由下列三項資訊來完全標定(identify):
對於任何特性標定,第二項類資訊都是在字面上定義的,從而形成靜態繫結[28]。屬於其他對象的特性,可以要麼單個的通過「遠端識別碼」(「點表示法」),要麼成組的通過「連接」(connection)機制,從其外面「遠端訪問」來參照和使用。
遠端識別碼的形式為:简单对象表示式.特性标识符
,它可用作簡單變數、陣列變數、函數指定式(designator)和過程陳述式中的識別碼,這種點表示法給出對資訊的單獨片段的訪問,例如:
if next.number >= previous.number then ……;
它將名為next
的類Order
的對象的特性number
,比較於名為previous
的類Order
的對象的特性number
。
「成組訪問」可通過連接陳述式inspect
來完成,例如:
inspect next when Plate do begin …… end; inspect new Plate(50) do begin …… end;
在這裏的begin
和end
之間的陳述式中,next
參照的對象所屬的Plate
類的所有資訊片段,都可以直接提及。
在do
後面的陳述式,如果是塊以外的一個陳述式,它表現得也如同一個塊。連接陳述式進而表現得如同被叫做「連接塊」的第二個虛構(fictitious)的塊所包圍。連接機制的用途,是為在連接塊中的特定特性標定,提供對象資訊和在when
子句中的類資訊的隱式定義。
設對象表達式求值為X
,在連接塊執行期間,稱對象X
為「被連接上」了。連接塊有一個關聯的「塊限定」,如果連接塊有when
子句,它是前面的類識別碼,如果沒有when
子句,它是前面的對象表達式的限定。
虛擬(virtual)量,是在類別宣告中由virtual:
前導的量,它有雙重用途:
- 允許在一個對象的一個字首層級上,訪問在內部字首層級上聲明的特性。
- 允許在一個字首層級上的特性重新定義,在外部字首層級上有效。
不同於一般的特性標定,虛過程形成「半動態繫結」[8],在某種意義上是類似傳名呼叫的機制[27]。這種機制不同於Smalltalk 80等物件導向程式設計語言中,上溯子類至超類的繼承鏈來找到最近的方法實現的那種動態繫結。
一個對象的一個虛擬量,要麼是「無匹配的」的,要麼被標定為具有一個「匹配」特性,它是在這個虛擬量的字首層級上或在內部字首層級上聲明的,其識別碼重合(coincide)於虛擬量的識別碼的一個特性。匹配特性必須與虛擬特性有相同的種類。在一個給定字首層級上的匹配量的類型,必須重合或從屬於這個虛擬規定的類型,和在任何外部字首層級上聲明的任何匹配量的類型。
在1986年修訂的語言標準中,虛過程量可選的可使用procedure <过程标识符> <过程规定>
方式來指定,即憑藉它的類型,並且如果它有形式參數,則還憑藉它們的類型、種類和傳送模態。含有過程規定is <过程声明>
的虛過程量,只能由有相同類型的過程來匹配,並它與這個過程規定具有相同的過程頭部。
例如,下面實現雜湊表的HashTable
類,聲明了整數類型的虛過程hash
並隨即實現了它,又定義了要用到這個雜湊函數hash
的尋找過程lookup
:
class HashTable(n); integer n;
virtual: integer procedure hash;
begin
integer procedure hash(t); text t;
begin
……
end hash;
text array table(0 : n-1); ……
integer procedure lookup(t, old);
name old; Boolean old; text t;
begin
integer i, istart; Boolean entered;
i := istart := hash(t);
while not entered do
begin
……
end;
lookup := i;
end lookup;
end HashTable;
HashTable class ALGOL_hashing;
begin
integer procedure hash(t); text t;
begin
……
end hash;
end ALGOL_hashing;
作為子類的ALGOL_hashing
類和以HashTable
類為字首的有字首塊,可以用自己的hash
過程實現,替代在HashTable
類中的實現。在子類的類主體和有字首塊的主體塊中,能獲得到過程lookup
,而它用到的過程hash
是這裏提供的替代實現。
對象表達式,具有類型ref(限定)
。對象表達式的限定規則包括:對象生成式(generator)、局部對象或限定對象,分別由跟隨符號new
、this
或qua
的識別碼的類來限定(qualification)。
對象生成式new C
,這裏標定了類C
,它涉及到屬於類C
的對象的生成和執行。這個對象是類C
的一個新實例。對象生成式的求值構成自如下行動:
- 生成這個對象,並且如果這個對象生成式有實際參數,則求值它們,將得到的這些值及或參照傳送給形式參數。
- 控制經過這個對象最初的
begin
進入其中,它籍此變成在「系附」狀態下執行。當所生成的對象執行了detach
基本過程變為「脫離」狀態,或者經過這個對象最終的end
退出而變為「終止」狀態,對象生成式的求值完成。
局部對象this C
,這裏的C
是類識別碼。如果用在類C
或C
的任何子類的類主體中,或用在其塊限定為類C
或C
的一個子類的連接塊中,則這個對象表達式是有效的。在一個給定的上下文中,一個局部對象的值,是一個對象或連接到一個對象,它是這個對象表達式在其中有效的、最小的在字面上外在包圍的塊實例;如果沒有這樣的塊,則這個對象表達式在這個上下文中是非法的。對於一個過程或類主體的實例(上下文),「在字面上外在包圍」(textually enclosing)意為包含它的聲明。
暫瞬限定X qua C
,這裏的X
表示一個簡單的參照表達式,而C
是類識別碼。設類D
是對象X
的限定,對於限定對象X qua C
,如果類C
外部於或等於類D
,或者是D
的一個子類,則這個對象表達式是合法的,否則是為非法。如果X
的值是none
,或者是屬於外部於C
的類的一個對象,則這個對象表達式求值形成一個執行時間錯誤;在其他情況下,X qua C
的值,就是X
的值。對一個串接的類對象的暫瞬限定,限制或擴充它的特性通過檢視或遠端訪問的可見性。
對象關係表達式,使用運算子is
和in
,來測試一個對象的類成員關係。關係X is C
,在X
參照的是屬於類C
的對象之時,值為true
,否則值為false
。關係X in C
,在X
參照的是屬於類C
的對象,或者X
是內部於類C
的類之時,值為true
,否則值為false
。
不同於算術關係表達式使用比較運算子=
和<>
,對象參照關係表達式,使用比較運算子==
和=/=
,來比較參照,從而區別於對應的被參照的值。兩個對象參照X
和Y
,在它們都參照相同的對象,或者它們都是none
之時,被稱為是「同一」的。關係X == Y
,在這種情況下擁有的值為true
,否則值為false
。關係X =/= Y
的值,是X == Y
的值的否定。
不同於ALGOL 60規定了量的作用域與可見性,在Simula 67中,一個識別碼定義及其關聯的識別碼,在其作用域與可見性之間需要做出區別。
- 作用域:一個識別碼定義的作用域,是它在其中可能有作用的那一部份程式正文。同一個識別碼,可以定義在程式的很多地方,因此可以關聯於不同的量。同一個識別碼的這些定義的作用域,因而可能有所重疊,例如在一個識別碼在內部塊中被重新聲明的情況下。
- 可見性:一個識別碼定義,如果它所關聯的識別碼,在程式的給定點上能夠提及到這個定義所聲明的量,則它被稱為在這個點上是可見的。在給定識別碼於此可見的程式正文的一個特定點上,最多只能有一個定義關聯於這個識別碼,例如在重新聲明的情況下,在它們作用域的併集內任何給定點上,只有一個定義是可見的。
一個識別碼定義的局部塊,是在字面上最近的包圍塊,即子塊、字首塊、過程主體或類主體,還包括圍繞for
陳述式的受控陳述式、過程聲明、類別宣告和連接塊等的虛構塊。這個識別碼和它的定義,被稱為局部於這個塊。
識別碼定義,只在它們的作用域內是可見的。一個特定定義的可見性,在它的作用域內可以受到下列限制:
- 在一個識別碼定義的局部塊所包圍的某個構造內,出現的具有相同識別碼的識別碼定義,是這個識別碼的重新定義。在它們共同的作用域內,只有最內部的重新定義是可見的。
- 一個重新定義,出現在類的某個內部字首層級。
- 遠端訪問,可以導致某些識別碼定義在檢視塊或點表示法內變成不可見的。
- 使用
this
或qua
,可以導致一個或多個重新定義被暫時停止。 - 在這個識別碼定義所局部於的類別宣告中,這個識別碼出現在由
hidden
及或protected
前導的保護部份中。
一個識別碼的重新定義,不允許出現在它的局部塊的頭部,這禁止了在同一個塊中出現相同識別碼的兩個定義。
一個程式執行的各個組成部份,都是塊即子塊、有字首塊、連接塊和類主體的動態實例。一個塊實例,被稱為「局部於」直接包含它的描述文字的那個塊實例。例如一個給定類別的實例,局部於包含這個類別宣告的塊實例。最外層塊的實例不局部於塊實例。
在任何時間,「程式順序控制」(Program Sequence Control),首字母簡寫為PSC,參照在一個塊實例中當前被執行的程式點;形象的說,PSC「定位」至這個程式點,並被這個塊實例所「包含」。進入任何塊,都涉及到生成這個塊的一個實例,於是PSC進入這個塊實例,到達它的第一個可執行陳述式上。
一個塊實例在任何時間,都是處在四種執行狀態之一:系附(attached)、脫離(detached)、恢復(resumed)或終止(terminated)。沒有在字面上給出字首的任何類,都被定義為字首了一個虛構(fictitious)的類,它只有一個特性即detach
過程,因此所有類對象或有字首塊的實例,都擁有detach
過程。在環境類ENVIRONMENT
中,還定義了用於排程的call
過程和resume
過程。
- 一個非類塊實例,總是在系附狀態下,這個實例被稱為系附到了導致它生成的那個塊上。因此一個過程體的實例,系附到包含對應函數指定式或過程陳述式的塊實例上。非類、非過程塊實例,系附到它所局部的塊實例。在PSC到達非類塊實例的最終的
end
的時候,PSC返回到緊隨導致這個塊實例生成的陳述式或表達式的程式點。 - 一個對象,最初是在系附狀態下,並被稱為系附到包含對應對象生成陳述式的那個塊實例上。一個對象,可以通過執行
detach
過程,進入脫離狀態。通過執行call
過程,可以使一個脫離狀態的對象重新進入系附狀態,它藉此變為系附到包含這個呼叫陳述式的那個塊實例上。通過執行resume
過程,可以使一個脫離狀態的對象進入恢復狀態。不使用detach
、call
或resume
過程的一個程式執行,是一個簡單的系附狀態的塊的巢狀結構。 - 當PSC通過一個對象的最終
end
,或通過goto
陳述式離開它的時候,這個對象進入終止狀態。沒有塊實例系附到一個終止狀態的類對象上。終止狀態的對象,仍作為一個數據項而存在,它可以通過針對這個對象的包括過程和函數特性的這些特性的遠端標定來參照。
每當一個塊實例不復存在,局部於或系附於它的所有塊實例也不復存在。一個對象的動態作用域,因而受限於它的類別宣告的作用域。在Simula 67最初標準中曾提到過,語言實現可以使用垃圾回收技術[9],來進一步縮減一個對象的有效壽命的跨度。一個陣列聲明的動態作用域,可以擴充超出包含這個聲明的塊實例的作用域,因為傳參照呼叫參數傳送模態適用於陣列。
一個準並列系統[29],由「構件」(component)構成。構件是塊實例組成的巢狀結構,其中標定(identify)這個構件的那個塊實例,叫做「構件頭領」。在每個系統中,構件中總是有一個被稱呼為「主構件」,在Simula 67最初標準中它叫做「主程式」,其他構件叫做「對象構件」。
構件的定序(sequencing),由detach
、call
和resume
過程支配。detach
過程針對隱式指定的一個對象進行操作,而call
和resume
過程顯式指定所操作的對象。
一個準並列系統,由包含局部的類別宣告的一個子塊或有字首塊的任何實例所標定。標定了一個系統的塊實例,叫做「系統頭領」。一個系統的主構件的頭領(head),重合(coincide)於系統頭領。最外層的標定了一個系統的塊實例,被稱為「最外層系統」。
一個系統的主構件的頭領,總是處在系附狀態。一個系統的對象構件的頭領,確切的就是局部於系統頭領的那些脫離狀態的或恢復狀態的對象。在任何時間,在一個系統的構件之中,有確切的一個構件被稱為「生效」(operative)的。不生效的構件,有關聯的「重新啟用點」,它標定在這個構件被啟用(activate)時,要在它那裏繼續執行的程式點。一個對象構件是生效的,若且唯若這個構件的頭領處在恢復狀態。
稱謂一個塊實例P
被一個塊實例X
「動態包圍」,若且唯若存在一個塊實例序列:(P = Z0), Z1, ……, Zn-1, (Zn = X)
(n >= 0)
,使得對於i = 1, 2, ……, n
有着:Zi-1
系附到Zi
;或者Zi-1
是一個恢復狀態的對象,它關聯的系統頭領系附到Zi
。終止狀態和脫離狀態的對象,不被除了自身之外的塊實例動態包圍。
將當前包含PSC的塊實例動態包圍起來的塊實例鏈,叫做「執行鏈」。在執行鏈上的塊實例,被稱為是「執行」(operating)的,最外層的塊實例總是執行的。一個構件只要其頭領是執行的它就是執行的。
一個系統如果有一個構件是執行的,它就是執行的;在任何時間,一個系統最多有一個構件是執行的;執行的系統的頭領,可以是不執行的。一個執行的構件總是生效的;如果一個系統的生效構件是不執行的,則這個系統也是不執行的。在不執行的系統中的生效的構件,是當這個系統成為不執行的時候在執行的構件,當這個系統再次成為執行的時候它將仍是執行的。
對於一個不生效的構件C
,設它的頭領是X
,如果一個塊實例P
包含了它的重新啟用點,則P
被X
動態包圍,並且P
除了自身之外不動態包圍塊實例。由構件頭領X
動態包圍的這個塊實例序列,被稱為構件C
的「重新啟用鏈」。除了X
之外,這個鏈上的所有構件頭領,標定了生效而不執行的構件。在構件C
成為執行的時候,在它的重新啟用鏈上所有塊也成為執行的。
除了系統構件,程式執行還可以包含不屬於特定系統的「獨立對象構件」。任何這種構件的頭領,都是一個脫離狀態的對象,它局部於一個類對象或一個過程主體實例,也就是說不局部於某個系統頭領。根據定義,獨立構件總是不生效的,只能對它執行call
過程。
准並列系統的detach
/resume
機制,是一種協程[30]。在Simula 67最初標準中,沒有恢復狀態,並且沒有規定call
過程,但call
過程通常會出現在當時的語言實現中[31]。detach
/call
機制,相當於現在稱為生成器的「半協程」,而最初標準中的resume
過程可理解為detach
過程與call
過程的組合[32]。1986年修訂的語言標準,增補定義了call
過程,新增了恢復狀態,並且重新定義了resume
過程,它不可再理解為detach
過程與call
過程的組合。
准並列系統,被建立於進入包含局部的類別宣告的一個子塊或有字首塊的時候,藉此生成的實例成為這個新系統的頭領。初始時,這個主構件是生效的,並且是這個系統唯一的構件。
建立一個對象構件,要通過針對一個系附狀態的對象,執行detach
過程,藉此PSC返回到這個對象系附到的那個塊實例。這個對象進入脫離狀態,並成為一個新的不生效的構件的頭領,這個構件的重新啟用點被定位到緊後於這個detach
過程呼叫。如果這個對象局部於一個系統頭領,則這個新的構件成為這個關聯的系統的成員。
通過detach
和call
過程,可以形成「半對稱定序」,這隻涉及到對象構件,而不區分它們是屬於系統的構件還是獨立的構件。
- 對於一個不生效的對象構件,通過針對它的脫離狀態的頭領,執行一個
call
過程,可以重新啟用這個構件,藉此PSC移動到它的重新啟用點上。這個頭領重新進入系附狀態,並變成系附到包含這個call
過程呼叫的塊實例上。這個構件正式的失去了本身(作為構件)的狀態。
通過detach
和resume
過程,可以形成「對稱構件定序」,這隻涉及到屬於一個準並列系統的那些構件。對立於半對稱定序中的「呼叫者」與它的「被呼叫者」,在對稱構件定序中的「恢復者」與它的「被恢復者」,具有完全的對稱聯絡。
- 對於這個系統的一個不生效的對象構件,通過針對它的脫離狀態的頭領,執行一個
resume
過程,可以重新啟用這個構件,藉此PSC移動到它的重新啟用點上;這個構件的頭領進入恢復狀態,而這個構件變成生效的。這個系統以前生效的構件變成不生效的,並且它重新啟用點被定位到緊後於這個resume
過程呼叫;如果這個構件是一個對象構件,則它的頭領進入脫離狀態。 - 對於當前生效的對象構件,通過針它的恢復狀態的頭領,執行一個
detach
過程呼叫,這個系統的主構件重獲生效的狀態,藉此PSC移動回到主構件的重新啟用點上。以前生效的構件變成不生效的,並且它重新啟用點被定位到緊後於這個detach
過程呼叫。這個構件的頭領進入脫離狀態。
PSC經過一個類對象的最終end
的效果,除了使這個對象成為終止狀態,而非脫離狀態之外,相同於針對這個對象執行detach
過程的效果。其結果是它不會得到重新啟用點,並且在它已經擁有作為構件頭領的狀態時,失去這種狀態。
程式範例
Simula 67下的經典Hello, World!範例:
begin
outtext("Hello, World!");
outimage;
end;
outtext
過程將字串輸出到緩衝區,而outimage
過程將緩衝區內容輸出到標準檔案,二者都定義在輸出檔案類OutFile
中,而它是檔案類File
的子類。
class Tree(val); integer val;
begin
ref(Tree) left, right;
procedure insert(x); integer x;
begin
if x < val then
begin
if left == none then
left :- new Tree(x)
else
left.insert(x)
end
else if right == none then
right :- new Tree(x)
else
right.insert(x);
end insert;
ref(Tree) procedure find(x); integer x;
begin
if x = val then
this Tree
else if x < val then
(if left == none then
none
else
left.find(x))
else if right == none then
none
else
right.find(x);
end find;
end Tree;
在find
過程的主體中出現了表達式this Tree
,它產生的值所參照的是當前節點。這裏通過函數指定式X.find(x)
,來呼叫對象X
的find
過程,如果X.val = x
,則這個函數的結果是到X
自身的參照值。
Simula 67標準通過如下實例來詮釋叫做「准並列系統」的協程機制:
begin comment 系统S1;
ref(C1) X1;
class C1;
begin
procedure P1;
detach;
P1
end C1;
ref(C2) X2;
class C2;
begin
procedure P2;
begin
detach;
! 可尝试detach或resume(X1);
end P2;
begin comment 系统S2;
ref(C3) X3;
class C3;
begin
detach;
P2
end C3;
X3 :- new C3;
resume(X3)
end S2
end C2;
X1 :- new C1;
X2 :- new C2;
call(X2); ! 可尝试resume(X2);
end S1;
這個例子程式中,有兩個准並列系統S1
和S2
。系統S1
是對應最外層子塊的最外層系統,它包含了兩個類別宣告C1
和C2
。系統S2
對應於類C2
的類主體中的匿名塊,它包含了一個類別宣告C3
。
在PSC進入最外層子塊開始處,產生系統S1
的系統頭領,到達第28行的系統S1
主構件的第一個可執行陳述式,開始生成對象X1
,進入類C1
,PSC到達第7行的類主體的第一個可執行陳述式,呼叫了類C1
的特性過程P1
,進入第6行的過程體的第一個可執行陳述式,這時的狀況是:
[S1] ← (X1) ← (P1) ← PSC
這裏方括號表示系統頭領,圓括號表示其他種類的塊實例,左向箭頭表示系附,使用粗體表示這個塊實例執行了對象生成式。執行第6行的detach
陳述式後,對象構件頭領X1
進入脫離狀態,儲存X1
的重新啟用點為第6行P1
過程體結束,回溯到第28行對象產生式結束,系統S1
的對象X1
生成完畢,這時的情況是:
[S1] ← PSC
|
(X1) ← (P1) ← <X1重新激活点>
這裏的豎槓將對象構件頭領列為一個系統的成員。PSC到達系統S1
主構件第29行,開始生成對象X2
,進入類C2
,PSC到達第17行的類主體中匿名塊開始處,進入這個子塊產生系統S2
的系統頭領,PSC到達第24行的系統S2
主構件的第一個可執行陳述式,開始生成對象X3
,進入類C3
,PSC到達第20行類主體開始處,這時的情況是:
[S1] ← (X2) ← [S2] ← (X3) ← PSC
|
(X1) ← (P1) ← <X1重新激活点>
執行第21行類主體的第一個可執行陳述式detach
後,對象構件頭領X3
進入脫離狀態,儲存X3
的重新啟用點為第22行,回溯到第24行對象產生式結束,系統S2
的對象X3
生成完畢,這時的情況是:
[S1] ← (X2) ← [S2] ← PSC
| |
| (X3) ← <X3重新激活点>
|
(X1) ← (P1) ← <X1重新激活点>
PSC到達系統S2
主構件的第25行,執行resume(X3)
陳述式後,儲存系統S2
主構件的重新啟用點為第26行,對象構件頭領X3
進入恢復狀態,PSC恢復到第22行的對象X3
的類主體之中,這時的情況是:
[S1] ← (X2) ← [S2] ← <S2重新激活点>
| |
| (X3) ← PSC
|
(X1) ← (P1) ← <X1重新激活点>
這裏的底線指示這個塊實例處在恢復狀態。執行第22行的類C2
的特性過程P2
,到達在第14行的過程體的第一個可執行陳述式,這時的情況如下,並標記為「狀況A」:
! 状况A ; [S1] ← (X2) ← [S2] ← <S2重新激活点> | | | (X3) ← (P2) ← PSC | (X1) ← (P1) ← <X1重新激活点>
執行在第14行P2
過程體的detach
陳述式後,對象構件頭領X2
進入脫離狀態,儲存X2
的重新啟用點為第15行,回溯到第29行對象產生式結束,系統S1
的對象X2
生成完畢,這時的情況如下,並標記為「狀況B」:
!状况B ; [S1] ← PSC | (X1) ← (P1) ← <X1重新激活点> | (X2) ← [S2] ← <S2重新激活点> | (X3) ← (P2) ← <X2重新激活点>
注意對象X3
仍是系統S2
的生效構件,它並沒有自己的重新啟用點。序列(P2, X3, X2)
是X2
的重新啟用鏈,這裏的X3
是恢復狀態,而它的系統頭領S2
系附到了X2
。
PSC進入系統S1
主構件第30行,執行call(X2)
陳述式後,對象構件頭領X2
進入系附狀態,PSC恢復為第15行的P2
過程體之中,這時的情況重現為前面的「狀況A」(S1
不加粗體)。
如果將系統S1
主構件中第30行覆寫為一個resume(X2)
,執行後對象構件頭領X2
進入恢復狀態,儲存系統S1
主構件的重新啟用點為第31行,PSC恢復為第15行的P2
過程體之中,會出現如下情況:
[S1] ← <S1重新激活点>
|
(X1) ← (P1) ← <X1重新激活点>
|
(X2) ← [S2] ← <S2重新激活点>
|
(X3) ← (P2) ← PSC
序列(P2, X3, X2)
是X2
的執行鏈。
如果將P2
過程體中第15行的註釋替換為一個detach
陳述式,執行後對象構件頭領X2
進入脫離狀態,儲存X2
的重新啟用點是第16行,PSC恢復到第31行的系統S1
主構件之中,這時的情況重現為前面的「狀況B」。
如果將P2
中第15行的註釋替換為一個resume(X1)
陳述式,執行後對象構件頭領X2
進入脫離狀態,對象構件頭領X1
進入恢復狀態,儲存X2
的重新啟用點為第16行,PSC恢復到第6行的P1
過程體的過程結束,會出現如下情況:
[S1] ← <S1重新激活点>
|
(X1) ← (P1) ← PSC
|
(X2) ← [S2] ← <S2重新激活点>
|
(X3) ← (P2) ← <X2重新激活点>
下面例子中,定義了一個字形類Glyph
,它是抽象基礎類,並且有二個實現子類:字元類Char
和行類Line
:
begin
class Glyph;
virtual: procedure print is procedure print;
begin
end;
Glyph class Char(c);
character c;
begin
procedure print;
outchar(c);
end;
Glyph class Line(cs);
ref(Glyph) array cs;
begin
procedure print;
begin
integer i;
for i := 1 step 1 until upperbound(cs, 1) do
cs(i).print;
outimage;
end;
end;
ref(Glyph) rg;
ref(Glyph) array rgs(1 : 4);
rgs(1) :- new Char('A');
rgs(2) :- new Char('b');
rgs(3) :- new Char('b');
rgs(4) :- new Char('a');
rg :- new Line(rgs);
rg.print;
end;
這裏的虛過程量print
具有過程規定is procedure print
,它匹配既沒有形式參數也沒有結果值的print
過程,如果不加以這種過程規定,則它可以匹配具有任何形式參數和任何結果值的print
過程。在Simula 67中,沒有不可以實例化帶有純虛過程類的特定限制,因而缺乏真正的抽象類的概念,所有類都可以被實例化,但是呼叫純虛過程會產生執行時間錯誤。
在下面的離散事件模擬例子中,Sam、Sally和Andy正在逛商店買衣服,他們必須共用一個試衣間。他們每人只能瀏覽商店大約12分鐘,並接着獨佔的使用試衣間大約3分鐘,每個行動都服從正態分佈,他們的試衣間經歷被模擬如下:
Simulation
begin
class FittingRoom;
begin
ref(Head) door;
Boolean inUse;
procedure request;
begin
if inUse then
begin
wait(door);
door.first.out;
end;
inUse := true;
end;
procedure leave;
begin
inUse := false;
activate door.first;
end;
door :- new Head;
end;
procedure report(message); text message;
begin
outfix(time, 2, 0);
outtext(": " & message);
outimage;
end;
Process class Person(pname); text pname;
begin
while true do
begin
hold(normal(12, 4, u));
report(pname & " 要求用试衣间");
fittingroom1.request;
report(pname & " 已进入试衣间");
hold(Normal(3, 1, u));
fittingroom1.leave;
report(pname & " 已离开试衣间");
end;
end;
integer u;
ref(FittingRoom) fittingRoom1;
fittingRoom1 :- new FittingRoom;
activate new Person("Sam");
activate new Person("Sally");
activate new Person("Andy");
hold(100);
end;
主程式是字首着模擬器類Simulation
的有字首塊。模擬器類可在任何塊上使用,而且模擬器甚至可以巢狀,比如在模擬某人做模擬的時候[33]。時間過程time
、等待過程wait
、保持過程hold
和啟用過程ACTIVAT
,定義在Simulation
類之中。
行程類Process
是Simulation
類的巢狀類。啟用陳述式activate
,只有處在Simulation
類所包含的類的對象之中,或處在其字首部份是這種對象的有字首塊之中,才是有效的。啟用陳述式的作用,被定義為得到呼叫啟用過程ACTIVAT
的那種效果。
集合類Simset
是Simulation
類的字首類,其中定義了三個巢狀類:表示集合整體的Head
類,表示集合元素的Link
類,和二者的字首類鏈結串列類Linkage
。首位過程first
,定義在Head
類中;退出過程out
和在等待過程wait
中用到的進入過程into
,定義在Link
類中。
在試衣間類FittingRoom
中,為了讓人們排隊等待訪問試衣間,使用了門對象door
,它是佇列類即Head
類的一個對象。試衣間類別定義了兩個過程:要求過程request
:如果試衣間中正有人使用,則他必須等待於門佇列之中,即wait(door)
;當他可以使用試衣間之時,相應的從門佇列中移除自己,即door.first.out
。離開過程leave
:如果門佇列中有人等待的話,放行其中第一個人,即activate door.first
。
個人類Person
是Process
類的子類,它的行動是瀏覽商店和在試衣間試衣,使用保持過程hold
來描述其所用時間,並呼叫對應的試衣間對象的過程來要求和離開試衣間。正態分佈的隨機抽籤過程normal
,定義在環境類ENVIRONMENT
中,它的最後的參數,必須是指定一個偽亂數串流的一個整數。
主程式是行程類Process
的子類MAIN_PROGRAM
類的對象。它建立FittingRoom
類別的實例fittingRoom1
對象,接着建立並啟用Person
類的三個對象,從而將他們三人放置入事件佇列之中,主程式在終止前保持100分鐘的模擬時間。
參見
註釋
延伸閱讀
外部連結
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.