Modula-3
Modula-3是一種系統編程語言,它是叫做Modula-2+的升級版本的Modula-2的後繼者。雖然它已經在研究界有了影響力,受其影響的語言有Java、C#和Python[9],但未能在工業上被廣泛採用。它是在1980年代末由在數字設備公司(DEC)系統研究中心(SRC)和Olivetti研究中心(ORC)的Luca Cardelli、James Donahue、Lucille Glassman、Mick Jordan(之前在Olivetti軟件技術實驗室工作)、Bill Kalsow和Greg Nelson設計。
編程範型 | 指令式, 過程式, 結構化, 模塊化, 並發 |
---|---|
語言家族 | Wirth Modula |
設計者 | Luca Cardelli, James Donahue, Lucille Glassman, Mick Jordan, Bill Kalsow, Greg Nelson[1] |
實作者 | DEC, Olivetti, elego Software Solutions GmbH |
面市時間 | 1988年 |
型態系統 | 強類型, 靜態, 安全或在不安全時顯式安全隔離 |
作用域 | 詞法 |
系統平台 | IA-32, x86-64, PowerPC, SPARC |
操作系統 | 跨平台: FreeBSD, Linux, Darwin, SunOS |
網站 | www |
主要實作產品 | |
SRC Modula-3, CM3[2], PM3[3], EZM3[4], M3/PC Klagenfurt[5] | |
啟發語言 | |
ALGOL, Euclid, Mesa, Modula-2, Modula-2+, Oberon, Object Pascal | |
影響語言 | |
C#, Java, Nim[6], Python[7], Baby Modula-3[8] |
Modula-3的主要特點,是保持系統編程語言的強力,同時具有簡單性和安全性。Modula-3意圖延續Pascal的類型安全和Modula-2的模塊化編程傳統,同時為實際編程引入新構造。特別是Modula-3添加了如下支持:例外處理、關鍵字參數與缺省參數值、有跟蹤的引用與垃圾回收、面向對象、不透明類型及其披露、泛型、多線程和不安全代碼顯式標記。Modula-3的設計目標,是以非常基本的形式,實現指令式編程語言的大部份重要的現代特徵。因此省略了涉嫌危險和複雜的特徵,如多重繼承和運算符重載。
發展歷史
Modula-3項目始於1986年11月,當時莫里斯·威爾克斯向尼克勞斯·維爾特寫信,提出了一些關於Modula新版本的想法。威爾克斯在此之前一直在DEC工作,他回到英格蘭並加入了Olivetti的研究策略委員會。Wirth已經轉移到了Oberon,但Wilkes的團隊在Modula名下繼續開發沒有任何問題。語言定義於1988年8月完成,並於1989年11月更新了版本[10]。DEC和Olivetti的編譯器很快就出現了,隨後又有了第三方實現。
它的設計受到SRC和Acorn計算機研究中心(ARC,在Olivetti收購Acorn之後叫做ORC)在Modula-2+語言上的工作的很大影響[11],Modula-2+是編寫DEC Firefly多處理器VAX工作站的操作系統的語言[12][13],還是在基於ARM的Acorn Archimedes系列計算機的ARX操作系統項目中,ARC編寫用於Acorn C和模塊執行庫(CAMEL)的Acorn編譯器的語言[14]。正如修訂的Modula-3報告所述[14],該語言演化自Mesa、Modula-2、Cedar和Modula-2+,還受到其他語言的影響如:Object Pascal,Oberon和Euclid。
在20世紀90年代,Modula-3作為一種教學語言獲得了可觀的傳播[15],華盛頓大學在1994年發行的微內核操作系統SPIN是用Modula-3開發的[16],但它從未廣泛用於工業用途。造成這種情況的原因,可能是Modula-3的關鍵支持者DEC的消亡,特別是在1998年DEC被出售給康柏之前就不再有效地維護它了。無論如何,儘管Modula-3有着簡單性和強大功能,似乎當時對有限實現了面向對象編程的過程式編譯語言的需求很少。
Critical Mass公司曾在一段時間內提供了一個名為CM3的商業編譯器,它由以前DEC SRC的在DEC被出售給Compaq之前雇用的一個主要實現者維護,和叫做Reactor的一個集成開發環境以及可擴展的Java虛擬機(以二進制和源格式許可發行並可用Reactor來構建)。但該公司在2000年停止了活動,並將其產品的一些源代碼提供給了elego軟件解決方案有限公司。
基本上,Modula-3的唯一企業支持者是elego軟件解決方案有限公司,它繼承了Critical Mass的資源,並且已經以源代碼和二進制形式發布了幾個版本的CM3系統。Reactor IDE在幾年後才開源發布,新的名稱是CM3-IDE。2002年3月,elego還接管了此前由蒙特利爾工程學院維護的另一個活躍的Modula-3發行版PM3的存儲庫,在此後幾年間以HM3名義進行了持續改進,直至最終被淘汰。
Modula-3是文檔記錄了語言特徵演化的少見語言之一,在《用Modula-3系統編程》中[17],收錄了設計者對四個設計要點的討論:結構等價與名稱等價、子類型規則、泛型模塊和參數模態。Modula-3現在主要是在大學中的比較編程語言課程中教授,其教科書已絕版。
程序示例
Modula-3中所有的程序至少具有一個模塊文件,而大多數程序還包括一個接口文件,客戶端使用它來訪問來自模塊的數據。語言語法的一個常見示例是Hello world程序:
MODULE Main;
IMPORT IO;
BEGIN
IO.Put("Hello World\n")
END Main.
與其他語言一樣,Modula-3程序必須有導出Main
接口的實現模塊,如上述示例中實現模塊缺省導出了同名的接口,它可以處在名為Main.m3的文件中;或者是通過EXPORTS
導出Main
接口的實現模塊,比如:
MODULE HelloWorld EXPORTS Main;
IMPORT IO;
BEGIN
IO.Put("Hello World\n")
END HelloWorld.
建議這個模塊所在的文件的名字與實際模塊名字相同,當它們不同時編譯器只是發出一個警告。
語言設計理念
Modula-3委員會將Modula-3設計為ALGOL語言家族的現代代表,他們認為:
- 始自BCPL的C語言家族,其優美在於以適度的代價得到了相對匯編語言的巨大飛躍,但它們接近於目標機器的低層編程模型是天然有危險性的;其中的衍生語言C++,通過增加對象豐富了C,但它放棄了C的最佳優點簡單性,卻沒有消除它最差缺點即低層編程模型。
- 另一個極端是LISP語言家族,它們有着混合了lambda演算和配對函數理論的編程模型,但它們趨於難以高效實現,因為在編程模型中值的統一對待,招致了所有的值都由指針統一表示的運行時間系統;這個家族的語言的良好實現避免了多數代價而能用於系統編程,但它們普遍秉性仍舊嚮往堆分配而非棧分配,這種運行時間系統經常將它們孤立於封閉環境中,而不能容納用其他語言寫的程序。
- ALGOL語言家族處在這兩個極端之間,它的現代代表包括Pascal、Ada、Modula-2等,這些語言擁有的編程模型反映了隨機存取機在工程上的約束,隱藏了任何特定於機器的細節。它們放棄了LISP家族的優美和數學對稱性,使得無須特殊技巧就可能高效的實現,並且避免了C家族的多數危險和依賴機器的特徵。
ALGOL家族語言都有強類型系統,其基本想法是把值空間劃分成類型,限制變量持有單一類型的值,並且限制運算應用於固定類型的運算數。這個語言家族的趨勢,在1960年代是朝向控制流程和數據結構特徵,在1970年代是朝向信息隱藏特徵如接口、不透明類型和泛型,在1980年代是採納來自LISP家族和始自BCPL的C家族的仔細挑選的技術。
Modula-3委員會堅持一個自稱為「複雜度預算」的設計原則:在五十頁內將語言準確而易讀的描述出來,為此只容納最有用的特徵。精選出來的特徵直接針對兩個主要目標:
這些特徵中任何一個都沒有特殊的新穎性,但是組合起來是簡單而強力的。人們越是更好的理解程序,就越是使用更大的建造塊去構造它。指令之後是語句,語句之後是過程,過程之後是接口,下一步應當是抽象類型。在理論層面,抽象類型,是通過其操作的規定,而非通過其數據的表示,來定義的類型。
詞法記號
關鍵字:
|
|
|
|
|
|
保留標識符:
|
|
|
|
|
|
|
運算符:
+ |
< |
#
|
= |
; |
.. |
:
|
- |
> |
{ |
} |
| |
:= |
<:
|
* |
<= |
( |
) |
^ |
, |
=>
|
/ |
>= |
[ |
] |
. |
& |
此外,注釋是開於(*
併合於*)
的任意字符的序列。注釋可以嵌套並可以擴展跨越多於一行。
基礎性語法和語義
Modula-3承襲了ALGOL家族傳統的強類型,並且為了使類型系統儘可能的統一,採用了如下原則:
- 首先,類型或目標類型確定是沒有歧義的,所有的表達式的類型由它的子表達式來確定,而非由它的使用來確定。
- 其次,沒有自動轉換。
- 第三,可賦值性和類型兼容性,是依據單一的在語法上用算符
<:
指定的子類型關係來定義的,它具有如下性質:如果T
是U
的子類型,則所有的T
的成員都是U
的成員,子類型關係是自反和傳遞的;子類型關係是處理具有繼承的對象所需要的,但也能用於確定常規類型的兼容性規則。
一個典型的不安全運行時間錯誤,是釋放仍可由活躍引用(懸空指針)觸及的數據結構。避免這個問題的唯一可靠方式,是不再觸及存儲的自動釋放,或稱為垃圾收集。Modula-3因而提供了有跟蹤的引用,它類似於Modula-2的指針,除了它們所指向的存儲被保存在有跟蹤的堆之中之外,在這裡它們將在到它們的引用都消失時自動釋放。垃圾收集的另一個巨大利益是簡化了接口,沒有垃圾收集,接口就必須規定客戶或實現是否有責任釋放每個分配的引用,和在什麼條件下可以安全的去這麼做。
例外是一次退出多層作用域的控制結構。一個例外被重複的發起退出當前活躍的作用域,直到找到給這個例外的處理器(handler),並將控制權移交給這個處理器,如果沒有處理器,則計算以某種依賴於系統的方式終止,比如進入調試器。在Modula-3中,EXIT
和RETURN
語句的語義,分別使用退出例外exit-exception
和返回例外return-exception
來定義。Modula-3的設計者認為語言實現,除了這種返回例外和退出例外之外,更應當以各種例外為代價,加速正常的結果產出;為每個引發的例外花費一千條指令,來為每個過程節約一條指令都是合理的。
塊與聲明
聲明介入一個常量、類型、變量、例外或過程的一個名字。這個名字的作用域是包含這個聲明的塊。一個塊可以出現為一個模塊或過程的本體,或一個塊語句,Pascal和Modula-2沒有塊語句[18]。一個塊有如下形式:
Decls
BEGIN
S
END
這裡的Decls
是成序列的聲明,而S
是一個語句,經常是順序複合語句即成序列的語句,它是這個塊的執行部份。這裡的Decls
與S
在縮進上對齊,是因為在Pascal語言家族中,可能含有嵌套過程聲明的聲明序列的位置相較於ALGOL 60,從緊隨在關鍵字BEGIN
之後轉移到了緊靠在其前面。一個塊的聲明可以介入一個名字最多一次,然而一個名字可以在嵌套的塊中重新聲明。在塊中這些聲明的次序,除了確定變量的初始化次序之外無關緊要。
一個Modula-3程序規定了一個計算,它作用在一序列的叫做地點(location)的數字元件之上。變量是地點的一個集合,它依據這個變量的類型所確定的約定,表示一個數學上的值。如果一個值可以由類型T
的某個變量來表示,則稱謂這個值是T
的一個成員,並且T
包含這個值。
聲明為一個變量、類型、過程、常量或例外指定一個被稱為標識符的符號作為名字。一個聲明應用於其上的程序區域,叫做這個聲明的作用域。作用域是可以嵌套的。一個標識符的含義,由這個標識符在其中聲明的最小的包圍作用域來確定。
類型聲明有如下形式:TYPE T = U
,這裡的T
是一個標識符,而U
是一個類型或類型表達式。在需要類型的各種地方,通常允許類型表達式。
變量聲明有如下形式:VAR id: T := E
,這裡的id
是一個標識符,T
是除了開放數組之外的一個非空類型,而E
是一個表達式。它聲明了id
是類型T
的一個變量,其初始值是E
的值。:= E
和: T
任何一個都可省略,但二者不能都省略。如果省略了T
,它採用E
的類型。如果省略了E
,初始值是類型T
的任意值。如果二者都存在,E
必須可賦值給T
。初始值是一種簡寫,等價於在隨後的塊的可執行部份開始處插入賦值id := E
,如果多個變量有初始值,按它們聲明的次序插入它們的賦值。形式VAR v_1, ..., v_n: T := E
,是VAR v_1: T := E; ...; VAR v_n: T := E
的簡寫。
常量聲明有如下形式:CONST id: T = C
,這裡的id
是一個標識符,T
是一個類型,而C
是一個常量表達式。它聲明id
為具有類型T
和值C
的一個常量。: T
可以省略,在這種情況下id
的類型是C
的類型。如果T
存在,則它必須包含C
。
值與類型
Modula-3的類型系統使用結構等價,而非Modula-2的名稱等價即類型構造子的每次出現產生一個新類型。在結構等價中,在如果兩個類型的定義在展開的時候,也就是在所有常量表達式都被替換為它們的值,並且所有類型名字都被替代為它們的定義的時候,變成相同的,則這兩個類型是相同的。在遞歸數據類型的情況下,這個展開是部份展開的無窮極限。
有三種序數類型:枚舉、子範圍和整數類型,預定義了如下序數類型:INTEGER
、LONGINT
、CARDINAL
(如同子範圍[0..LAST(INTEGER)]
)、BOOLEAN
(枚舉{FALSE, TRUE}
)和CHAR
(包含至少256個元素表示ISO-Latin-1代碼的枚舉)。整數和枚舉元素合起來叫做序數值。序數值v
如果是整數或擴展精度整數,則v
的基礎類型分別是INTEGER
或LONGINT
,否則v
的基礎類型是包含它的那個唯一性的枚舉類型。兩個序數如果有相同的值則二者相等。
有三種浮點類型:REAL
、LONGREAL
和EXTENDED
,這些類型的屬性規定在對應的必要接口中。兩個浮點數如果定義它們的底層實現相同則二者相等。
一個引用值要麼是NIL
,要麼是一個叫做所引用者(referent)的變量的地址。預定義了如下引用類型:REFANY
,它包含所有的有跟蹤的引用,ADDRESS
,它包含所有的無跟蹤的引用,和NULL
,它只包含NIL
。Modula-3支持在運行時間的數據分配。分配操作有如下形式:NEW(T, ...)
,這裡的T
是除了預定義的REFANY
、ADDRESS
或NULL
之外的引用類型。兩個引用如果尋址相同的地點則二者相等。
記錄是叫做這個記錄的字段的命名變量的一個序列,記錄類型的聲明有如下形式:TYPE T = RECORD FieldList END
,在這裡的類型表達式中,算子RECORD
是類型構造子,而它的實際參數是字段聲明的一個列表FieldList
,其中每個字段都有如下形式:fieldName: Type := default
,這裡的字段名字fieldName
是一個標識符,而字段類型Type
是除了開放數組之外的非空類型,而字段缺省值default
是一個常量表達式。形式f_1, ..., f_m: Type := default
,是f_1: Type := default; ...; f_m: Type := default
的簡寫。兩個記錄如果有相同的字段,並且對應的字段都相等則二者相等。
在Modula-3中的對象,非常類似於Simula 67中的對象:它們總是引用,它們擁有數據字段和方法二者,並且它們有着單一繼承而非多重繼承。預定義了如下對象類型:ROOT
,它是沒有字段或方法的有跟蹤的對象類型,和UNTRACED ROOT
,它是沒有字段或方法的無跟蹤的對象類型。
不透明類型是一個名字,它指示某個給定引用類型的未知子類型。預定義了兩個不透明類型:TEXT <: REFANY
和MUTEX <: ROOT
,它們分別表示文本串和互斥信號量,它們的屬性分別規定於必要接口Text
和Thread
之中。
數組是叫做這個數組的元素的組成變量的有索引的搜集。有兩種數組類型,固定的和開放的。固定數組的長度是在編譯時間確定的,開放數組的長度是在運行時間分配確定的,這個長度此後不能改變。兩個數組如果有相同的長度,並且對應的元素都相等則二者相等。
包裝類型的變量出現在記錄、對象或數組中,占據指定數量的位元,並毗鄰於前導的字段或元素緊壓打包。包裝類型的聲明有如下形式:TYPE T = BITS n FOR Base
,這裡的Base
是一個基礎類型,而n
是整數取值的常量表達式。
集合是取自某個序數類型的值的搜集。集合類型聲明有如下形式:TYPE T = SET OF Base
,這裡的基礎類型Base
是一個序數類型。兩個集合如果有相同的元素則二者相等。
不包含值的一個類型是空的,例如[1..0]
是一個空類型。空類型可以用來建造非空類型,比如SET OF [1..0]
。聲明一個空類型的一個變量是靜態錯誤。
所有的表達式都有一個唯一的類型,但是一個值可以是很多類型的一個成員,例如值6
是[0..9]
和INTEGER
二者的成員,因此論述「一個值的類型」將會是有歧義的。短語「x
的類型」,意味着「表達式x
的類型」;然而短語「x
是T
的成員」,意味着「x
的值是T
的成員」。但在有一種情況下,一個值可以稱為有着一個類型:所有的對象或有跟蹤的引用值,包括了一個類型代碼,它叫做這個引用值的分配類型。分配類型通過TYPECASE
語句來測試。
語言規定提供了類型運算BITSIZE()
、BYTESIZE()
和ADRSIZE()
,分別返回變量x
或者類型T
的那些變量的位元數目、8位字節的數目和可尋址地點的數目。在所有情況下,x
必須是指定式,而T
必須不是開放數組類型,指定式x
只在它的類型是開放數組的時候求值。
序數類型
枚舉類型聲明有如下形式:TYPE T = {id_1, id_2, ..., id_n}
,這裡的id
是各不相同的標識符,類型T
是n
個值的有序集合,表達式T.id_i
指示這個類型在遞增次序下的第i
個值,空枚舉{ }
是允許的。
子範圍類型聲明有如下形式:TYPE T = [Lo..Hi]
,這裡的Lo
和Hi
是有相同的基礎類型的兩個序數值,T
的值是從Lo
到Hi
含二者所有的值,它們必須是常量表達式,如果Lo
超過Hi
,則子範圍是空的。
每個不同的枚舉類型介入新的一組值,而子範圍類型重複使用底層類型的值。例如:
TYPE
T1 = {A, B, C};
T2 = {A, B, C};
U1 = [T1.A..T1.C];
U2 = [T1.A..T2.C]; (* sic *)
V = {A, B}
這裡的T1
和T2
是相同類型,因為它們有相同的展開定義。U1
和U2
也是相同的類型,因為T1.C = T2.C
。類型T1
和U1
卻是不同的,儘管它們包含相同的值,但是T1
的展開定義是枚舉,而U1
的展開定義是子範圍。類型V
是第三個類型,它的值V.A
和V.B
無關於值T1.A
和T1.B
。
語言規定提供了支持序數類型的一些類型運算:對於序數類型T
,NUMBER(T)
返回在T
中元素的數目,FIRST(T)
返回T
的最小值,LAST(T)
返回T
的最大值。ORD(T.id)
將一個枚舉的元素T.id
,轉換成表示它在枚舉次序中所處位置的整數,如果元素的類型是枚舉T
的子範圍,結果是元素在T
內的位置,而非在子範圍內。VAL(i, T)
將表示在枚舉類型T
中位置的整數i
,轉換成在這個枚舉中此位置上的那個元素,如果T
是枚舉類型的子範圍,結果是在源出枚舉類型的位置i
上的元素,而非在子範圍內。如果n
是類型T
的一個整數,則有平凡的:ORD(n) = VAL(n, T) = n
。
數組類型
固定數組類型聲明有如下形式:TYPE T = ARRAY Index OF Element
,這裡的索引類型Index
是序數類型,而元素類型Element
是除了開放數組之外的任何類型。開放數組類型聲明有如下形式:TYPE T = ARRAY OF Element
,這裡的元素類型Element
是任何類型。
數組類型T
的值,是元素類型為Element
的數組;對於固定數組類型,這個數組的長度是索引類型Index
的元素數目;對於開放數組類型,這個數組的長度是任意的;開放數組的索引類型是INTEGER
子範圍[0..n-1]
,這裡的n
是這個數組的長度。開放數組類型只能用作形式參數的類型,引用類型的所引用者,另一個開放數組的元素類型,或作為數組構造子中的類型。
多維數組的形狀是它每一維的長度的序列。更精確的說,一個數組的形狀是它的長度跟隨着任何它的元素的形狀,非數組的形狀是空序列。數組如果有相同的類型和形狀,則它們是可賦值的。如果賦值的要麼來源要麼目標是開放數組,則需要運行時間檢查。形如ARRAY Index_1, ..., Index_n OF Element
的表達式,是ARRAY Index_1 OF ... OF ARRAY Index_n OF Element
的簡寫。
如果a
有數組類型T
,則a[i]
指示a
的一個指定元素,它在數組中的位置對應於i
在索引類型中位置。形如a[i_1, ..., i_n]
的表達式,是a[i_1]...[i_n]
的簡寫。索引的解釋由一個數組的類型確定,而非它的值;數組賦值改變數組變量的值,而非它的類型。例如:
VAR
a := ARRAY [1..3] OF REAL {1.0, 2.0, 3.0};
b: ARRAY [-1..1] OF REAL := a;
這裡在a
或b
做元素值變更之前,a = b
為TRUE
,儘管a[1] = 1.0
而b[1] = 3.0
。
可以使用NUMBER(A)
,得到一個固定數組類型的索引類型中的元素數目,使用FIRST(A)
和LAST(A)
,分別得到一個固定數組類型的索引類型的最小元素和最大元素。可以使用NUMBER(a)
,得到一個數組的索引類型中的元素數目,使用FIRST(a)
和LAST(a)
,分別得到一個數組的索引類型的最小元素和最大元素,在這種情況下,表達式a
只在它指示一個開放數組的時候求值。
引用類型
一個引用類型要麼是有跟蹤的要麼是無跟蹤的。當到已分配的一段存儲的所有的有跟蹤的引用都消失時,系統對這段存儲進行垃圾回收。有跟蹤的引用類型以如下形式來聲明:TYPE T = REF Type
,這裡的Type
是任何類型。T
的值是到類型Type
的變量的有跟蹤的引用,Type
叫做T
的所引用類型。無跟蹤的引用類型以如下形式來聲明:TYPE T = UNTRACED REF Type
,這裡的Type
是任何無跟蹤的類型(這個限制在不安全模塊中解除)。
兩個引用類型如果都是有跟蹤的或都是無跟蹤的,那麼它們有相同的引用類。一個一般性的類型是有跟蹤類型的條件為,它是有跟蹤的引用類型,其任何字段類型是有跟蹤類型的記錄類型,其元素類型是有跟蹤類型的數組類型,或其底層未包裝類型是有跟蹤類型的包裝類型。
在有跟蹤的和無跟蹤的二者情況下,關鍵字REF
可選的可以前導上BRANDED b
,這裡的b
是叫做銘牌(brand)的文本常量。銘牌用來區分原本上相同的類型,它們沒有其他語義效果。在一個程序中所有銘牌都必須是唯一性的。如果BRANDED
出現而b
缺席,系統自動的為b
提供一個唯一的值。
分配操作NEW(T, ...)
返回T
的所引用類型的一個新分配的變量的地址,如果T
是對象類型,則返回到有配對的方法套件的一個新分配的數據記錄的引用。NEW()
返回的引用不同於所有的現存引用。這個新引用的分配類型是T
。如果T
的所引用類型為空則是一個靜態錯誤。如果T
是到有k
個開放維度的數組的引用,NEW(T)
操作有如下形式:NEW(T, n_1, ..., n_k)
,這裡的這些n
是指定新數組在它的前k
個維度上的長度的整數取值表達式。
所有的對象類型和有跟蹤的引用類型(包括NULL
)都有一個關聯的整數代碼。不同的類型有不同的代碼。給一個類型的代碼對程序的任何單一執行都是常量,但對不同的執行可以不同。類型運算TYPECODE(T)
,返回類型T
的這個代碼,而TYPECODE(r)
,返回引用r
的分配類型的這個代碼。如果T
是REFANY
,或者不是對象類型或有跟蹤的引用類型,則是一個靜態錯誤。類型運算ISTYPE(x, T)
,測試x
是否為T
的成員,這裡的T
必須是對象類型或有跟蹤的引用類型,而x
必須可賦值給T
。
表達式
表達式規定產生一個值或變量的一個計算。所有的表達式都有一個靜態確定的類型,它包含這個表達式可以產生的所有的值。在語法上,一個表達式要麼是一個運算數,要麼是應用於自身也是表達式的實際參數的一個運算。運算數是標識符、文字或類型。通過遞歸的求值一個運算的實際參數並應用這個運算,來求值一個表達式。實際參數的求值次序,對除了短路求值的AND
和OR
之外的所有運算都是未定義的。
對一個過程的調用,在這個過程是返回一個結果的函數過程的時候,是稱為函數應用的一個表達式,這個表達式的類型是過程的結果類型。
Modula-3語言規定提供了前綴算術運算:+
、-
,中綴算術運算:+
、-
、*
、/
、DIV
、MOD
,數值函數運算:ABS(x)
、FLOAT(x, T)
、FLOOR(x)
、CEILING(x)
、ROUND(r)
、TRUNC(r)
、MAX(x, y)
、MIN(x, y)
,關係運算:=
、#
、 <=
、>=
、>
、<
,邏輯運算:NOT p
、p AND q
、p OR q
,文本串接運算:a & b
,內存分配運算:NEW(T, ...)
,集合運算:併集+
、差集-
、交集*
、對稱差集/
和隸屬關係測試e IN s
,它在序數e
是集合s
的元素的時候返回TRUE
。
表達式可建構自如下文字:整數文字、實數文字、字符文字和文本文字。集合、數組和記錄有構造子表達式:
S{e_1, ..., e_n}
,這裡的S
是一個集合類型,而這些e
是表達式或lo..hi
形式的範圍。這個構造子指示類型為S
的一個值,它包含列出的那些值和在列出的範圍中的那些值,這些e
、lo
和hi
必須可賦值給類型S
的元素類型。A{e_1, ..., e_n}
,這裡的A
是一個數組類型,而這些e
是表達式。這個構造子指示類型為A
的一個值,它包含遵循列出次序的這些列出元素,這些e
必須可賦值給A
的元素類型。如果A
是多維數組,則這些e
必須自身也是數組取值表達式。如果A
是固定數組類型,並且n
至少為1
,則e_n
可以跟隨着, ..
,指出e_n
的值按照需要重複任意多次來填滿這個數組。R{Bindings}
,這裡的R
是一個記錄類型,而Bindings
是完全同於過程調用的關鍵字或位置綁定的一個列表。這個綁定列表會被重寫來適合R
的字段和缺省值的列表,完全同於過程調用中的那些做為,記錄字段名字扮演了過程形式參數的角色。這個構造子指示類型為R
的一個值,它的字段值由重寫後的這些綁定來指定。
常量表達式是一般性的表達式類的子集,所要求的限制使得可以靜態的求值這個表達式。在常量表達式中,除了NEW()
、解引用(顯式的或隱式的)、SUBARRAY()
、TYPECODE()
、NARROW()
、ISTYPE()
、ADR()
和LOOPHOLE()
之外的所有運算都是合法的,唯獨可運用的過程,是在提供無符號字運算的Word
接口中的函數。變量在常量表達式中,只能作為給FIRST()
、LAST()
、NUMBER()
、BITSIZE()
、BYTESIZE()
或ADRSIZE()
的實際參數出現,並且這個變量必須不是開放數組類型的。文字和頂層過程常量在常量表達式中是合法的。
指定式
產生變量的表達式叫做指定式(designator)。常量表達式永遠不是指定式。指定式的類型是它產生的變量的類型。一個指定式依賴於上下文,可以指示要麼一個變量,要麼這個變量的值。比如,它作為賦值的左值時是變量,作為右值時是這個變量的值。一些指定式是只讀的,它們不能用在可能改變這個變量的值的上下文中,並非只讀的指定式就是可寫的指定式。
一個標識符是可寫的指定式的條件為,它被聲明為變量,是VAR
或VALUE
形式參數,TYPECASE
或TRY-EXCEPT
語句的局部變量,或綁定到可寫指定式的WITH
局部變量。一個標識符是只讀的指定式的條件為,它是READONLY
形式參數,FOR
語句的局部變量,或綁定到非指定式或只讀的指定式的WITH
局部變量。只有如下運算可以產生指定式:
r^
,這個運算叫做解引用(dereferencing),它表示r
的所引用者。表達式r^
總是可寫的指定式。如果r
的類型是REFANY
、ADDRESS
、NULL
、對象類型或不透明類型,則是一個靜態錯誤;如果r
是NIL
,則是一個必查的運行時間錯誤。a[i]
,這個運算叫做下標,它表示數組a
的值的第i + 1 - FIRST(a)
個元素,比如說a
的索引類型是[-1..1]
,a[1]
是這個數組的值的第1 + 1 - (-1)
個即第3
個元素。表達式a[i]
是否為指定式進而是否可寫同於a
。表達式i
必須可賦值給a
的索引類型。如果a
是到數組的引用,則這裡有隱含的解引用,即這裡的a[i]
是a^[i]
的簡寫。r.f
、o.f
、I.x
、o.m
、T.m
和E.id
,這種點表示法運算叫選取(selection)。r.f
表示記錄r
的指名字段f
,表達式r.f
是否為指定式進而是否可寫同於r
。如果r
是到記錄的引用,則這裡有隱含的解引用,即r.f
是r^.f
的簡寫。o.f
表示對象o
的指名字段f
,表達式o.f
是可寫的指定式。I.x
表示導入的接口I
的指名實體x
,如果x
被聲明為變量,則表達式I.x
是指定式並且總是可寫的。o.m
表示對象o
的指名方法m
,o.m
不產生指定式。T.m
表示對象類型T
的指名方法m
,T.m
不產生指定式。E.id
表示枚舉類型E
的值id
,E.id
不產生指定式。
SUBARRAY(a, from, for)
,它產生數組a
的越過前from
個元素並包含下for
個元素的子數組。SUBARRAY()
不複製數組,它是否為指定式進而是否可寫同於a
。如果a
是多維數組,SUBARRAY()
只應用於頂層數組。如果from + for
超過了NUMBER(a)
,則是一個必查的運行時間錯誤。
子類型規則
使用T <: U
指示T
是U
的子類型,也就是U
是T
的超類型。子類型規則是:如果T <: U
,則類型T
的所有值,都是類型U
的值,反過來則不成立。
對於序數類型T
和U
,T <: U
的條件為,它們擁有相同的基礎類型,並且所有的T
的成員都是U
的成員。就是說序數類型上的子類型,反映了在值集合上的子集關係。
對於數組類型,一個數組類型A
是一個數組類型B
的子類型的條件為,它們有相同的最終元素類型,相同的維度數,並且對於每個維度,要麼二者都是開放的,要麼A
是固定的而B
是開放的,要麼它們都是固定的並有着相同的大小。
對於過程類型,T <: U
的條件為,它們除了形式參數名字、缺省值和RAISES
集合之外是相同的,並且T
的RAISES
集合包含在U
的RAISES
集合中。NIL
是所有的過程類型的一個成員。
對於包裝類型,BITS n FOR T
和T
有相同的值,也就是說:BITS n FOR T <: T
並且T <: BITS n FOR T
。
對於集合類型[19],SET OF A <: SET OF B
的條件是A <: B
,就是說集合的子類型規則簡單的使用值集合規則。
對於引用類型有:
NULL <: REF T <: REFANY
NULL <: UNTRACED REF T <: ADDRESS
就是說,REFANY
和ADDRESS
分別包含所有的有跟蹤的和無跟蹤的引用,NIL
是所有的引用類型的一個成員。這兩個規則也適用於加銘牌的類型。
對於對象類型有:
ROOT <: REFANY
UNTRACED ROOT <: ADDRESS
NULL <: T OBJECT ... END <: T
就是說,所有對象都是引用,NIL
是所有的對象類型的一個成員,而所有的子類型都包括在它的超類型之中。第三個規則也適用於加銘牌的類型。
對於所有的T
,都有T <: T
。對於所有的T, U, V
,T <: U
並且U <: V
,蘊涵T <: V
。就是說<:
是自反和傳遞的。但是T <: U
並且U <: T
,不蘊涵T
與U
是相同的,因為子類型關係不受形式參數名字、缺省值和包裝的影響。
可賦值性
一個類型T
可賦值(assignable)給類型U
的條件是:
T <: U
,或者U <: T
並且T
是一個數組或除了ADDRESS
之外的一個引用類型(這個限制在不安全模塊中解除),或者T
和U
是至少有一個共同成員的序數類型。
在第一種情況下,沒有運行時間錯誤是有可能的。在第二種情況下,允許賦值一個REFANY
到一個REF T
,還允許賦值一個ARRAY OF T
到一個ARRAY I OF T
,這裡分別要求對引用的類型和數組長度的運行時間檢查;可以使用類型運算NARROW(x, T): T
,將對象類型或有跟蹤的引用類型的超類型變量當作其子類型的成員,這時需要運行時間檢查,Modula-2+介入它就是為了以安全的方式,將REFANY
變量賦值給確知的實際類型REF T
。在第三種情況下,常規的範圍檢查就能確保特定的T
的成員也是U
的成員。
一個表達式e
可賦值給一個變量v
的條件是:
e
的類型可賦值給v
的類型,並且e
的值是v
的類型的一個成員,它不是一個局部過程,並且如果它是一個數組,則有同v
一樣的形狀。
一個表達式e
可賦值給類型T
,如果e
可賦值給類型T
的某個變量。如果T
不是一個開放數組,這等同聲稱e
可賦值給類型T
的任何變量。
語句
執行一個語句產生一個計算,它可以正常產出結果(在可計算理論中稱為停機),發起一個例外,導致一個必查的運行時間錯誤,或無限循環。如果結果是一個例外,它可以可選的配對上一個實際參數。如果一個表達式作為一個語句的執行部份而求值,這個求值引發一個例外,則這個例外成為這個語句的結果。空語句是無操作的(no-op),在語言報告中寫為(*skip*)
。
過程調用在這個過程是真正過程的時候是一個語句。調用一個函數過程並丟棄它的結果使用EVAL
語句,它有如下形式:EVAL e
,這裡的e
是一個表達式。它的效果是求值e
並忽略其結果。RETURN
語句用於從過程中返回。
賦值語句有如下形式:v := e
,這裡的v
是可寫的指定式,而e
是可賦值給v
所指示變量的表達式。賦值語句將v
設置為e
的值,v
和e
的求值次序是未定義的,但e
會在v
更新之前求值。特別是,如果v
和e
是有交疊的子數組,以沒有元素在被用作來源之前被用作目標的方式進行賦值。
順序複合語句有如下形式:S_1; S_2
,它在Modula-2中稱為「語句序列」。一些編程者使用分號作為語句終結符(terminator),另一些編程者將它用作分隔符(separator);Modula-3允許這兩種風格,語言報告將其用作分隔符。
塊語句有如下形式:Decls BEGIN S END
,這裡的Decls
是一序列的聲明,S是一個語句。塊介入在Decls
中聲明的常量、類型、變量和過程,並接着執行S
。聲明的名字的作用域是這個塊。
選擇控制結構
Modula-3提供了選擇控制結構IF
和CASE
語句。IF
語句有如下形式:
IF B_1 THEN S_1
ELSIF B_2 THEN S_2
...
ELSIF B_n THEN S_n
ELSE S_0
END
這裡的這些B
是布爾表達式,而這些S
是語句。ELSE S_0
和每個ELSIF B_i THEN S_i
都是可選的。IF
語句依次求值B
,直到某個B_i
求值為TRUE
,則接着執行S_i
。如果沒有表達式求值為TRUE
,並且ELSE S_0
存在,則執行S_0
。如果沒有表達式求值為TRUE
,並且ELSE S_0
缺席,則IF
語句是無操作的(no-op)。Modula-3的CASE
語句,不同於Modula-2的同名語句,它有如下形式:
CASE Expr OF
L_1 => S_1
| ...
| L_n => S_n
ELSE S_0
END
這裡的Expr
是類型為序數類型的一個表達式,而每個L
是一個列表,這個列表構成自常量表達式,或用e_1..e_2
指示的常量表達式的範圍,它表示從e_1
到e_2
含二者的值。如果e_1
超出了e_2
,這個範圍為空。如果兩個L
表示的集合有交疊,或者任何常量表達式不是類型Expr
的一個成員,則是一個靜態錯誤。ELSE S_0
是可選的。
CASE
語句求值Expr
。如果結果的值在任何L_i
之中,則執行S_i
。如果這個值不在任何L_i
之中,並且ELSE S_0
存在,則執行它。如果這個值不在任何L_i
中,並且ELSE S_0
缺席,則發生一個必查的運行時間錯誤。一些編程者使用豎槓作為初始符(initiator),另一些編程者將它用作分隔符。Modula-3允許這兩種風格,語言報告將其用作分隔符。
分配類型測試
TYPECASE
語句有如下形式:
TYPECASE Expr OF
T_1 (v_1) => S_1
| ...
| T_n (v_n) => S_n
ELSE S_0
END
這裡的Expr
是類型為引用類型的一個表達式,這些S
是語句,這些T
是引用類型,而這些v
是標識符。如果Expr
有類型ADDRESS
,或者任何T
不是Expr
的類型的子類型,則為一個靜態錯誤。ELSE S_0
和每個(v)
都是可選的。如果(v_i)
缺席,形式T_1, ..., T_n => S
,是T_1 => S | ... | T_n => S
的簡寫。
TYPECASE
語句求值Expr
,如果結果的引用值,是任何列出的類型T_i
的成員,則針對其中最小的i
,執行S_i
。NIL
是所有引用類型的一個成員,因此NULL
情況只有首先出現才能起作用。如果這個值不是列出的這些類型的一個成員,並且ELSE S_0
存在,則執行它。如果這個值不是列出的這些類型的一個成員,並且ELSE S_0
缺席,則發生一個必查的運行時間錯誤。每個(v_i)
聲明類型為T_i
的一個變量,並且它的作用域是S_i
。如果v_i
存在,它在執行S_i
之前被初始化為Expr
的值。
可以使用TYPECASE
語句測試類型為REFANY
或對象的一個表達式的值的引用類型,比如寫一個過程ToText(r: REFANY): TEXT
,在其中測試r
的值是NULL
、REF BOOLEAN
和REF INTEGER
中的哪一個類型。
循環控制結構
Modula-3提供了循環控制結構FOR
、LOOP
、WHILE
和REPEAT
語句,和必須在字面上包圍在這四個循環語句中,用於從循環中退出的EXIT
語句。語句LOOP S END
,循環直到S
中有一個EXIT
發生。語句WHILE B DO S END
,被定義為:
LOOP
IF B THEN S
ELSE EXIT
END
END
語句REPEAT S UNTIL B
,被定義為:
LOOP
S;
IF B THEN EXIT END
END
FOR
語句有如下形式:FOR id := first TO last BY step DO S END
,這裡的id
是一個標識符,而first
和last
是有相同基礎類型的序數表達式,step
是整數取值的表達式。而S
是一個語句。BY step
是可選的,如果省略,step
缺省為1
。標識符id
指示其作用域是S
的一個只讀變量,其類型是first
和last
共同的基礎類型。FOR
語句的語義,可如下這樣用WHILE
和WITH
語句精確的定義為一個塊:
VAR
i := ORD(first);
done := ORD(last);
delta := step;
BEGIN
IF delta >= 0 THEN
WHILE i <= done DO
WITH id = VAL(i, T) DO S END;
INC(i, delta)
END
ELSE
WHILE i >= done DO
WITH id = VAL(i, T) DO S END;
INC(i, delta)
END
END
END
這裡的T
是id
的類型,i
、done
和delta
代表不出現在FOR
語句中的變量。從FOR
語句的這個定義可以看出,如果一個FOR
循環的上界是LAST(INTEGER)
或LAST(LONGINT)
,就應當將它改寫為WHILE
循環來避免溢出。
With語句
Modula-3的WITH
語句,無關於Modula-2的同名語句,它有如下形式:WITH id = e DO S END
,這裡的id
是一個標識符,e
是一個表達式,而S
是一個語句。WITH
語句聲明了具有作用域S
的id
,作為變量e
的別名,或作為值e
的只讀名字。表達式e
在進入WITH
語句的時候被求值一次。一個單一的WITH
語句可以包含多個綁定,它們順序的求值,就是說WITH id_1 = e_1, id_2 = e_2, ...
,等價於WITH id_1 = e_1 DO WITH id_2 = e_2 DO ....
。
WITH
語句可類比於某種過程調用P(e)
,這裡的P
被聲明為:
PROCEDURE P(mode id: typeof e) =
BEGIN
S
END P
這裡的算符typeof
只是個示意,它不在語言規定之中;如果e
是可寫的指定式,則mode
是VAR
,否則mode
是READONLY
。在WITH
語句和調用P(e)
之間唯一的區別為,出現在WITH
語句內的自由變量、RETURN
和EXIT
,是在WITH
語句所在的上下文中解釋的,而非在新增加的P
的上下文中。
Modula-3語言報告使用WITH
語句定義其他語句,比如,INC
和DEC
語句分別有如下形式:INC(v, n)
和DEC(v, n)
,這裡的v
指示一個序數類型的變量,而n
是可選的一個整數取值表達式,如果省略n
則缺省為1
,這兩個語句分別等價於WITH x = v DO x := VAL(ORD(x) + n, T) END
和WITH x = v DO x := VAL(ORD(x) - n, T) END
,這裡的T
是v
的類型,而x
代表不出現在n
中的變量。
例外
例外聲明有如下形式:EXCEPTION id(T)
,這裡的id
是一個標識符,而T
是除了開放數組之外的一個類型,它聲明了id
是具有實際參數類型T
的例外。如果省略了(T)
,這個例外不接受實際參數。例外聲明只允許出現在接口和模塊的最外層作用域之中。所有的聲明的例外都是各不相同的。
RAISE
語句有兩種形式,沒有實際參數的形式:RAISE e
,這裡的e
是不接受實際參數的一個例外,它的結果是發起例外e
;具有實際參數的形式:RAISE e(x)
,這裡的e
是接受實際參數的一個例外,而x
是可賦值給e
的實際參數類型的一個表達式,它的結果是引發具有配對實際參數x
的例外e
。
Modula-3的例外處理,基於了TRY-EXCEPT
語句。採用類似的塊系統的有Delphi、Python[20]、Scala[21]和Visual Basic.NET。 TRY-EXCEPT
語句有如下形式:
TRY
Body
EXCEPT
id_1 (v_1) => Handler_1
| ...
| id_n (v_n) => Handler_n
ELSE Handler_0
END
這裡的Body
和每個Handler
都是語句,每個id
指名一個例外,而每個v_i
是一個標識符。ELSE Handler_0
和每個(v_i)
都是可選的。每個(v_i)
聲明一個變量,其類型是例外id_i
的實際參數類型,而它的作用域是Handler_i
。如果(v_i)
缺席,形式id_1, ..., id_n => Handler
,是id_1 => Handler; ...; id_n => Handler
的簡寫。
TRY
子句執行Body
。如果結果是正常的,則忽略EXCEPT
等子句。如果Body
引發任何列出的例外id_i
,則執行Handler_i
。在處理具有配對實際參數x
的例外id_i
的時候,v_i
在執行Handler_i
之前被初始化為x
。如果Body
引發任何其他例外,並且ELSE Handler_0
存在,則執行它。在這兩種情況的任何之一下,TRY-EXCEPT
語句的結果是選出的處理器的結果。如果Body
引發未列出的例外,而ELSE Handler_0
缺席,則TRY-EXCEPT
語句的結果是Body
引發的例外。
Modula-3還提供TRY-FINALLY
語句,它有如下形式:TRY S_1 FINALLY S_2 END
,它執行語句S_1
接着執行語句S_2
。如果S_1
的結果是正常的,則TRY
語句的結果等價於S_1; S_2
。如果S_1
的結果是例外,並且S_2
的結果是正常,則在S_2
執行之後,重新發起來自S_1
的例外。如果二者的結果都是例外,則TRY
語句的結果是來自S_2
的例外。
在Modula-3中,EXIT
和RETURN
語句的語義,分別被定義為發起叫做exit-exception
和return-exception
的例外。exit-exception
不接受實際參數,return-exception
接受任意類型的實際參數,程序不能顯式的命名這些例外。語句LOOP S END
,非正式的等價於:
TRY
S; S; S; ...
EXCEPT
exit-exception => (*skip*)
END
過程
過程類型
- 過程體,它是一個語句。
- 簽名,它有如下形式
(formal_1; ...; formal_n): R RAISES S
,這裡的每個formal_i
是形式參數,R
是結果類型,S
是RAISES
集合(這個過程可以引發的例外的集合)。 - 環境,它是在解釋過程體中變量名字方面有關的作用域。
形式參數有如下形式:Mode Name: Type := Default
,這裡的:
Mode
是參數模態,它可以是VALUE
、VAR
或READONLY
。如果省略了Mode
,它缺省為VALUE
。Name
是命名這個形式參數的標識符。形式參數名字必須是各不相同的。Type
是這個形式參數的類型。Default
是一個常量表達式,它是這個形式參數的缺省值。如果Mode
是VAR
,則:= Default
必須省略,否則:= Default
和: Type
中任何一個都可以省略,但不能二者都省略。如果省略了Type
,它採用Default
的類型。如果二者都存在,Default
的值必須是Type
的一個成員。
形式Mode v_1, ..., v_n: Type := Default
,是Mode v_1: Type := Default; ...; Mode v_n: Type := Default
的簡寫。
VAR
形式參數綁定到對應的實際參數所指示的變量上,也就是說它是別名。對於VAR
形式參數,實際參數必須是可寫的指定式,它的類型同於形式參數的類型,或者在VAR
數組形式參數的情況下,它可賦值給這個形式參數。
VALUE
形式參數綁定到具有未用的位置的一個變量上,並且它被初始化為實際參數對應的值。對於VALUE
或READONLY
形式參數,實際參數是可賦值給形式參數類型的任何表達式,並且放開了針對賦值局部過程的禁止。
READONLY
形式參數,如果實際參數是指定式並且有與形式參數相同的類型,或者是可賦值給形式參數的類型的一個數組類型,則被當作VAR
形式參數對待,它的只讀語義體現在指定這個指定式為只讀的,否則被當作VALUE
形式參數對待。
在簽名中,如果省略了: R
,則這個過程是真正(proper)過程,否則為函數過程;如果省略了RAISES S
,則假定它為RAISES {}
。一個過程發起未包括在RAISES
集合中的一個例外,是一個必查的運行時間錯誤,如果語言實現將這個運行時間錯誤映射成一個例外,則這個例外隱含的包括在所有的RAISES
子句中。
過程聲明
有兩種形式的過程聲明:只允許出現在接口中的PROCEDURE id sig
,和只允許出現在模塊中的PROCEDURE id sig = B id
,這裡的id
是一個標識符,sig
是過程簽名,而B
是一個塊。在一個模塊的最外層作用域內聲明的過程是頂層過程,其他過程是局部過程。局部過程可以作為形式參數傳遞但不能被賦值,因為在棧實現下,局部過程在包含它的棧楨被彈出後會成為無效的。
過程類型聲明有如下形式:TYPE T = PROCEDURE sig
,這裡的sig
是簽名規定。一個過程P
是過程類型T
的一個成員,或稱為是它的一個值的條件為,它是NIL
,或者它的簽名被T
的簽名所含蓋(cover)。這裡的簽名sig_A
含蓋簽名sig_B
的條件是:
- 它們有相同數目的形式參數,並且對應的形式參數有相同的類型和模態。
- 它們有相同的結果類型,或者都沒有結果類型。
sig_A
的RAISES
集合包含sig_B
的RAISES
集合。
過程常量是聲明為過程的一個標識符。過程變量是聲明為具有過程類型的一個變量。例如:
PROCEDURE P(txt: TEXT := "P") =
BEGIN
IO.Put(txt)
END P;
VAR q: PROCEDURE(txt: TEXT := "Q") := P;
聲明了過程常量P
和過程變量q
。兩個過程如果有一致的閉包,就是說它們涉及相同的過程體和環境,則二者相等即有相同的值。形式參數名字和缺省值,影響一個過程的類型即簽名,而非這個過程的值。將一個過程常量賦值給一個過程變量,改變這個它的值,而非它的類型。比如上例中有:P = q
為TRUE
。缺省的形式參數的解釋,由一個過程的類型來確定,而非這個過程的值。比如上例子中:P()
打印P
,而q()
打印Q
。
過程調用
過程調用有如下形式:P(Bindings)
,這裡的P
是一個過程取值的表達式,而Bindings
是關鍵字或位置綁定的一個列表。關鍵字綁定有如下形式:name := actual
,這裡的actual
是一個表達式,而name
是一個標識符。位置綁定有如下形式:actual
,這裡的actual
是一個表達式。當關鍵字和位置綁定混合在一個調用中的時候,位置綁定必須前導於關鍵字綁定。如果綁定列表為空,圓括號仍然是需要的。
綁定列表要如下這樣重寫,來適合P
的類型簽名:首先,每個位置綁定actual
,若為Bindings
中的第i
個綁定,則通過補充上第i
個形式參數的名字,轉換成關鍵字綁定增加到關鍵字綁定列表中。其次,對於有缺省值並且在第一步之後沒有被綁定的每個形式參數,將它的形式參數的名字name
和它的缺省值default
,形成關鍵字綁定name := default
增加到關鍵字綁定列表。重寫成的關鍵字綁定列表必須只綁定形式參數,並且必須只綁定每個形式參數正好一次。
執行過程調用,需要求值過程取值表達式P
和它的實際參數,綁定形式參數,並執行過程體。P
和它的實際參數的求值次序是未定義的。調用一個未定義或NIL
過程,是一個必查的運行時間錯誤。調用具有過程體B
的真正過程,在綁定了實際參數之後等價於:
TRY
B
EXCEPT
return-exception => (*skip*)
END
而調用具有過程體B
的函數過程等價於:
TRY
B; (错误:没有返回值)
EXCEPT
return-exception (v) => (结果成为v)
END
遞歸定義
對於常量、類型或過程聲明N = E
、變量聲明N: E
、例外聲明N(E)
或揭示N = E
,如果N
出現在E
的任何部份展開之中,則它是遞歸的。對於省略了類型的變量聲明N := I
,如果N
出現在I
的類型E
的任何部份展開中,則它是遞歸的。允許這種聲明的條件,是N
在E
的任何部份展開中所有的出現為如下三者之一:在類型構造子REF
或PROCEDURE
的某個出現之內,在類型構造子OBJECT
的一個字段或方法類型之內,或者在一個過程體之內。下面是合法的遞歸聲明的例子:
TYPE
RealList = REF RECORD
val: REAL;
link: RealList
END;
ListNode = OBJECT
link: ListNode
END;
IntList = ListNode OBJECT
val: INTEGER
END;
T = PROCEDURE(n: INTEGER; p: T);
EXCEPTION E(PROCEDURE() RAISES {E});
PROCEDURE P(b: BOOLEAN) =
BEGIN
IF b THEN P(NOT b) END
END P;
特徵性技術
Modula-3的設計者認為Modula-2最成功的特徵,是提供了在模塊之間的顯式接口,故而接口在Modula-3中保留而在本質上沒有變化。到模塊的接口,是披露了模塊公開部份的聲明搜集,而未聲明於接口中的模塊中的東西是私有的。模塊導入它所依賴的接口,而導出它所實現的接口(在Modula-3中可以是多個接口)。
在現代編程語言的實現下,一個抽象類型的值,被表示一個對象,它的操作由叫做對象的方法的過程值的一個套件來實現。一個新的對象類型,可以定義為現存類型的子類型,在這種情況下新類型擁有舊類型的所有方法,並且可能還有新的方法(繼承)。新類型可以為舊方法提供新的實現(覆蓋)。對象類型和Modula-2的不透明類型的組合,產生了一個新事物:部份不透明(partially opaque)類型,這裡的一個對象的一些字段在一個作用域內是可見的,而其他的是隱藏的。
泛型模塊是一種模板,在其中一些導入的接口被當作形式參數,它們在實例化泛型的時候要綁定到實際接口。不同的泛型實例是獨立編譯的,源代碼程序被重複使用,而編譯後的代碼對不同實例通常是不同的。為保持簡單性,在Modula-3中泛型被限制在模塊層面,泛型的過程和類型不孤立存在,並且泛型形式參數必須都是完全的接口。
將計算分解入並發進程(或線程)中是關注點分離的基本方法。Modula-2提供的線程很弱,本質上相當於協程。Modula-3採用了霍爾的監視器,它是並發編程的更堅實基礎。Modula-2+在設計中加入了簡化的Mesa同步原語,它足夠成功而在實質上沒有改變的結合入了Modula-3。
一個語言特徵是不安全的,如果它的濫用可以破壞運行時系統,使得程序的進一步運行不忠實於語言的語義,不安全特徵的一個例子,是沒有邊界檢查的數組賦值。一個安全的程序的一個錯誤,可以導致計算中止,並給出運行時間錯誤信息或給出錯誤解答,但它不會導致計算分崩離析。Modula-3在這個領域裡跟從Cedar,接受只在顯式的標記為不安全的模塊中才允許的少量不安全特徵。
模塊化
接口中的聲明,除了任何變量初始化必須是常量,和過程聲明必須只能指定簽名而不能有過程體之外,同於在塊中的聲明。接口有如下形式:
INTERFACE id;
Imports;
Decls
END id.
這裡的id
是命名這個接口的標識符,Imports
是一序列的導入語句,而Decls
是一序列的聲明。在Decls
中的名字和可見的導入的名字必須是各不相同的。兩個或更多個接口形成導入環是靜態錯誤。典型的接口定義,通常為每個接口定義一個數據結構(記錄或對象)以及任何的支持過程。
模塊除了實體名字的可見性之外,就像是一個塊。一個實體在一個塊中是可見的,如果它被聲明在這個塊中,或在某個包圍的塊中;而一個實體在一個模塊中是可見的,如果它被聲明在這個模塊中,或在這個模塊所導入或導出的一個接口中。模塊有如下形式:
MODULE id EXPORTS Interfaces;
Imports;
Block id.
這裡的id
是命名這個模塊的一個標識符,Interfaces
是這個模塊導出的那些接口的不同名字的一個列表,Imports
是一序列的導入語句,而Block
是一個塊,它是模塊體。名字id
必須重複於終結模塊體的END
之後。EXPORTS Interfaces
可以省略,在這種情況下Interfaces
缺省為id
,即模塊將缺省導出相同名稱的接口。Modula-3中的導出EXPORTS
,與Modula-2中的導出EXPORT
無關,它在概念上對應於Modula-2的「實現」。
如果模塊M
導出了接口I
,則在I
中聲明的所有名字在M
中是不加以限定而可見的。一個模塊M
導出一個接口I
,可以重新聲明在這個接口中聲明的一個或多個過程,為其提供過程體。在M
中的簽名,必須被在I
中的簽名所含蓋。要確定在對這個過程的調用中的關鍵字綁定的解釋,在M
內使用M
中的簽名,在其他地方使用I
中的簽名。除了重新聲明導出的過程之外,在Block
的頂層中聲明的名字,可見的導入的名字,和在導出的接口中聲明的名字必須是各不相同的。
一個程序的模塊和接口的名字叫做全局名字。查找全局名字的方法是依賴於實現的,比如通過文件系統查找路徑。一個模塊或接口X
導入一個接口I
,從而使得在I
中聲明的實體和揭示在X
中可見,但不包括I
所導入的那些。一個模塊M
使用了接口I
,如果M
導入或導出了I
,或者M
使用了導入I
的一個接口。Modula-3中所有的編譯單元,要麼是接口要麼是模塊。任何編譯單元都可以使用IMPORT
語句從其他接口導入標識符。與其他語言的包含功能不同,任何標識符都可以追蹤到它的起源位置。
IMPORT
語句有如下的典型使用形式:IMPORT I
,它導入全局名字為I
的接口,並給它一個同樣的局部名字,從而使用點表示法訪問接口中的實體(類似於訪問記錄中的字段)。比如接口中的主要類型按慣例命名為T
,則使用它時採用的限定形式為I.T
。如果導入接口的名字與模塊內的其他實體的名字發生衝突,則可以使用IMPORT I AS X
這樣的形式給與它不同的局部名字。
IMPORT
語句還有如下的另一種形式:FROM I IMPORT N
,它為在接口I
中聲明為N
的實體,介入一個局部名字N
,比如:FROM IO IMPORT Put
,並且不加限定的使用它,比如:Put("Hello World\n")
。局部綁定優先於全局綁定,例如:IMPORT I AS J, J AS I; FROM I IMPORT N
,為全局名字為J.N
的實體介入了局部名字N
。非法使用相同的局部名字兩次是靜態錯誤。
一個程序是模塊和接口的搜集,包含了任何它的模塊或接口所導入或導出的所有接口,並且在其中沒有重複定義的過程、模塊或接口。執行一個程序的效果是執行它的所有模塊的模塊體。模塊的執行次序受到初始化規則的約束:如果模塊M
依賴於模塊N
並且N
不依賴於M
,則N
的模塊體將先於M
的模塊體執行。這裡的模塊M
依賴於模塊N
,如果M使用了N
導出的一個接口,或者M
所依賴的一個模塊依賴於N
。
其模塊體最後執行的模塊叫做主模塊。在初始化規則不能唯一的確定它的情況下,語言實現提供了指定這個主模塊的方法,即主模塊是導出Main
的模塊,它的內容是依賴於實現的。程序執行在主模塊終止的時候終止,即使並發的控制線程仍在執行。
下面是接口與模塊的一個規範性的例子,是有隱藏表示的一個公開堆棧:
INTERFACE Stack;
TYPE T <: REFANY;
PROCEDURE Create(): T;
PROCEDURE Push(VAR s: T; x: REAL);
PROCEDURE Pop(VAR s: T): REAL;
END Stack.
MODULE Stack;
REVEAL T = BRANDED OBJECT
item: REAL;
link: T
END;
PROCEDURE Create(): T =
BEGIN
RETURN NIL
END Create;
PROCEDURE Push(VAR s: T; x: REAL) =
BEGIN
s := NEW(T, item := x, link := s)
END Push;
PROCEDURE Pop(VAR s: T): REAL =
VAR res: REAL;
BEGIN
res := s.item;
s := s.link;
RETURN res
END Pop;
BEGIN
END Stack.
如果多於一個模塊需要此堆棧的表示,則應當把它轉移到私有接口中,從而在需要它的任何地方的都可以導入它:
INTERFACE Stack;
(* ... 同上 ... *)
END Stack.
INTERFACE StackRep;
IMPORT Stack;
REVEAL Stack.T = BRANDED OBJECT
item: REAL;
link: Stack.T
END
END StackRep.
MODULE Stack;
IMPORT StackRep;
(* Push、Pop和Create同上 *)
BEGIN
END Stack.
對象類型
一個對象要麼是NIL
,要麼是到有配對的方法套件的一個數據記錄的引用,方法套件是接受這個對象作為第一個實際參數的那些過程的一個記錄。這個數據記錄可以包含經由對象類型的子類型介入的增加字段,這個套件可以包含經由子類型介入的增加方法。對象賦值是引用賦值。要複製一個對象的數據記錄到另一個對象,這些字段必須單獨的賦值。
Modula-3意圖保持採用對象這個最簡單的術語,而非在其他面向對象語言中對應的術語類。對象類型聲明採用如下形式:
TYPE T = ST OBJECT
Fields
METHODS
Methods
OVERRIDES
Overrides
END
這裡的ST
是可選的超類型,如果省略則缺省為ROOT
,Fields
是完全如同記錄類型那樣的字段聲明的一個列表,Methods
是方法聲明的一個列表,而Overrides
是方法覆蓋的一個列表。在字段和方法聲明中介入的名字,必須相互不同,並且不同於在覆蓋聲明中的名字。T
的字段構成自ST
的字段和隨後的新聲明的字段。T
的方法構成自經過OVERRIDES
中的覆蓋修改的ST
的方法,和隨後的METHODS
中聲明的方法。T
有着同ST
一樣的引用類。
關鍵字OBJECT
可選的可以前導上BRANDED
或BRANDED b
來給對象加銘牌,使之唯一而避免結構等價,這裡的b
是文本常量。其含義同於非對象的引用類型,b
被省略時,系統自動生成唯一性的一個字符串。
方法聲明有如下形式:m sig := proc
,這裡的m
是個標識符,sig
是過程簽名,而proc
是頂層過程常量。方法聲明指定了T
的方法m
有簽名sig
和值proc
。如果省略了:= proc
假定它為:= NIL
。如果proc
非空,它的第一個形式參數必須是缺省的模態VALUE
,並有着T
的某個超類型的類型(包括且經常是T
自身),並且去除了第一個形式參數結果就是sig
所含蓋的簽名。
方法覆蓋(override)有如下形式:m := proc
,這裡的m
是超類型ST
的方法的名字,而proc
是頂層過程常量。方法覆蓋指定了T
的方法m
是proc
,並非指定ST.m
。如果proc
非空,它的第一個形式參數必須是缺省的模態VALUE
,並有着T
的某個超類型的類型(包括且經常是T
自身),去除了proc
第一個形式參數結果就是ST
的m
方法所含蓋的簽名。舉例說明:
TYPE
Super = OBJECT
a: INTEGER;
METHODS
p()
END;
Sub = Super OBJECT
b: INTEGER
END;
PROCEDURE ProcSuper(self: Super) = ... ;
PROCEDURE ProcSub(self: Sub) = ... ;
這裡的過程ProcSuper
和ProcSub
是類型Super
和Sub
的對象的p
方法的候選值。例如:
TYPE T1 = Sub OBJECT
OVERRIDES
p := ProcSub
END
|
TYPE T2 = Super OBJECT
OVERRIDES
p := ProcSuper
END
|
聲明了具有Sub 數據記錄和預期一個Sub 的p 方法的一個類型。T1 是Sub 的有效的子類型。
|
聲明了具有Super 數據記錄和預期一個Super 的p 方法的一個類型。T2 是Super 的有效的子類型。
|
TYPE T3 = Sub OBJECT
OVERRIDES
p := ProcSuper
END
|
TYPE T4 = Super OBJECT
OVERRIDES
p := ProcSub
END
|
聲明了具有Sub 數據記錄和預期一個Super 的p 方法的一個類型。因為所有Sub 都是Super ,這個方法不挑剔要放入的對象。
|
嘗試聲明具有Super 數據記錄和預期一個Sub 的p 方法的一個類型,因為不是所有Super 都是Sub ,這個方法很挑剔要放入的對象。T4 的聲明是個靜態錯誤。
|
如果T
是對象類型或到記錄的引用,NEW(T)
操作有如下形式:NEW(T, Bindings)
,這裡的Bindings
是用來初始化新字段的關鍵字綁定的一個列表,不允許位置綁定。每個綁定f := v
將字段f
初始化為值v
。如果T
是對象類型,則Bindings
還可以包括形如m := P
的方法覆蓋,這裡的m
是T
的一個方法而P
是一個頂層過程常量。NEW(T, m := P)
是NEW(T OBJECT OVERRIDES m := P END)
的語法糖。
如果o
是一個對象,則o.f
指示在o
的數據記錄中名為f
的數據字段。o.f
是可寫的指定式,它的類型是這個字段的所聲明的類型。如果m
是o
方法之一,以o.m(Bindings)
形式的過程調用,指示執行o
的m
方法,這等價於:(o的m方法) (o, Bindings)
。
如果T
是對象類型而m
是T
的方法的名字之一,則T.m
指示T
的m
方法。它的過程類型的第一個實際參數有類型T
,而第一個實際參數對應的形式參數名字是未指定的,因此在調用T.m
時,第一個實際參數必須按位置來指定,不能用關鍵字。這種表示法便於子類型方法調用它的某個超類型的對應方法。
在子類型中的字段或方法遮蓋(mask)了在超類型中的任何同名的字段或方法。要訪問被遮蓋的字段,使用類型運算NARROW(x, T)
來將子類型變量當作超類型的成員,這裡的T
必須是對象類型或有跟蹤的引用類型,而x
必須可賦值給T
。NARROW(x, T): T
在檢查了x
是T
一個成員之後返回x
,如果檢查失敗則發生運行時間錯誤。
下面的例子展示了在聲明新方法和覆蓋現存方法之間的不同,首先聲明如下:
TYPE
Super = OBJECT
METHODS
m() := ProcSuper
END;
SubOverridden = Super OBJECT
OVERRIDES
m := ProcSub
END;
SubExtended = Super OBJECT
METHODS
m() := ProcSub
END;
VAR
a := NEW(Super);
b := NEW(SubOverridden);
c := NEW(SubExtended);
然後可以用a.m()
激活ProcSuper(a)
,用b.m()
激活ProcSub(b)
,用c.m()
激活ProcSub(c)
,如此調用在覆蓋和擴展之間沒有區別。但是擴展的c
的方法套件有兩個方法,而覆蓋的b
的方法套件只有一個方法,這可以通過使用NARROW(x, T)
將子類型的變量b
和c
視為超類型Super
的成員來披露:NARROW(b, Super).m()
激活ProcSub(b)
,NARROW(c, Super).m()
激活ProcSuper(c)
。
對象不能解引用,因為在語言實現中,一個對象變量的靜態類型不確定它的數據記錄的類型。對象類型確定了關乎數據記錄字段和方法套件的超類型鏈的前綴的那些類型。
實現
下面是語言設計者提出的對象的一種可能實現的梗概[19],一個對象可以表示為它的數據記錄的第一個字的地址。前面的字存儲一個對象頭部,它包含一個唯一於對象類型的類型代碼。這些類型代碼是小型整數,對每個對象類型和每個有跟蹤的引用類型都有一個代碼。在對象頭部之前的字,存儲到這個對象的方法套件的一個引用。如果這個對象沒有方法,這個字可以省略。這還允許對象共享方法套件,這是常見情況。如果o
是一個對象,d
是它的數據字段之一,而m
是它的方法之一,則在這種表示下:
o.d |
是 | Mem[o + d]
|
o.m |
是 | Mem[Mem[o – 2] + m]
|
TYPECODE(o) |
是 | Mem[o – 1]
|
這裡假定了字段和方法以自然方式表示為偏移量。進一步的問題是如何高效的測試一個對象o
是否有着類型T
,這是NARROW()
和TYPECASE
語句所需要的。NARROW()
的最簡單實現,是維護一個以類型代碼為索引的數組st
,st[tc]
是其類型代碼為tc
的對象類型的超類型的類型代碼,如果它沒有超類型則為NIL
。要測試o
是否是一個T
,使用一個循環來計算T
的類型代碼是否出現在如下序列之中:
TYPECODE(o), st[TYPECODE(o)], st[st[TYPECODE(o)]], ... NIL
這個序列可以稱為o
的類型的超類型路徑,而它的長度可稱為o
的類型的深度。利用上每個類型的深度都是編譯時間確定的,因此可以同相應的類型代碼一起存儲的事實,可以有更快速的NARROW()
實現。如果T
的類型代碼,出現在類型U
的超類型路徑上,它就該在位置depth(U) - depth(T)
上。如果每個類型的超類型路徑都是順序數組,這意味着NARROW()
可以用恆定時間實現。因為超類型路徑通常不太長,這是一個有吸引力的策略。在不常見的對象類型有非常長的超類型鏈的情況下,只有這個鏈的直到某個極大長度的一個前綴,會被順序的存儲。在運行時間,這個深度差如果超出這個鏈的順序存儲的長度,實現必須回退到鍊表。
不透明類型及其披露
不透明(opaque)類型聲明有如下形式:TYPE T <: U
,這裡的T
是標識符,而U
是指示一個引用類型的表達式。它將名字T
介入為不透明類型,並披露了U
是T
的超類型。
Modula-3的揭示(revelation)機制,將關於一個不透明類型的信息,介入到一個作用域之內,不同於其他聲明,揭示不介入新的名字。不同的作用域,可以披露(reveal)關於一個不透明類型的不同的信息。例如,某個不透明類型,在一個作用域內知曉為REFANY
的子類型,在其他的作用域內可以知曉為ROOT
的子類型。
不透明類型名字所指示的實際類型叫做具體類型。T
的具體類型,必須披露於程序的其他地方。在對象聲明中,如果ST
被聲明為不透明類型,則只在知曉ST
的具體類型為對象類型的作用域內,T
的聲明是合法的。如果T
被聲明為不透明類型,則只在完全知曉T
的具體類型,或者知曉它為對象類型的作用域內,NEW(T)
是合法的。
揭示分為兩種:部份的和完全的,一個程序可以包含一個不透明類型的任意多個部份揭示,但必須包含唯一的一個完全揭示。
部份揭示有如下形式:REVEAL T <: V
,這裡的V
是類型表達式(可能就是個名字),而T
是被聲明為不透明類型的一個標識符(可能有限定)。它披露了V
是T
的超類型。在任何作用域內,對一個不透明類型披露的超類型,必須是在子類型關係下是線性有序的。就是說,如果披露了T <: U1
和T <: U2
,則必須也要披露要麼U1 <: U2
要麼U2 <: U1
。REVEAL T <: T
是合法的非遞歸聲明。
完全揭示有如下形式:REVEAL T = V
,這裡的V
是類型表達式(不能就是個名字),它的最外層類型構造子,是有銘牌的一個引用或對象類型,而T
是被聲明為不透明類型的一個標識符(可能有限定)。這個揭示指定了V
是T
的具體類型。在任何作用域內,披露為T
的超類型的任何類型,都必須是V
的超類型,否則是一個靜態錯誤。不同的不透明類型有不同的具體類型,因為V
包含了一個銘牌並且在程序中所有的銘牌都是獨一的。REVEAL I.T = I.T BRANDED OBJECT ... END
是非法的遞歸聲明。
揭示只允許用在接口和模塊的最外層作用域內。在接口中的揭示可以被導入到任何需要它的作用域之內。揭示提供了向客戶隱藏實現細節的,在概念上簡單清晰卻非常強力的機制,例如:
INTERFACE I;
TYPE T <: ROOT;
PROCEDURE P(x: T): T;
END I.
INTERFACE IRep;
IMPORT I;
REVEAL I.T = MUTEX BRANDED OBJECT
count: INTEGER
END;
END IRep.
INTERFACE IClass;
IMPORT I;
REVEAL I.T <: MUTEX;
END IClass.
導入I
的編譯單元見到的I.T
是ROOT
的不透明子類型,因而被限制為可以分配類型I.T
的對象,把它們傳遞給I.P
,或聲明I.T
的子類型。導入IRep
的編譯單元見到的I.T
是具體類型,它是有擴展的count
字段的MUTEX
的子類型。導入IClass
的編譯單元見到的I.T
是MUTEX
的不透明子類型,因而可以鎖定類型I.T
的對象。
泛型
在泛型接口或模塊中,某些導入的接口名字被當作形式參數,它們在泛型被實例化的時候要綁定到實際接口上。泛型接口及其實例有如下左側的形式,並等價於右側定義的普通接口:
GENERIC INTERFACE G(F_1, ..., F_n);
Body
END G.
INTERFACE I = G(A_1, ..., A_n) END I.
|
INTERFACE I;
IMPORT A_1 AS F_1, ..., A_n AS F_n;
Body
END I.
|
這裡的G
是命名這個泛型接口的一個標識符,F_1, ..., F_n
是叫做G
的形式導入的標識符的一個列表,而Body
同在非泛型接口中一樣,是一序列導入和隨後的一序列聲明。這裡的I
是這個實例的名字,而A_1, ..., A_n
是G
的形式導入要綁定的實際接口的一個列表。
泛型模塊及其實例有如下左側的形式,並等價於右側定義的普通模塊:
GENERIC MODULE G(F_1, ..., F_n);
Body
END G.
MODULE I EXPORTS E = G(A_1, ..., A_n) END I.
|
MODULE I EXPORTS E;
IMPORT A_1 AS F_1, ..., A_n AS F_n;
Body
END I.
|
這裡的G
是命名這個泛型模塊的一個標識符,F_1, ..., F_n
是叫做G
的形式導入的標識符的一個列表,而Body
同在非泛型模塊中一樣,是一序列的導入和隨後的一個塊。這裡的I
是這個實例的名字,E
是由M
導出的接口的一個列表,而A_1, ..., A_n
是G
的形式導入要綁定的實際接口的一個列表。EXPORTS E
可以省略,在這種情況下它缺省為EXPORTS I
。泛型模塊自身沒有導出,它們只在被實例化時提供。
泛型接口及其對應的泛型模塊(與C++模板一樣)可以輕鬆定義和使用抽象數據類型,但粒度在模塊級別,裸露的類型INTEGER
或REAL
不能使用,相比之下,它們在C++模板中可以使用。例如,可以定義泛型堆棧:
GENERIC INTERFACE Stack(Elem);
(* 这里的Elem.T不是开放数组类型 *)
TYPE T <: REFANY;
PROCEDURE Create(): T;
PROCEDURE Push(VAR s: T; x: Elem.T);
PROCEDURE Pop(VAR s: T): Elem.T;
END Stack.
GENERIC MODULE Stack(Elem);
REVEAL T = BRANDED OBJECT
n: INTEGER;
a: REF ARRAY OF Elem.T
END;
PROCEDURE Create(): T =
BEGIN
RETURN NEW(T, n := 0, a := NIL)
END Create;
PROCEDURE Push(VAR s: T; x: Elem.T) =
BEGIN
IF s.a = NIL THEN
s.a := NEW(REF ARRAY OF Elem.T, 5)
ELSIF s.n > LAST(s.a^) THEN
WITH temp = NEW(REF ARRAY OF Elem.T, 2 * NUMBER(s.a^)) DO
FOR i := 0 TO LAST(s.a^) DO
temp[i] := s.a[i]
END;
s.a := temp
END
END;
s.a[s.n] := x;
INC(s.n)
END Push;
PROCEDURE Pop(VAR s: T): Elem.T =
BEGIN
DEC(s.n);
RETURN s.a[s.n]
END Pop;
BEGIN
END Stack.
然後使用接口例如IntegerElem
,對其進行實例化,只要這個接口定義了泛型模塊所需的屬性即可:
INTERFACE IntegerElem;
TYPE T = INTEGER;
END IntegerElem.
INTERFACE IntStack = Stack(IntegerElem) END IntStack.
MODULE IntStack = Stack(IntegerElem) END IntStack.
面向對象
Modula-3下的面向對象編程,經常採用部份不透明類型,它通過如下慣用法來聲明:Type T <: Public; Public = OBJECT ... END
,這裡的不透明類型T
,支持在隨後定義的對象類型中的字段和方法,而不會披露T
的確切結構,也不披露T
可能支持的其他方法。
下面的例子,定義一個接口Person
,具有按習慣約定命名的兩個類型,導出類型T
,和公開對象類型Public
,這裡聲明了T
是Public
的不透明子類型,Public
具有兩個方法getAge()
和init()
;隨後是Person
接口的完全實現:
INTERFACE Person;
TYPE T <: Public;
Public = OBJECT
METHODS
getAge(): INTEGER;
init(name: TEXT; age: INTEGER): T;
END;
END Person.
MODULE Person;
REVEAL T = Public BRANDED OBJECT
name: TEXT;
age: INTEGER;
OVERRIDES
getAge := Age;
init := Init;
END;
PROCEDURE Age(self: T): INTEGER =
BEGIN
RETURN self.age;
END Age;
PROCEDURE Init(self: T; name: TEXT; age: INTEGER): T =
BEGIN
self.name := name;
self.age := age;
RETURN self;
END Init;
BEGIN
END Person.
要建立一個新的Person.T
對象,要使用內建操作NEW()
,並調用方法init()
:
VAR jim := NEW(Person.T).init("Jim", 25);
下面是整數堆棧的例子:
INTERFACE IntegerStack;
TYPE
T <: Public OBJECT;
Public = OBJECT
METHODS
init(): T;
push(x: INTEGER);
pop(): INTEGER;
END;
END IntegerStack.
MODULE IntegerStack;
REVEAL T = Public BRANDED OBJECT
n: INTEGER;
a: REF ARRAY OF INTEGER;
OVERRIDES
init := Init;
push := Push;
pop := Pop;
END;
PROCEDURE Init(self: T): T =
BEGIN
self.n := 0;
self.a := NIL;
RETURN self
END Init;
PROCEDURE Push(self: T; x: INTEGER) =
BEGIN
IF self.a = NIL THEN
self.a := NEW(REF ARRAY OF INTEGER, 5)
ELSIF self.n > LAST(self.a^) THEN
WITH temp = NEW(REF ARRAY OF INTEGER, 2 * NUMBER(self.a^)) DO
FOR i := 0 TO LAST(self.a^) DO
temp[i] := self.a[i]
END;
self.a := temp
END
END;
self.a[self.n] := x;
INC(self.n)
END Push;
PROCEDURE Pop(self: T): INTEGER =
BEGIN
DEC(self.n);
RETURN self.a[self.n]
END Pop;
BEGIN
END IntegerStack.
創建新堆棧在這裡的部份不透明類型實現下為:VAR myStack := NEW(IntegerStack.T).init()
,在上述的不透明類型實現下為:VAR myStack := IntStack.Create()
。而壓入一個數在這裡的部份不透明類型實現下為:myStack.push(8)
,在上述的不透明類型實現下為:IntStack.Push(myStack, 8)
。
多線程
語言支持多線程和在線程間的同步。在運行時庫(m3core)中有一個必要接口叫做Thread
,它支持採用分叉會合模型的多線程應用。這裡還預定義不透明類型MUTEX
被用來同步多個線程,並保護共享數據免於具有可能損害或競爭條件的同時訪問。MUTEX
是一個對象,因此可以從它派生其他對象。
LOCK
語句有如下形式:LOCK mu DO S END
,這裡的S
是一個語句,而mu
是一個表達式,它等價於:
WITH m = mu DO
Thread.Acquire(m);
TRY S FINALLY Thread.Release(m) END
END
這裡的m
表示不出現在S
中的一個變量。LOCK
語句介入一個互斥鎖要鎖定的塊,並暗含着在代碼執行軌跡離開這個塊時的解鎖它。
不安全標記
某些功能被認為是不安全的,編譯器無法再保證結果是一致性的(例如,當與C編程語言交接時)。在INTERFACE
或MODULE
前面加上前綴關鍵字UNSAFE
,可用於告訴編譯器啟用語言的某些不安全的低級功能。例如,在UNSAFE
模塊中,使用LOOPHOLE()
將整數的諸位元複製成浮點REAL
數,使用DISPOSE()
釋放無跟蹤的內存。
一個接口是內在安全的,如果在安全模塊中使用這個接口,無法產生不核查的運行時間錯誤。如果導出一個安全接口的所有的模塊都是安全的,編譯器保證這個接口的內在安全性。如果導出一個安全接口的任何模塊是不安全的,就要編程者而非編譯器,去做這種保證了。安全接口導入不安全接口,或安全模塊導入或導出不安全接口,都是靜態錯誤。導入不安全接口的編譯單元本身必定也是不安全的。
對其他編程語言的影響
儘管Modula-3沒有獲得主流地位,DEC-SRC M3發行的某些部份做到了。可能最有影響的一部份是網絡對象庫,它形成了Java包括了網絡協議的最初的遠程方法調用(RMI)實現的基礎。在Sun從通用對象請求代理架構(CORBA)標準轉移到基於IIOP的協議的時候才被放棄。Java關於遠程對象的垃圾收集的文檔仍提及了Modula-3網絡對象為其先驅性工作[22] 。
Python從Modula-3借鑑了模塊系統、例外系統和關鍵字參數,Python的類機制受到C++和Modula-3的啟發[23]。Nim利用了Modula-3的某些方面比如有跟蹤和無跟蹤的指針。
引用
- ^ Modula-3 Reference Manual (頁面存檔備份,存於網際網路檔案館) Luca Cardelli, James Donahue, Lucille Glassman, Mick Jordan, Bill Kalsow, Greg Nelson. DEC Systems Research Center (SRC) (February 1995)
- ^ Critical Mass Modula-3 (CM3). Critical Mass Modula-3. elego Software Solutions GmbH. [2020-03-21]. (原始內容存檔於2020-09-24).
- ^ Polytechnique Montréal Modula-3 (PM3): What is it. Polytechnique Montréal Modula-3. elego Software Solutions GmbH. [2020-03-21]. (原始內容存檔於2020-11-19).
- ^ Polstra, John D. Ezm3: An Easier Modula-3 Distribution. CVSup.org. November 9, 2006 [2020-03-21]. (原始內容存檔於2007-04-29).
- ^ Weich, Carsten. M3/PC Klagenfurt 96: a Modula-3 environment for MS-DOS. Department of Informatics. University of Klagenfurt. [2020-03-21]. (原始內容存檔於2000-05-20).
- ^ Picheta, Dominik; Locurcio, Hugo. Frequently Asked Questions. [2020-03-21]. (原始內容存檔於2016-12-10).
- ^ van Rossum, Guido. Programming Python: Foreword (1st ed.). Python.org. May 1996 [2020-03-21]. (原始內容存檔於2014-07-24).
besides ABC, my main influence was Modula-3. This is another language with remarkable elegance and power, designed by a small, strong-willed team (most of whom I had met during a summer internship at DEC's Systems Research Center in Palo Alto).
- ^ Baby Modula-3 and a theory of objects (頁面存檔備份,存於網際網路檔案館) Martin Abadi. Digital Equipment Corporation (DEC) Systems Research Center (SRC) Research Report 95 (February 1993).
- ^ Design and History FAQ - Why must ‘self’ be used explicitly in method definitions and calls?. Python.org. March 21, 2020 [2020-03-21]. (原始內容存檔於2012-10-24).
The idea was borrowed from Modula-3. It turns out to be very useful, for a variety of reasons.
- ^ Modula-3: Language definition.. [2021-06-25]. (原始內容存檔於2021-06-25).
- ^ Rovner, Paul; Levin, Roy; Wick, John. SRC-RR-3 On extending Modula-2 for building large, integrated systems. Hewlett-Packard Labs (報告). January 11, 1985 [2021-08-11]. (原始內容存檔於2021-05-16).
- ^ Thacker, Charles P.; Stewart, Lawrence C.; Satterthwaite, Edwin H. Jr. Firefly: a multiprocessor workstation SRC-RR-23. Hewlett-Packard Labs (報告). December 30, 1987 [2021-08-11]. (原始內容存檔於2021-05-16).
- ^ McJones, Paul R.; Swart, Garret F. Evolving the Unix system interface to support multithreaded programs SRC-RR-21. Hewlett-Packard Labs (報告). September 28, 1987 [2021-08-11]. (原始內容存檔於2021-05-16).
- ^ 14.0 14.1 Modula-3 report (revised) (頁面存檔備份,存於網際網路檔案館) Luca Cardelli, James Donahue, Lucille Glassman, Mick Jordan, Bill Kalsow, Greg Nelson. DEC Systems Research Center (SRC) Research Report 52 (November 1989)
- ^ Peter Robinson. From ML to C via Modula-3 - an approach to teaching programming (PDF). 1994 [2021-08-11]. (原始內容 (PDF)存檔於2021-12-17).
The Computer Science course at the University of Cambridge teaches ML as an introductory language at the beginning of the freshman year, and then uses Modula-3 to introduce imperative programming at the end of that year. Further lectures on advanced features of Modula-3 are given early in the second year, together with separate lectures on C. Other, specialised languages are introduced subsequently as the course progresses.
- ^ SPIN OVERVIEW. [2021-08-11]. (原始內容存檔於2022-02-24).
SPIN and its extensions are written in Modula-3, a type-safe programming language developed at DEC SRC. Modula-3 offers modern language features such as objects, garbage collection, and threads. We rely on its type-safe properties to protect sensitive kernel data and interfaces from malicious or errant extensions.
- ^ Systems Programming with Modula-3.
- ^ Modula-2 Reference - Statements. [2023-02-21]. (原始內容存檔於2023-02-21).
- ^ 19.0 19.1 Luca Cardelli, Jim Donahue, Mick Jordan, Bill Kalsow, Greg Nelson. The Modula-3 Type System (PDF). 1989 [2021-08-12]. (原始內容 (PDF)存檔於2021-03-24).
- ^ Why was Python created in the first place?. [2021-06-22]. (原始內容存檔於2008-02-23).
I had some experience with using Modula-2+ and talked with the designers of Modula-3 and read the Modula-3 report. Modula-3 is the origin of the syntax and semantics used for exceptions, and some other Python features.
- ^ Scala Center at EPFL.. [2022-05-15]. (原始內容存檔於2020-09-23).
- ^ Garbage Collection of Remote Objects (頁面存檔備份,存於網際網路檔案館), Java Remote Method Invocation Documentation for Java SE 8.
- ^ The Python Tutorial - Classes. [2018-07-01]. (原始內容存檔於2020-12-03).
It is a mixture of the class mechanisms found in C++ and Modula-3. ……As in Modula-3, there are no shorthands for referencing the object’s members from its methods: the method function is declared with an explicit first argument representing the object, which is provided implicitly by the call. ……I would use Modula-3 terms, since its object-oriented semantics are closer to those of Python than C++, but I expect that few readers have heard of it.
外部連結
- 官方網站
- GitHub上的Modula3
- CM3 Implementation Website (頁面存檔備份,存於網際網路檔案館)
- Modula-3 Home Page (mirror)
- Modula-3: Language definition (頁面存檔備份,存於網際網路檔案館)
- Building Distributed OO Applications: Modula-3 Objects at Work. Michel R. Dagenais. Draft Version (January 1997)
- Object-Oriented Data Abstraction in Modula-3. Joseph Bergin (1997) (頁面存檔備份,存於網際網路檔案館)