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