Modula-3

Modula-3是一種系統程式語言，它是叫做Modula-2+的升級版本的Modula-2的後繼者。雖然它已經在研究界有了影響力，受其影響的語言有Java、C#和Python，但未能在工業上被廣泛採用。它是在1980年代末由在數字設備公司（DEC）DEC系統研究中心（SRC）和Olivetti研究中心（ORC）的Luca Cardelli、James Donahue、Lucille Glassman、Mick Jordan（之前在Olivetti軟體技術實驗室工作）、Bill Kalsow和Greg Nelson設計。

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月更新了版本。DEC和Olivetti的編譯器很快就出現了，隨後又有了第三方實現。

它的設計受到DEC系統研究中心和Acorn電腦研究中心（ARC，在Olivetti收購Acorn之後叫做ORC）在Modula-2+語言上的工作的很大影響，Modula-2+是編寫DEC Firefly多處理器VAX工作站的作業系統的語言，還是在基於ARM的Acorn Archimedes系列電腦的ARX作業系統項目中，ARC編寫用於Acorn C和模塊執行庫（CAMEL）的Acorn編譯器的語言。正如修訂的Modula-3報告所述，該語言演化自Mesa、Modula-2、Cedar和Modula-2+，還受到其他語言的影響如：Object Pascal，Oberon和Euclid (程式語言)。

在20世紀90年代，Modula-3作為一種教學語言獲得了可觀的傳播，華盛頓大學在1994年發行的微內核作業系統SPIN (作業系統)是用Modula-3開發的，但它從未廣泛用於工業用途。造成這種情況的原因，可能是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系統編程》中，收錄了設計者對四個設計要點的討論：結構等價與名稱等價、子類型規則、泛型模塊和參數模態。Modula-3現在主要是在大學中的比較程式語言課程中教授，其教科書已絕版。

## 程序示例

Modula-3中所有的程序至少具有一個模塊文件，而大多數程序還包括一個接口文件，客戶端使用它來訪問來自模塊的數據。語言語法的一個常見示例是Hello world程序：

```modula2
MODULE Main; 
    IMPORT IO;
BEGIN
    IO.Put("Hello World\n")
END Main.
```

與其他語言一樣，Modula-3程序必須有導出`Main`接口的實現模塊，如上述示例中實現模塊預設導出了同名的接口，它可以處在名為Main.m3的文件中；或者是通過`EXPORTS`導出`Main`接口的實現模塊，比如：

```modula2
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委員會堅持一個自稱為「複雜度預算」的設計原則：在五十頁內將語言準確而易讀的描述出來，為此只容納最有用的特徵。精選出來的特徵直接針對兩個主要目標：

- 接口、對象、泛型和線程，提供有助於建造大型程序的基礎抽象模式。
- 不安全代碼的顯式標記、自動垃圾收集和例外，有助於讓程序更加安全和健壯。

這些特徵中任何一個都沒有特殊的新穎性，但是組合起來是簡單而強力的。人們越是更好的理解程序，就越是使用更大的建造塊去構造它。指令之後是語句，語句之後是過程，過程之後是接口，下一步應當是抽象類型。在理論層面，抽象類型，是通過其操作的規定，而非通過其數據的表示，來定義的類型。

## 詞法記號

關鍵字：

- `AND`、`ANY`、`ARRAY`、`AS`、`BEGIN`、`BITS`、`BRANDED`、`BY`、`CASE`、`CONST`、`DIV`｜、`DO`、`ELSE`、`ELSIF`、`END`、`EVAL`、`EXCEPT`、`EXCEPTION`、`EXIT`、`EXPORTS`、`FINALLY`、`FOR`｜、`FROM`、`GENERIC`、`IF`、`IMPORT`、`IN`、`INTERFACE`、`LOCK`、`LOOP`、`METHODS`、`MOD`、`MODULE`｜、`NOT`、`OBJECT`、`OF`、`OR`、`OVERRIDES`、`PROCEDURE`、`RAISE`、`RAISES`、`READONLY`、`RECORD`、`REF`｜、`REPEAT`、`RETURN`、`REVEAL`、`ROOT`、`SET`、`THEN`、`TO`、`TRY`、`TYPE`、`TYPECASE`、`UNSAFE`｜、`UNTIL`、`UNTRACED`、`VALUE`、`VAR`、`WHILE`、`WITH`

保留標識符：

- `ABS`、`ADDRESS`、`ADR`、`ADRSIZE`、`BITSIZE`、`BOOLEAN`｜、`BYTESIZE`、`CARDINAL`、`CEILING`、`CHAR`、`DEC`、`DISPOSE`｜、`EXTENDED`、`FALSE`、`FIRST`、`FLOAT`、`FLOOR`、`INC`｜、`INTEGER`、`ISTYPE`、`LAST`、`LONGINT`、`LONGREAL`、`LOOPHOLE`｜、`MAX`、`MIN`、`MUTEX`、`NARROW`、`NEW`、`NIL`｜、`NULL`、`NUMBER`、`ORD`、`REAL`、`REFANY`、`ROUND`｜、`SUBARRAY`、`TEXT`、`TRUE`、`TRUNC`、`TYPECODE`、`VAL`

運算符：

`+`｜`<`｜`#`｜`=`｜`;`｜`..`｜`:`

`*`｜`<=`｜`(`｜`)`｜`^`｜`,`｜`=>`

`/`｜`>=`｜`[`｜`]`｜`.`｜`&`

此外，注釋是開於`(*`併合於`*)`的任意字符的序列。注釋可以嵌套並可以擴展跨越多於一行。

## 基礎性語法和語義

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沒有塊語句。一個塊有如下形式：

```modula2
    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`只在它的類型是開放陣列的時候求值。

#### 序數類型

枚舉類型聲明有如下形式：，這裡的`id`是各不相同的標識符，類型`T`是`n`個值的有序集合，表達式`T.id_i`指示這個類型在遞增次序下的第`i`個值，空枚舉`{ }`是允許的。

子範圍類型聲明有如下形式：TYPE T = Lo..Hi，這裡的`Lo`和`Hi`是有相同的基礎類型的兩個序數值，`T`的值是從`Lo`到`Hi`含二者所有的值，它們必須是常量表達式，如果`Lo`超過`Hi`，則子範圍是空的。

每個不同的枚舉類型介入新的一組值，而子範圍類型重複使用底層類型的值。例如：

```modula2
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]`的簡寫。索引的解釋由一個陣列的類型確定，而非它的值；陣列賦值改變數組變量的值，而非它的類型。例如：

```modula2
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`。

對於集合類型，`SET OF A <: SET OF B`的條件是`A <: B`，就是說集合的子類型規則簡單的使用值集合規則。

對於引用類型有：

```objectpascal
NULL <: REF T <: REFANY
NULL <: UNTRACED REF T <: ADDRESS
```

就是說，`REFANY`和`ADDRESS`分別包含所有的有跟蹤的和無跟蹤的引用，`NIL`是所有的引用類型的一個成員。這兩個規則也適用於加銘牌的類型。

對於對象類型有：

```objectpascal
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`語句有如下形式：

```modula2
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的同名語句，它有如下形式：

```modula2
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`語句有如下形式：

```modula2
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，被定義為：

```modula2
LOOP
    IF B THEN S
    ELSE EXIT 
    END
END
```

語句REPEAT S UNTIL B，被定義為：

```modula2
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`語句精確的定義為一個塊：

```modula2
    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`被聲明為：

```modula2
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、Scala和Visual Basic.NET。`TRY-EXCEPT`語句有如下形式：

```objectpascal
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，非正式的等價於：

```objectpascal
TRY
    S; S; S; ...
EXCEPT 
    exit-exception => (*skip*)
END
```

### 過程

#### 過程類型

一個過程要麼是`NIL`，要麼是有如下構成的三元組：

- 過程體，它是一個語句。
- 簽名，它有如下形式`(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`集合。

過程常量是聲明為過程的一個標識符。過程變量是聲明為具有過程類型的一個變量。例如：

```modula2
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`的真正過程，在綁定了實際參數之後等價於：

```objectpascal
TRY
    B
EXCEPT
    return-exception => (*skip*)
END
```

而調用具有過程體`B`的函數過程等價於：

```objectpascal
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`的一個欄位或方法類型之內，或者在一個過程體之內。下面是合法的遞歸聲明的例子：

```objectpascal
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，接受只在顯式的標記為不安全的模塊中才允許的少量不安全特徵。

### 模塊化

接口中的聲明，除了任何變量初始化必須是常量，和過程聲明必須只能指定簽名而不能有過程體之外，同於在塊中的聲明。接口有如下形式：

```objectpascal
INTERFACE id;
    Imports;
    Decls
END id.
```

這裡的`id`是命名這個接口的標識符，`Imports`是一序列的導入語句，而`Decls`是一序列的聲明。在`Decls`中的名字和可見的導入的名字必須是各不相同的。兩個或更多個接口形成導入環是靜態錯誤。典型的接口定義，通常為每個接口定義一個數據結構（記錄或對象）以及任何的支持過程。

模塊除了實體名字的可見性之外，就像是一個塊。一個實體在一個塊中是可見的，如果它被聲明在這個塊中，或在某個包圍的塊中；而一個實體在一個模塊中是可見的，如果它被聲明在這個模塊中，或在這個模塊所導入或導出的一個接口中。模塊有如下形式：

```modula2
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`的模塊，它的內容是依賴於實現的。程序執行在主模塊終止的時候終止，即使並發的控制線程仍在執行。

下面是接口與模塊的一個規範性的例子，是有隱藏表示的一個公開堆棧：

```objectpascal
INTERFACE Stack;
    TYPE T <: REFANY;
    PROCEDURE Create(): T;
    PROCEDURE Push(VAR s: T; x: REAL);
    PROCEDURE Pop(VAR s: T): REAL;
END Stack.
```

```modula2
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.
```

如果多於一個模塊需要此堆棧的表示，則應當把它轉移到私有接口中，從而在需要它的任何地方的都可以導入它：

```objectpascal
INTERFACE Stack;
    (* ... 同上 ... *)
END Stack.

INTERFACE StackRep;
    IMPORT Stack;   
    REVEAL Stack.T = BRANDED OBJECT
        item: REAL;
        link: Stack.T
    END
END StackRep.
```

```modula2
MODULE Stack;
    IMPORT StackRep;
    (* Push、Pop和Create同上 *)
BEGIN
END Stack.
```

### 對象類型

一個對象要麼是`NIL`，要麼是到有配對的方法套件的一個數據記錄的引用，方法套件是接受這個對象作為第一個實際參數的那些過程的一個記錄。這個數據記錄可以包含經由對象類型的子類型介入的增加欄位，這個套件可以包含經由子類型介入的增加方法。對象賦值是引用賦值。要複製一個對象的數據記錄到另一個對象，這些欄位必須單獨的賦值。

Modula-3意圖保持採用對象這個最簡單的術語，而非在其他面向對象語言中對應的術語類。對象類型聲明採用如下形式：

```objectpascal
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`方法所含蓋的簽名。舉例說明：

```objectpascal
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`方法的候選值。例如：

```objectpascal
TYPE T1 = Sub OBJECT
    OVERRIDES
        p := ProcSub
    END
```

```objectpascal
TYPE T2 = Super OBJECT
    OVERRIDES
        p := ProcSuper
    END
```

聲明了具有`Sub`數據記錄和預期一個`Sub`的`p`方法的一個類型。`T1`是`Sub`的有效的子類型。｜聲明了具有`Super`數據記錄和預期一個`Super`的`p`方法的一個類型。`T2`是`Super`的有效的子類型。

```objectpascal
TYPE T3 = Sub OBJECT
    OVERRIDES
        p := ProcSuper
    END
```

```objectpascal
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`，如果檢查失敗則發生運行時間錯誤。

下面的例子展示了在聲明新方法和覆蓋現存方法之間的不同，首先聲明如下：

```objectpascal
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)`。

對象不能解引用算符，因為在語言實現中，一個對象變量的靜態類型不確定它的數據記錄的類型。對象類型確定了關乎數據記錄欄位和方法套件的超類型鏈的前綴的那些類型。

#### 實現

下面是語言設計者提出的對象的一種可能實現的梗概，一個對象可以表示為它的數據記錄的第一個字的地址。前面的字存儲一個對象頭部，它包含一個唯一於對象類型的類型代碼。這些類型代碼是小型整數，對每個對象類型和每個有跟蹤的引用類型都有一個代碼。在對象頭部之前的字，存儲到這個對象的方法套件的一個引用。如果這個對象沒有方法，這個字可以省略。這還允許對象共享方法套件，這是常見情況。如果`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`的類型代碼是否出現在如下序列之中：

```modula2
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是非法的遞歸聲明。

揭示只允許用在接口和模塊的最外層作用域內。在接口中的揭示可以被導入到任何需要它的作用域之內。揭示提供了向客戶隱藏實現細節的，在概念上簡單清晰卻非常強力的機制，例如：

```objectpascal
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`的對象。

### 泛型

在泛型接口或模塊中，某些導入的接口名字被當作形式參數，它們在泛型被實例化的時候要綁定到實際接口上。泛型接口及其實例有如下左側的形式，並等價於右側定義的普通接口：

```objectpascal
GENERIC INTERFACE G(F_1, ..., F_n);
Body
END G.
INTERFACE I = G(A_1, ..., A_n) END I.
```

```objectpascal
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`的形式導入要綁定的實際接口的一個列表。

泛型模塊及其實例有如下左側的形式，並等價於右側定義的普通模塊：

```modula2
GENERIC MODULE G(F_1, ..., F_n);
Body
END G.
MODULE I EXPORTS E = G(A_1, ..., A_n) END I.
```

```modula2
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++模板中可以使用。例如，可以定義泛型堆棧：

```objectpascal
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.
```

```modula2
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`，對其進行實例化，只要這個接口定義了泛型模塊所需的屬性即可：

```objectpascal
INTERFACE IntegerElem;
    TYPE T = INTEGER;
END IntegerElem.

INTERFACE IntStack = Stack(IntegerElem) END IntStack.
```

```modula2
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`接口的完全實現：

```objectpascal
INTERFACE Person;
    TYPE T <: Public;
        Public = OBJECT 
        METHODS
            getAge(): INTEGER;
            init(name: TEXT; age: INTEGER): T;
        END;
END Person.
```

```modula2
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()`：

```modula2
VAR jim := NEW(Person.T).init("Jim", 25);
```

下面是整數堆棧的例子：

```objectpascal
INTERFACE IntegerStack;
    TYPE
        T <: Public OBJECT;
        Public = OBJECT
        METHODS
            init(): T;
            push(x: INTEGER);
            pop(): INTEGER;
        END;
END IntegerStack.
```

```modula2
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`是一個表達式，它等價於：

```objectpascal
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網路對象為其先驅性工作。

Python從Modula-3借鑑了模塊系統、例外系統和關鍵字參數，Python的類機制受到C++和Modula-3的啟發。Nim利用了Modula-3的某些方面比如有跟蹤和無跟蹤的指針。

## 外部連結

- www.modula3.org
- 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)

## 基本資料

- 編程范型：指令式, 過程式, 結構化, 模塊化, 並發
- 類型系統：強類型, 靜態, 安全或在不安全時顯式安全隔離
- 作用域：詞法
- 語言家族：Wirth Modula
- 設計者：Luca Cardelli, James Donahue, Lucille Glassman, Mick Jordan, Bill Kalsow, Greg Nelson
- 實作者：DEC, Olivetti, elego Software Solutions GmbH
- 發行日期：1988年
- 軟體實現：DEC系統研究中心 Modula-3, CM3, PM3, EZM3, M3/PC Klagenfurt
- 受影響於：ALGOL, Euclid (程式語言), Mesa, Modula-2, Modula-2+, Oberon, Object Pascal
- 施影響於：C#, Java, Nim, Python, Baby Modula-3
- 平台：IA-32, x86-64, PowerPC, SPARC
- 作業系統：跨平台: FreeBSD, Linux, Darwin, SunOS