编辑“︁
Modula-3
”︁(章节)
跳转到导航
跳转到搜索
警告:
您没有登录。如果您进行任何编辑,您的IP地址会公开展示。如果您
登录
或
创建账号
,您的编辑会以您的用户名署名,此外还有其他益处。
反垃圾检查。
不要
加入这个!
==特征性技术== Modula-3的设计者认为[[Modula-2]]最成功的特征,是提供了在[[模块化编程|模块]]之间的显式[[介面 (資訊科技)|接口]],故而接口在Modula-3中保留而在本质上没有变化。到模块的接口,是披露了模块公开部份的声明[[集合 (计算机科学)|搜集]],而未声明于接口中的模块中的东西是私有的。模块导入它所依赖的接口,而导出它所实现的接口(在Modula-3中可以是多个接口)。 在现代编程语言的实现下,一个抽象类型的值,被表示一个[[对象 (计算机科学)|对象]],它的操作由叫做对象的[[方法 (计算机科学)|方法]]的过程值的一个套件来实现。一个新的对象类型,可以定义为现存类型的[[子类型]],在这种情况下新类型拥有旧类型的所有方法,并且可能还有新的方法([[继承 (计算机科学)|继承]])。新类型可以为旧方法提供新的实现([[方法覆盖|覆盖]])。对象类型和[[Modula-2]]的[[不透明数据类型|不透明类型]]的组合,产生了一个新事物:部份不透明(partially opaque)类型,这里的一个对象的一些字段在一个作用域内是可见的,而其他的是隐藏的。 [[泛型编程|泛型]]模块是一种[[模板元编程|模板]],在其中一些导入的接口被当作形式参数,它们在[[实例 (计算机科学)|实例化]]泛型的时候要绑定到实际接口。不同的泛型实例是独立编译的,源代码程序被[[代码复用|重复使用]],而编译后的代码对不同实例通常是不同的。为保持简单性,在Modula-3中泛型被限制在模块层面,泛型的过程和类型不孤立存在,并且[[参数多态|泛型形式参数]]必须都是完全的接口。 将计算分解入[[并发性|并发]][[进程]](或[[线程]])中是[[关注点分离]]的基本方法。[[Modula-2]]提供的线程很弱,本质上相当于[[协程]]。Modula-3采用了[[東尼·霍爾|霍尔]]的[[監視器 (程序同步化)|监视器]],它是[[并发计算|并发编程]]的更坚实基础。{{le|Modula-2+}}在设计中加入了简化的[[Mesa语言|Mesa]]同步原语,它足够成功而在实质上没有改变的结合入了Modula-3。 一个语言特征是不安全的,如果它的滥用可以破坏运行时系统,使得程序的进一步运行不忠实于语言的语义,不安全特征的一个例子,是没有[[边界检查]]的数组赋值。一个安全的程序的一个错误,可以导致计算中止,并给出运行时间错误信息或给出错误解答,但它不会导致计算分崩离析。Modula-3在这个领域里跟从Cedar,接受只在显式的标记为不安全的模块中才允许的少量不安全特征。 ===模块化=== [[介面 (資訊科技)|接口]]中的声明,除了任何变量初始化必须是常量,和过程声明必须只能指定签名而不能有过程体之外,同于在[[块 (编程)|块]]中的声明。[[介面 (資訊科技)|接口]]有如下形式: <syntaxhighlight lang="objectpascal"> INTERFACE id; Imports; Decls END id. </syntaxhighlight> 这里的<code>id</code>是命名这个接口的标识符,<code>Imports</code>是一序列的导入语句,而<code>Decls</code>是一序列的声明。在<code>Decls</code>中的名字和可见的导入的名字必须是各不相同的。两个或更多个接口形成导入环是静态错误。典型的接口定义,通常为每个接口定义一个数据结构(记录或对象)以及任何的支持过程。 [[模块化编程|模块]]除了实体名字的可见性之外,就像是一个[[块 (编程)|块]]。一个实体在一个块中是可见的,如果它被声明在这个块中,或在某个包围的块中;而一个实体在一个模块中是可见的,如果它被声明在这个模块中,或在这个模块所导入或导出的一个接口中。[[模块化编程|模块]]有如下形式: <syntaxhighlight lang="modula2"> MODULE id EXPORTS Interfaces; Imports; Block id. </syntaxhighlight> 这里的<code>id</code>是命名这个模块的一个标识符,<code>Interfaces</code>是这个模块导出的那些接口的不同名字的一个列表,<code>Imports</code>是一序列的导入语句,而<code>Block</code>是一个块,它是模块体。名字<code>id</code>必须重复于终结模块体的<code>END</code>之后。<code>EXPORTS Interfaces</code>可以省略,在这种情况下<code>Interfaces</code>缺省为<code>id</code>,即模块将缺省导出相同名称的接口。Modula-3中的导出<code>EXPORTS</code>,与[[Modula-2]]中的导出<code>EXPORT</code>无关,它在概念上对应于Modula-2的“实现”。 如果模块<code>M</code>导出了接口<code>I</code>,则在<code>I</code>中声明的所有名字在<code>M</code>中是不加以限定而可见的。一个模块<code>M</code>导出一个接口<code>I</code>,可以重新声明在这个接口中声明的一个或多个过程,为其提供过程体。在<code>M</code>中的[[类型签名|签名]],必须被在<code>I</code>中的签名所含盖。要确定在对这个过程的调用中的关键字绑定的解释,在<code>M</code>内使用<code>M</code>中的签名,在其他地方使用<code>I</code>中的签名。除了重新声明导出的过程之外,在<code>Block</code>的顶层中声明的名字,可见的导入的名字,和在导出的接口中声明的名字必须是各不相同的。 一个程序的模块和接口的名字叫做全局名字。查找全局名字的方法是依赖于实现的,比如通过[[文件系统]][[PATH (变量)|查找]][[路径 (计算机科学)|路径]]。一个模块或接口<code>X</code>导入一个接口<code>I</code>,从而使得在<code>I</code>中声明的实体和揭示在<code>X</code>中可见,但不包括<code>I</code>所导入的那些。一个模块<code>M</code>使用了接口<code>I</code>,如果<code>M</code>导入或导出了<code>I</code>,或者<code>M</code>使用了导入<code>I</code>的一个接口。Modula-3中所有的编译单元,要么是接口要么是模块。任何编译单元都可以使用<code>IMPORT</code>语句从其他接口导入标识符。与其他语言的[[包含指令|包含]]功能不同,任何标识符都可以追踪到它的起源位置。 <code>IMPORT</code>语句有如下的典型使用形式:{{code|2="modula2"|1=IMPORT I}},它导入全局名字为<code>I</code>的接口,并给它一个同样的局部名字,从而使用[[点表示法]]访问接口中的实体(类似于访问记录中的字段)。比如接口中的主要类型按惯例命名为<code>T</code>,则使用它时采用的限定形式为<code>I.T</code>。如果导入接口的名字与模块内的其他实体的名字发生冲突,则可以使用{{code|2="modula2"|1=IMPORT I AS X}}这样的形式给与它不同的局部名字。 <code>IMPORT</code>语句还有如下的另一种形式:{{code|2="modula2"|1=FROM I IMPORT N}},它为在接口<code>I</code>中声明为<code>N</code>的实体,介入一个局部名字<code>N</code>,比如:{{code|2="modula2"|1=FROM IO IMPORT Put}},并且不加限定的使用它,比如:{{code|2="modula2"|1=Put("Hello World\n")}}。局部绑定优先于全局绑定,例如:{{code|2="modula2"|1=IMPORT I AS J, J AS I; FROM I IMPORT N}},为全局名字为<code>J.N</code>的实体介入了局部名字<code>N</code>。非法使用相同的局部名字两次是静态错误。 一个程序是模块和接口的搜集,包含了任何它的模块或接口所导入或导出的所有接口,并且在其中没有重复定义的过程、模块或接口。执行一个程序的效果是执行它的所有模块的模块体。模块的执行次序受到初始化规则的约束:如果模块<code>M</code>依赖于模块<code>N</code>并且<code>N</code>不依赖于<code>M</code>,则<code>N</code>的模块体将先于<code>M</code>的模块体执行。这里的模块<code>M</code>依赖于模块<code>N</code>,如果M使用了<code>N</code>导出的一个接口,或者<code>M</code>所依赖的一个模块依赖于<code>N</code>。 其模块体最后执行的模块叫做主模块。在初始化规则不能唯一的确定它的情况下,语言实现提供了指定这个主模块的方法,即主模块是导出<code>Main</code>的模块,它的内容是依赖于实现的。程序执行在主模块终止的时候终止,即使并发的控制线程仍在执行。 下面是接口与模块的一个规范性的例子,是有隐藏表示的一个公开[[堆栈]]: <syntaxhighlight lang="objectpascal"> INTERFACE Stack; TYPE T <: REFANY; PROCEDURE Create(): T; PROCEDURE Push(VAR s: T; x: REAL); PROCEDURE Pop(VAR s: T): REAL; END Stack. </syntaxhighlight> <syntaxhighlight lang="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. </syntaxhighlight> 如果多于一个模块需要此堆栈的表示,则应当把它转移到私有接口中,从而在需要它的任何地方的都可以导入它: <syntaxhighlight lang="objectpascal"> INTERFACE Stack; (* ... 同上 ... *) END Stack. INTERFACE StackRep; IMPORT Stack; REVEAL Stack.T = BRANDED OBJECT item: REAL; link: Stack.T END END StackRep. </syntaxhighlight> <syntaxhighlight lang="modula2"> MODULE Stack; IMPORT StackRep; (* Push、Pop和Create同上 *) BEGIN END Stack. </syntaxhighlight> ===对象类型=== 一个[[对象 (计算机科学)|对象]]要么是<code>[[空指针|NIL]]</code>,要么是到有配对的[[方法 (计算机科学)|方法]]套件的一个数据[[记录]]的[[参照|引用]],方法套件是接受这个对象作为第一个实际参数的那些[[子程序|过程]]的一个记录。这个数据记录可以包含经由对象类型的[[子类型]]介入的增加字段,这个套件可以包含经由子类型介入的增加方法。对象[[赋值语句|赋值]]是引用赋值。要复制一个对象的数据记录到另一个对象,这些字段必须单独的赋值。 Modula-3意图保持采用[[对象 (计算机科学)|对象]]这个最简单的术语,而非在其他面向对象语言中对应的术语[[类 (计算机科学)|类]]。对象类型声明采用如下形式: <syntaxhighlight lang="objectpascal"> TYPE T = ST OBJECT Fields METHODS Methods OVERRIDES Overrides END </syntaxhighlight> 这里的<code>ST</code>是可选的[[子类型|超类型]],如果省略则缺省为<code>ROOT</code>,<code>Fields</code>是完全如同记录类型那样的字段声明的一个列表,<code>Methods</code>是方法声明的一个列表,而<code>Overrides</code>是方法覆盖的一个列表。在字段和方法声明中介入的名字,必须相互不同,并且不同于在覆盖声明中的名字。<code>T</code>的字段构成自<code>ST</code>的字段和随后的新声明的字段。<code>T</code>的方法构成自经过<code>OVERRIDES</code>中的覆盖修改的<code>ST</code>的方法,和随后的<code>METHODS</code>中声明的方法。<code>T</code>有着同<code>ST</code>一样的引用类。 关键字<code>OBJECT</code>可选的可以前导上<code>BRANDED</code>或<code>BRANDED b</code>来给对象加铭牌,使之唯一而避免结构等价,这里的<code>b</code>是文本常量。其含义同于非对象的引用类型,<code>b</code>被省略时,系统自动生成唯一性的一个字符串。 方法声明有如下形式:<code>m sig := proc</code>,这里的<code>m</code>是个标识符,<code>sig</code>是过程[[类型签名|签名]],而<code>proc</code>是顶层过程常量。方法声明指定了<code>T</code>的方法<code>m</code>有签名<code>sig</code>和值<code>proc</code>。如果省略了<code>:= proc</code>假定它为<code>:= NIL</code>。如果<code>proc</code>非空,它的第一个形式参数必须是缺省的模态<code>VALUE</code>,并有着<code>T</code>的某个超类型的类型(包括且经常是<code>T</code>自身),并且去除了第一个形式参数结果就是<code>sig</code>所含盖的签名。 方法[[方法覆盖|覆盖]](override)有如下形式:<code>m := proc</code>,这里的<code>m</code>是超类型<code>ST</code>的方法的名字,而<code>proc</code>是顶层过程常量。方法覆盖指定了<code>T</code>的方法<code>m</code>是<code>proc</code>,并非指定<code>ST.m</code>。如果<code>proc</code>非空,它的第一个形式参数必须是缺省的模态<code>VALUE</code>,并有着<code>T</code>的某个超类型的类型(包括且经常是<code>T</code>自身),去除了<code>proc</code>第一个形式参数结果就是<code>ST</code>的<code>m</code>方法所含盖的签名。举例说明: <syntaxhighlight lang="objectpascal"> TYPE Super = OBJECT a: INTEGER; METHODS p() END; Sub = Super OBJECT b: INTEGER END; PROCEDURE ProcSuper(self: Super) = ... ; PROCEDURE ProcSub(self: Sub) = ... ; </syntaxhighlight> 这里的过程<code>ProcSuper</code>和<code>ProcSub</code>是类型<code>Super</code>和<code>Sub</code>的对象的<code>p</code>方法的候选值。例如: {| class = wikitable |- style="vertical-align:top" | <syntaxhighlight lang="objectpascal"> TYPE T1 = Sub OBJECT OVERRIDES p := ProcSub END </syntaxhighlight> || <syntaxhighlight lang="objectpascal"> TYPE T2 = Super OBJECT OVERRIDES p := ProcSuper END </syntaxhighlight> |- | 声明了具有<code>Sub</code>数据记录和预期一个<code>Sub</code>的<code>p</code>方法的一个类型。<code>T1</code>是<code>Sub</code>的有效的子类型。 || 声明了具有<code>Super</code>数据记录和预期一个<code>Super</code>的<code>p</code>方法的一个类型。<code>T2</code>是<code>Super</code>的有效的子类型。 |- style="vertical-align:top" | <syntaxhighlight lang="objectpascal"> TYPE T3 = Sub OBJECT OVERRIDES p := ProcSuper END </syntaxhighlight> || <syntaxhighlight lang="objectpascal"> TYPE T4 = Super OBJECT OVERRIDES p := ProcSub END </syntaxhighlight> |- | 声明了具有<code>Sub</code>数据记录和预期一个<code>Super</code>的<code>p</code>方法的一个类型。因为所有<code>Sub</code>都是<code>Super</code>,这个方法不挑剔要放入的对象。 || 尝试声明具有<code>Super</code>数据记录和预期一个<code>Sub</code>的<code>p</code>方法的一个类型,因为不是所有<code>Super</code>都是<code>Sub</code>,这个方法很挑剔要放入的对象。<code>T4</code>的声明是个静态错误。 |} 如果<code>T</code>是对象类型或到记录的引用,<code>NEW(T)</code>操作有如下形式:{{code|2="modula2"|1=NEW(T, Bindings)}},这里的<code>Bindings</code>是用来初始化新字段的关键字绑定的一个列表,不允许位置绑定。每个绑定<code>f := v</code>将字段<code>f</code>初始化为值<code>v</code>。如果<code>T</code>是对象类型,则<code>Bindings</code>还可以包括形如<code>m := P</code>的方法覆盖,这里的<code>m</code>是<code>T</code>的一个方法而<code>P</code>是一个顶层过程常量。{{code|2="modula2"|1=NEW(T, m := P)}}是{{code|2="objectpascal"|1=NEW(T OBJECT OVERRIDES m := P END)}}的[[语法糖]]。 如果<code>o</code>是一个对象,则<code>o.f</code>指示在<code>o</code>的数据记录中名为<code>f</code>的数据字段。<code>o.f</code>是可写的指定式,它的类型是这个字段的所声明的类型。如果<code>m</code>是<code>o</code>方法之一,以<code>o.m(Bindings)</code>形式的过程调用,指示执行<code>o</code>的<code>m</code>方法,这等价于:<code>(o的m方法) (o, Bindings)</code>。 如果<code>T</code>是对象类型而<code>m</code>是<code>T</code>的方法的名字之一,则<code>T.m</code>指示<code>T</code>的<code>m</code>方法。它的过程类型的第一个实际参数有类型<code>T</code>,而第一个实际参数对应的形式参数名字是未指定的,因此在调用<code>T.m</code>时,第一个实际参数必须按位置来指定,不能用关键字。这种表示法便于子类型方法调用它的某个超类型的对应方法。 在子类型中的字段或方法遮盖(mask)了在超类型中的任何同名的字段或方法。要访问被遮盖的字段,使用类型运算<code>NARROW(x, T)</code>来将子类型变量当作超类型的成员,这里的<code>T</code>必须是对象类型或有跟踪的引用类型,而<code>x</code>必须可赋值给<code>T</code>。<code>NARROW(x, T): T</code>在检查了<code>x</code>是<code>T</code>一个成员之后返回<code>x</code>,如果检查失败则发生运行时间错误。 下面的例子展示了在声明新方法和覆盖现存方法之间的不同,首先声明如下: <syntaxhighlight lang="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); </syntaxhighlight> 然后可以用<code>a.m()</code>激活<code>ProcSuper(a)</code>,用<code>b.m()</code>激活<code>ProcSub(b)</code>,用<code>c.m()</code>激活<code>ProcSub(c)</code>,如此调用在覆盖和扩展之间没有区别。但是扩展的<code>c</code>的方法套件有两个方法,而覆盖的<code>b</code>的方法套件只有一个方法,这可以通过使用<code>NARROW(x, T)</code>将子类型的变量<code>b</code>和<code>c</code>视为超类型<code>Super</code>的成员来披露:<code>NARROW(b, Super).m()</code>激活<code>ProcSub(b)</code>,<code>NARROW(c, Super).m()</code>激活<code>ProcSuper(c)</code>。 对象不能{{le|解引用算符|Dereference operator|解引用}},因为在语言实现中,一个对象变量的静态类型不确定它的数据记录的类型。对象类型确定了关乎数据[[记录]][[字段]]和[[方法 (计算机科学)|方法]]套件的超类型链的前缀的那些类型。 ====实现==== 下面是语言设计者提出的对象的一种可能实现的梗概<ref name="type">{{cite web|url=http://lucacardelli.name/papers/modula3typesystem.a4.pdf|title=The Modula-3 Type System|authors=Luca Cardelli, Jim Donahue, Mick Jordan, Bill Kalsow, Greg Nelson|year=1989|access-date=2021-08-12|archive-date=2021-03-24|archive-url=https://web.archive.org/web/20210324050223/http://lucacardelli.name/Papers/Modula3TypeSystem.A4.pdf}}</ref>,一个对象可以表示为它的数据记录的第一个[[字 (计算机)|字]]的地址。前面的字存储一个对象头部,它包含一个唯一于对象类型的类型代码。这些类型代码是小型整数,对每个对象类型和每个有跟踪的引用类型都有一个代码。在对象头部之前的字,存储到这个对象的方法套件的一个引用。如果这个对象没有方法,这个字可以省略。这还允许对象共享方法套件,这是常见情况。如果<code>o</code>是一个对象,<code>d</code>是它的数据字段之一,而<code>m</code>是它的方法之一,则在这种表示下: {| class="wikitable" |- |<code>o.d</code> || 是 || <code>Mem[o + d]</code> |- |<code>o.m</code> || 是 || <code>Mem[Mem[o – 2] + m]</code> |- |<code>TYPECODE(o)</code> || 是 || <code>Mem[o – 1]</code> |} 这里假定了字段和方法以自然方式表示为[[偏移量]]。进一步的问题是如何高效的测试一个对象<code>o</code>是否有着类型<code>T</code>,这是<code>NARROW()</code>和<code>TYPECASE</code>语句所需要的。<code>NARROW()</code>的最简单实现,是维护一个以类型代码为索引的数组<code>st</code>,<code>st[tc]</code>是其类型代码为<code>tc</code>的对象类型的超类型的类型代码,如果它没有超类型则为<code>NIL</code>。要测试<code>o</code>是否是一个<code>T</code>,使用一个循环来计算<code>T</code>的类型代码是否出现在如下序列之中: <syntaxhighlight lang="modula2"> TYPECODE(o), st[TYPECODE(o)], st[st[TYPECODE(o)]], ... NIL </syntaxhighlight> 这个序列可以称为<code>o</code>的类型的超类型[[路径 (计算机科学)|路径]],而它的长度可称为<code>o</code>的类型的深度。利用上每个类型的深度都是编译时间确定的,因此可以同相应的类型代码一起存储的事实,可以有更快速的<code>NARROW()</code>实现。如果<code>T</code>的类型代码,出现在类型<code>U</code>的超类型路径上,它就该在位置<code>depth(U) - depth(T)</code>上。如果每个类型的超类型路径都是顺序[[数组]],这意味着<code>NARROW()</code>可以用恒定时间实现。因为超类型路径通常不太长,这是一个有吸引力的策略。在不常见的对象类型有非常长的超类型链的情况下,只有这个链的直到某个极大长度的一个前缀,会被顺序的存储。在运行时间,这个深度差如果超出这个链的顺序存储的长度,实现必须回退到[[链表]]。 ===不透明类型及其披露=== [[不透明数据类型|不透明]](opaque)类型声明有如下形式:{{code|2="modula2"|1=TYPE T <: U}},这里的<code>T</code>是标识符,而<code>U</code>是指示一个引用类型的表达式。它将名字<code>T</code>介入为不透明类型,并披露了<code>U</code>是<code>T</code>的超类型。 Modula-3的揭示(revelation)机制,将关于一个不透明类型的信息,介入到一个[[作用域]]之内,不同于其他声明,揭示不介入新的名字。不同的作用域,可以披露(reveal)关于一个不透明类型的不同的信息。例如,某个不透明类型,在一个作用域内知晓为<code>REFANY</code>的子类型,在其他的作用域内可以知晓为<code>ROOT</code>的子类型。 不透明类型名字所指示的实际类型叫做具体类型。<code>T</code>的具体类型,必须披露于程序的其他地方。在对象声明中,如果<code>ST</code>被声明为不透明类型,则只在知晓<code>ST</code>的具体类型为对象类型的作用域内,<code>T</code>的声明是合法的。如果<code>T</code>被声明为不透明类型,则只在完全知晓<code>T</code>的具体类型,或者知晓它为对象类型的作用域内,<code>NEW(T)</code>是合法的。 揭示分为两种:部份的和完全的,一个程序可以包含一个不透明类型的任意多个部份揭示,但必须包含唯一的一个完全揭示。 部份揭示有如下形式:{{code|2="modula2"|1=REVEAL T <: V}},这里的<code>V</code>是类型表达式(可能就是个名字),而<code>T</code>是被声明为不透明类型的一个标识符(可能有限定)。它披露了<code>V</code>是<code>T</code>的超类型。在任何作用域内,对一个不透明类型披露的超类型,必须是在子类型关系下是线性有序的。就是说,如果披露了<code>T <: U1</code>和<code>T <: U2</code>,则必须也要披露要么<code>U1 <: U2</code>要么<code>U2 <: U1</code>。{{code|2="modula2"|1=REVEAL T <: T}}是合法的非递归声明。 完全揭示有如下形式:{{code|2="modula2"|1=REVEAL T = V}},这里的<code>V</code>是类型表达式(不能就是个名字),它的最外层[[类型构造子]],是有铭牌的一个引用或对象类型,而<code>T</code>是被声明为不透明类型的一个标识符(可能有限定)。这个揭示指定了<code>V</code>是<code>T</code>的具体类型。在任何作用域内,披露为<code>T</code>的超类型的任何类型,都必须是<code>V</code>的超类型,否则是一个静态错误。不同的不透明类型有不同的具体类型,因为<code>V</code>包含了一个铭牌并且在程序中所有的铭牌都是独一的。{{code|2="modula2"|1=REVEAL I.T = I.T BRANDED OBJECT ... END}}是非法的递归声明。 揭示只允许用在接口和模块的最外层[[作用域]]内。在接口中的揭示可以被导入到任何需要它的作用域之内。揭示提供了向客户隐藏实现细节的,在概念上简单清晰却非常强力的机制,例如: <syntaxhighlight lang="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. </syntaxhighlight> 导入<code>I</code>的编译单元见到的<code>I.T</code>是<code>ROOT</code>的不透明子类型,因而被限制为可以分配类型<code>I.T</code>的对象,把它们传递给<code>I.P</code>,或声明<code>I.T</code>的子类型。导入<code>IRep</code>的编译单元见到的<code>I.T</code>是具体类型,它是有扩展的<code>count</code>字段的<code>MUTEX</code>的子类型。导入<code>IClass</code>的编译单元见到的<code>I.T</code>是<code>MUTEX</code>的不透明子类型,因而可以锁定类型<code>I.T</code>的对象。 ===泛型=== 在[[泛型编程|泛型]]接口或模块中,某些导入的接口名字被当作形式参数,它们在泛型被实例化的时候要绑定到实际接口上。泛型接口及其实例有如下左侧的形式,并等价于右侧定义的普通接口: {| class = wikitable |- style="vertical-align:top" | <syntaxhighlight lang="objectpascal"> GENERIC INTERFACE G(F_1, ..., F_n); Body END G. INTERFACE I = G(A_1, ..., A_n) END I. </syntaxhighlight> || <syntaxhighlight lang="objectpascal"> INTERFACE I; IMPORT A_1 AS F_1, ..., A_n AS F_n; Body END I. </syntaxhighlight> |} 这里的<code>G</code>是命名这个泛型接口的一个标识符,<code>F_1, ..., F_n</code>是叫做<code>G</code>的形式导入的标识符的一个列表,而<code>Body</code>同在非泛型接口中一样,是一序列导入和随后的一序列声明。这里的<code>I</code>是这个实例的名字,而<code>A_1, ..., A_n</code>是<code>G</code>的形式导入要绑定的实际接口的一个列表。 泛型模块及其实例有如下左侧的形式,并等价于右侧定义的普通模块: {| class = wikitable |- style="vertical-align:top" | <syntaxhighlight lang="modula2"> GENERIC MODULE G(F_1, ..., F_n); Body END G. MODULE I EXPORTS E = G(A_1, ..., A_n) END I. </syntaxhighlight> || <syntaxhighlight lang="modula2"> MODULE I EXPORTS E; IMPORT A_1 AS F_1, ..., A_n AS F_n; Body END I. </syntaxhighlight> |} 这里的<code>G</code>是命名这个泛型模块的一个标识符,<code>F_1, ..., F_n</code>是叫做<code>G</code>的形式导入的标识符的一个列表,而<code>Body</code>同在非泛型模块中一样,是一序列的导入和随后的一个块。这里的<code>I</code>是这个实例的名字,<code>E</code>是由<code>M</code>导出的接口的一个列表,而<code>A_1, ..., A_n</code>是<code>G</code>的形式导入要绑定的实际接口的一个列表。<code>EXPORTS E</code>可以省略,在这种情况下它缺省为<code>EXPORTS I</code>。泛型模块自身没有导出,它们只在被实例化时提供。 [[泛型编程|泛型]]接口及其对应的泛型模块(与[[模板 (C++)|C++模板]]一样)可以轻松定义和使用[[抽象数据类型]],但粒度在模块级别,裸露的类型<code>INTEGER</code>或<code>REAL</code>不能使用,相比之下,它们在C++模板中可以使用。例如,可以定义泛型[[堆栈]]: <syntaxhighlight lang="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. </syntaxhighlight> <syntaxhighlight lang="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. </syntaxhighlight> 然后使用接口例如<code>IntegerElem</code>,对其进行实例化,只要这个接口定义了泛型模块所需的属性即可: <syntaxhighlight lang="objectpascal"> INTERFACE IntegerElem; TYPE T = INTEGER; END IntegerElem. INTERFACE IntStack = Stack(IntegerElem) END IntStack. </syntaxhighlight> <syntaxhighlight lang="modula2"> MODULE IntStack = Stack(IntegerElem) END IntStack. </syntaxhighlight> ===面向对象=== Modula-3下的[[面向对象编程]],经常采用部份不透明类型,它通过如下惯用法来声明:{{code|2="objectpascal"|1=Type T <: Public; Public = OBJECT ... END}},这里的不透明类型<code>T</code>,支持在随后定义的对象类型中的字段和方法,而不会披露<code>T</code>的确切结构,也不披露<code>T</code>可能支持的其他方法。 下面的例子,定义一个接口<code>Person</code>,具有按习惯约定命名的两个类型,导出类型<code>T</code>,和公开对象类型<code>Public</code>,这里声明了<code>T</code>是<code>Public</code>的不透明[[子类型]],<code>Public</code>具有两个方法<code>getAge()</code>和<code>init()</code>;随后是<code>Person</code>接口的完全实现: <syntaxhighlight lang="objectpascal"> INTERFACE Person; TYPE T <: Public; Public = OBJECT METHODS getAge(): INTEGER; init(name: TEXT; age: INTEGER): T; END; END Person. </syntaxhighlight> <syntaxhighlight lang="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. </syntaxhighlight> 要建立一个新的<code>Person.T</code>对象,要使用内建操作<code>NEW()</code>,并调用方法<code>init()</code>: <syntaxhighlight lang="modula2"> VAR jim := NEW(Person.T).init("Jim", 25); </syntaxhighlight> 下面是整数堆栈的例子: <syntaxhighlight lang="objectpascal"> INTERFACE IntegerStack; TYPE T <: Public OBJECT; Public = OBJECT METHODS init(): T; push(x: INTEGER); pop(): INTEGER; END; END IntegerStack. </syntaxhighlight> <syntaxhighlight lang="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. </syntaxhighlight> 创建新堆栈在这里的部份不透明类型实现下为:{{code|2="modula2"|1=VAR myStack := NEW(IntegerStack.T).init()}},在上述的不透明类型实现下为:{{code|2="modula2"|1=VAR myStack := IntStack.Create()}}。而压入一个数在这里的部份不透明类型实现下为:{{code|2="modula2"|1=myStack.push(8)}},在上述的不透明类型实现下为:{{code|2="modula2"|1=IntStack.Push(myStack, 8)}}。 ===多线程=== 语言支持多[[线程]]和在线程间的[[同步 (计算机科学)|同步]]。在[[运行时库]](m3core)中有一个必要接口叫做<code>Thread</code>,它支持采用[[分叉会合模型]]的多线程应用。这里还预定义不透明类型<code>[[互斥锁|MUTEX]]</code>被用来同步多个线程,并保护共享数据免于具有可能损害或竞争条件的同时访问。<code>MUTEX</code>是一个对象,因此可以从它派生其他对象。 <code>LOCK</code>语句有如下形式:{{code|2="modula2"|1=LOCK mu DO S END}},这里的<code>S</code>是一个语句,而<code>mu</code>是一个表达式,它等价于: <syntaxhighlight lang="objectpascal"> WITH m = mu DO Thread.Acquire(m); TRY S FINALLY Thread.Release(m) END END </syntaxhighlight> 这里的<code>m</code>表示不出现在<code>S</code>中的一个变量。<code>LOCK</code>语句介入一个[[互斥锁]]要锁定的块,并暗含着在代码执行轨迹离开这个块时的解锁它。 ===不安全标记=== 某些功能被认为是不安全的,编译器无法再保证结果是[[一致性]]的(例如,当与C编程语言交接时)。在<code>INTERFACE</code>或<code>MODULE</code>前面加上前缀关键字<code>UNSAFE</code>,可用于告诉编译器启用语言的某些不安全的低级功能。例如,在<code>UNSAFE</code>模块中,使用<code>LOOPHOLE()</code>将整数的诸位元复制成浮点<code>REAL</code>数,使用<code>DISPOSE()</code>释放无跟踪的内存。 一个接口是内在安全的,如果在安全模块中使用这个接口,无法产生不核查的运行时间错误。如果导出一个安全接口的所有的模块都是安全的,编译器保证这个接口的内在安全性。如果导出一个安全接口的任何模块是不安全的,就要编程者而非编译器,去做这种保证了。安全接口导入不安全接口,或安全模块导入或导出不安全接口,都是静态错误。导入不安全接口的编译单元本身必定也是不安全的。
摘要:
请注意,所有对Local Chinese Wikipedia的贡献均可能会被其他贡献者编辑、修改或删除。如果您不希望您的文字作品被随意编辑,请不要在此提交。
您同时也向我们承诺,您提交的内容为您自己所创作,或是复制自公共领域或类似自由来源(详情请见
Project:著作权
)。
未经许可,请勿提交受著作权保护的作品!
取消
编辑帮助
(在新窗口中打开)
导航菜单
个人工具
未登录
讨论
贡献
创建账号
登录
命名空间
页面
讨论
大陆简体
不转换
简体
繁體
大陆简体
香港繁體
澳門繁體
大马简体
新加坡简体
臺灣正體
查看
阅读
编辑
查看历史
更多
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
工具
链入页面
相关更改
特殊页面
页面信息