Simula

来自维基百科,自由的百科全书

Simula

Simula,一種編譯式程式語言,由奧利-約翰·達爾克利斯登·奈加特,在1960年代於挪威奧斯陸挪威计算中心英语Norwegian Computing Center,開發出來了Simula I與Simula 67兩代。它承繼了ALGOL 60作為基礎,被認為是第一個物件導向程式設計的程式語言。

事实速览 编程范型, 設計者 ...
Simula
Thumb
编程范型多范型: 指令式, 过程式, 结构化, 物件導向, 并发
設計者奧利-約翰·達爾
實作者克利斯登·奈加特
发行时间1962年,​63年前​(1962(Simula I)
1967年,​58年前​(1967(Simula 67)
当前版本
  • Standard SIMULA(1986年8月25日)[1]
編輯維基數據鏈接
型態系統静态名称式英语Nominal type system
作用域词法
實作語言主要为ALGOL 60(有一些Simscript成份)
操作系统类Unix系统Windowsz/OSTOPS-10MVS英语MVS
主要實作產品
Portable Simula Revisited[2], GNU Cim[3]
受影响于
ALGOL 60, Simscript[4]
影響語言
Smalltalk[5]CLU[6]C++BETAObject PascalModula-3Java
关闭

Simula 67介入了对象子类(后来惯称为子类继承超类)、虚过程[8],还有协程离散事件模拟和特征性的垃圾收集[9]。Simula的影響经常被低估[10]Smalltalk[5]CLU[6]C++Object PascalModula-3Java和后来的很多编程语言,受到了Simula 67的启发。BETA是Simula的现代后继者。

歷史

Thumb
最初Simula设计的离散事件网络的实例示意图

1957年,克利斯登·奈加特開始在NDRE英语Norwegian Defence Research EstablishmentFerranti Mercury英语Ferranti Mercury電腦上寫模擬器,為此他需要更強大的程式語言。1962年1月,奧利-約翰·達爾開始跟他合作,他們受到ALGOL 60的啟發。1962年5月,發展出第一份模擬器程式語言提議,語言取名為SIMULA,后改称为Simula I。此時,克利斯登·奈加特受到Sperry Rand公司英语Sperry Corporation邀請,去協助他們開發UNIVAC 1107英语UNIVAC 1100/2200 series電腦。UNIVAC軟體部門的主管鮑伯·貝莫英语Bob Bemer,力邀克利斯登·奈加特前往國際資訊處理聯盟英语International Federation for Information Processing(IFIP)舉行的第二次國際會議上,發表了論文「SIMULA-擴展ALGOL到離散事件網路的描述」[11]

1963年8月,挪威計算中心英语Norwegian Computing Center(NCC)購買到UNIVAC 1107英语UNIVAC 1100/2200 series,在UNIVAC的合約同意下,奧利-約翰·達爾在這台電腦上安裝以凯斯西储 1107 ALGOL 60的編譯器來實作的Simula I。1965年1月,Simula I終於可以在UNIVAC 1107上完全的運作。Simula I在1968年被移植到了Burroughs英语Burroughs large systems B5500電腦,以及后来蘇聯的URAL-16英语Ural (computer)電腦。

1965年,東尼·霍爾首次提出“記錄”(record class)構造的概念[12],1966年,克利斯登·奈加特奧利-約翰·達爾二人,將Simula I的具有“准并行”性质的“進程”,擴展成了具有了記錄類性质的廣義進程,隨即改稱為“物件[13]。1967年5月,奈加特和達爾在奧斯陸舉辦的IFIP工作小組論壇中,發表了關於子類聲明的論文,形成Simula 67的第一份定義文件[14]

1968年召開的會議,組成了SIMULA標準小組,並發表了第一份官方Simula標準文件「SIMULA 67通用基础语言」[15]。在1960年代后期和1970年代早期,Simula 67主要实现于四个系统之上:挪威计算中心英语Norwegian Computing CenterUNIVAC 1100系列英语UNIVAC 1100/2200 series,挪威计算中心的IBM System/360System/370英语IBM System/370奥斯陆大学Kjeller英语Kjeller联合安装的CDC 3000系列英语CDC 3000 series,和瑞典国防研究所英语Swedish National Defence Research Institute(FOA)的DEC TOPS-10

当前的Simula 67业界标准,是在1986年修订的“标准SIMULA”[7],它在1987年被接受为瑞典国家标准[16]。它有四个主要实现:Simula AS英语Simula Research LaboratoryLund Simula、GNU Cim[3]和Portable Simula Revisited[2]

Simula影响了Smalltalk[5],和后来的面向对象编程语言[10]C++語言和Java語言的創始人,都認可自己受到了Simula的重要影響[17]

语法和语义

Simula 67包含通用算法语言ALGOL 60的多数特征作为自己的子集[7],它是大小写不敏感的。Simula 67的中心概念是对象,对象是自我容纳英语Self-sustainability(self-contained)的一段程序即一个实例,它拥有由一个声明定义的自己的局部数据和行动(action)。是一种过程[18],它有能力引起在它被调用后仍存活的一个块实例,而这些实例就叫做这个类的对象。为了操纵对象和相互关联对象,语言介入了链表处理设施。

Simula 67为了将整个程序执行组织为:对象所属的诸“行动阶段”的一个序列,而将其所必需的基本功能特征,包含在作为“系统类”的“环境类”之中。在环境类中有被称为“标准系统类”的模拟器Simulation,它定义了充当系统时间轴的“定序集合”(sequencing set)对象SQS进程Process和事件通告类EVENT_NOTICE,定序集合的成员是事件通告对象,它通过其PROC特性提及一个进程对象;事件通告表示了对应这个进程对象的下一个行动阶段的一个事件,被调度在系统时间EVTIME时发生。

是成序列的声明,跟随着成序列的语句,并被包围在关键字beginend之间。块自身是一种语句[19],在Simula 67中,它包括子块和有前缀(prefixed)块,在语法上,子块就是无前缀的ALGOL 60的块,即匿名块和过程主体。

声明担负定义在程序中用到的(quantity)的特定属性,并给它们关联上标识符,这包括:简单变量、数组、接转(switch标签列表)、过程、和外来声明。块中的每个语句,都可以是块或复合语句。复合语句与块相比在语法上没有声明。

所有的块,自动地介入名称目录英语Nomenclature(nomenclature)的一个新的层级:在这个块内出现的标识符,可以通过合适的声明,而被指定为局部于所论及的这个块;这个标识符在这个块里面的所表示的实体,不存在于它的外面;这个标识符在这个块外面的所表示的任何实体,在这个块里面是不可见的;在Simula 67中,可通过连接或远程访问使它成为可见的。

除了表示标签的标识符之外,一个标识符,如果出现在一个块中,而且并非声明于这个块中,则它非局部英语Non-local variable于这个块。因为块中的语句自身可以是一个块,局部和非局部于一个块的概念,必须递归地去理解。

是一种形式描述或模式,关乎汇聚英语Aggregate数据结构和关联的算法以及行动[20]。当一个块执行的时候,生成这个块的一个动态实例[21]。一个块实例,可以被认为是它的形式描述的文字复本。在计算机中,一个块实例可以采用某一种形式的内存区域,它包含需要的动态块信息,并包括空间来持有局部于这个块的变量的内容[22]

块的任何一个内部的块,仍然是一个模式,但是在其中出现的非局部英语Non-local variable标识符,标定了局部于在字面上外在包围(textually enclosing)的块实例的项目[23]非局部英语Non-local variable于内部的块的标识符绑定,对这个内部的块的任何随后的动态实例,保持有效[24]

块实例中的局部变量,标识了分配给块实例的内存片段。在进入一个块之中的时候,任何局部于这个块即在其中声明的变量,都会被初始化。局部于一个块的一个变量,是一个内存设备,其内容依据这个变量的类型,要么是一个,要么是一个引用

类型可特征化为直接关联着一个可能值的集合,即这个类型的“值范围”。引用概念,对应于一个名字或一个“指针”的直观想法。在Simula 67中有两种引用类型对象引用类型和文本引用。对于值类型不关联着引用概念。提供了引用值的机制,还反映了机器寻址可能性;在特定简单情况下,一个引用可以被实现为一个存储的值的内存地址

过程

过程与块相比,它有一个名字,可以在程序的一些不同地方提及,并且在调用时可以给出参数[25]。过程和块都不可能建立到它或它内部的引用[25];对于一个给定块,有可能生成它的一些可以共存和交互的实例[22],例如递归过程的实例。

过程的参数传送模态,除了有传值调用传名调用[26],在Simula 67中,又增加了传引用调用。过程的缺省的传送模态,在Simula 67中,对于值类型的参数是传值调用,对于所有其他类型的参数是传引用调用;故而在过程声明的参数规定中,增加了以name为前导的名字部份,用来指定所述及的参数采用传名调用。

在过程主体内传名调用的形式参数的每次出现,都引起对实际参数的一次求值。在Simula 67中,这个求值发生在过程语句的上下文中,就是说不会出现标识符冲突,因为过程主体和它的变量此时是不可见的。过程调用的执行,在有参数的情况下要经历如下步骤:建立形式参数块实例;求值对应于传值调用或传引用调用的实际参数,并将其结果赋值给形式参数块实例的对应变量;过程主体被初始化并开始执行。

真正(proper)过程,不定义特定类型的函数指定式的值,在Simula 67中,它被称为具有普遍(universal)类型,任何类型都从属(subordinate)于普遍类型。

类与对象

类声明定义一个程序(数据和行动)模式,而符合(conforming)这个模式的对象被称为“属于相同的类”。不同于Smalltalk 80面向对象编程语言,Simula 67的类和后来的Modula-3的对象类型,不进行实例变量英语Instance variable类变量英语Class variable的区分。

对于一个给定对象,类声明中形式参数、在虚拟部份中规定的虚拟,和声明为局部于类主体(body)的量,叫做这个对象的特性(attribute)。一个特性的声明或规定叫做特性定义。在1986年修订的语言标准中,通过保护规定可以限制类特性标识符的可见性。

类声明中的形式参数,不适用传名调用,这是为了保证实际参数在类所引起的对象存活期间不能消失[27]。在规定部份中需要对每个形式参数进行规定,这些参数被当作局部于类主体的变量。类主体通常是一个,即使它如语法形式所允许的那样是块以外的一个语句,也表现得如同一个块。一个分裂(split)主体表现为一个块,在其中符号inner表示一个虚设(dummy)语句。

Simula 67的类主体中除了有定义特性的声明,还可以有定义行动的语句。如果在一个类声明中有行动定义,则符合这个模式的行动,可以由属于这个类的所有对象执行。如果在类声明中没有指定行动,则定义了一类纯数据结构,很多后来的面向对象编程语言就是这样,将对象作为记录结构的扩展。例如:

class Order(number); integer number;
begin
    integer numberOfUnits, arrivalDate;
    real processingTime;
end;

属于特定类的对象,通过对象表达式中的对象生成式new来生成,并制作出到它的引用。例如,想要多少就能生成多少属于这个Order类的一个新对象:

new Order(103);

属于一个对象的行动,可以都在就一个过程而言的一个序列中执行。这些行动,还可以作为一系列的独立子序列或“行动阶段”(active phase)来执行。在一个给定对象的两个行动阶段之间,可以出现任何数目的其他对象的行动阶段。

子类与类串接

一个类可以被用作到另一个类声明的“前缀”,从而将前缀所定义的性质,建造入这个新的类声明定义的对象之中。具有前缀类C和类标识符D的一个类声明,定义了类C的一个子类D。属于子类D的对象,由自身是类C的对象的“前缀部份”,和类D的类声明主体所描述的“主体部份”组成。这两部份串接而形成了一个复合对象,它可以用串接成的类声明来正式的描述,串接的过程被认为先于程序执行而发生。

类只能在定义它的块层级中用作前缀。前缀类自身也可以有前缀。类不能出现在自己的前缀序列中。子类被称为内部(inner)于前缀类,而前缀类被称为外部(outer)于子类,但不可将子类称为前缀类的内部类。这不同于后来的面向对象编程语言中的称谓:“派生类”扩展“基础类”,或“子类”继承“超类”。例如:

Order class SingleOrder;
begin
    real setupTime, finishingTime, weight;
end;
SingleOrder class Plate;
begin
    real length, width;
end;

属于Order类的子类SingleOrder类和Plate类的新对象,都含有Order类定义的数据,再加上在各种类声明中定义的补充数据。例如,属于Plate类的对象含有Order类、SingleOrder类和Plate类定义的所有数据。

一个标识符在一个块中的出现,如果并未处在远程标识符之中或在有重新定义的内部的块之中,则被称为“未托付”(uncommitted)出现。对于子类的声明及其前缀类串接成的类声明,在局部于子类的特性标识符,和局部于其前缀类的特性标识符,二者的未托付出现之间的可能冲突,将凭借对涉及到的局部于子类的特性标识符的适合的系统性变更来避免,但是对应虚拟的标识符不变更。

对于前缀类和子类声明串接成的类声明,它的形式参数列表,由前缀类的形式参数列表,和附加的子类声明的参数列表二者合并而成。它的值部份、规定部份和虚拟部份,是前缀类和子类声明相应各部份的并集。结果的虚拟部份不能包含某个标识符的多于一次出现。

对于类主体,最初的begin和随后的声明被称为“块头部”,从第一个可执行语句直到inner之前的语句被称为“初始运算”,在inner之后的语句和最终的end被称为“复合尾部”。如果前缀类主体是分裂主体,用前缀类的块头部复本替代子类的块头部中的begin,将前缀类的初始运算复本插入到子类的块头部之后,用前缀类的复合尾部复本替代子类的复合尾部中的end。如果前缀类主体不是分裂主体,它被解释为如同将;inner插入到最终的end之前。

Cn是具有前缀序列C1, C2, ……, Cn-1的一个类,这里类Ck (k = 1, 2, ……, n)的下标k叫做前缀层级,X是属于类Cn的一个对象,则X是一个复合对象;非正式的说,串接机制有如下结果:

  • 对象X拥有的特性集合,是在序列C1, C2, ……, Cn中诸类所定义特性集合的并集。在类Ck (1 <= k <= n)中定义的特性,被称为定义在前缀层级k
  • 对象X拥有的“运算规则”,由来自这些类主体的诸语句构成,它们所属的前缀层级遵循规定的次序。来自类Ck的语句,被称为属于对象X的前缀层级k
  • 在对象X的前缀层级k的语句,能访问在对象X的等于或外部于k的前缀层级上定义的它的所有特性,但是如果外部的类Ci (i < k)中存在冲突定义,则导致对应特性不可见而不能直接访问。这些不可见特性仍有访问方式,例如通过使用过程或this Ci
  • 在对象X的前缀层级k的语句,不能立即访问在对象X的内部于k的前缀层级上定义的它的那些特性,除非通过虚拟
  • 在前缀层级k的分裂主体中,符号inner,表示对象X的属于内部于k的前缀层级的运算规则的诸语句,或者在k = n时表示虚设语句。如果序列C1, ……, Cn-1都没有分裂主体,则在对象X的运算规则中诸语句所属的前缀层级遵循递增次序。

用作前缀的系统类,与其前缀链的所有的类一起,被当作声明于最小的包围其字面上出现的块中。因此重新声明可以出现在一个程序的内部的块层级中。

有前缀块的一个实例,是由块前缀生成的一个单一的对象,与这个主体块的实例,串接而成一个复合对象,其串接定义采用类似于类的串接规则。块前缀的类的形式参数,按块前缀的实际参数所示初始化。对有前缀块有两个限制:一个类如果在其中通过使用this来引用类自身,则它作为块前缀是非法的。块前缀的类标识符所提及的类,必须局部于最小的包围这个有前缀块的块。

对象引用

当属于各种类的很多对象,作为同一个程序的各个部份而共存的时候,需要能够对个体对象指定名字,为此介入了叫做“引用”的新的基本类型;还要能相互关联对象,比如通过二叉树和各种其他类型的列表结构。Simula 67将循环双向链表叫做“集合”,它将集合类Simset作为“标准系统类”介入为环境类的一部份。

对于某个类的一个对象,有一个唯一的关联的“对象引用”标定这个对象,并且对于任何类C,都有一个关联的对象引用类型ref(C)。这种类型的,被称为由类C限定;它的值,要么是一个对象,要么是表示“没有对象”的特殊值none

限定(qualification)将值的范围,限制至包括在限定类中的这些类的对象。不管何种限定,值的范围都包括值none。一个对象引用的限定,是一个类标识符,它因此服从声明的作用域和可见性英语Visibility (disambiguation)规则。限定关联于所提及的类声明所局部于的那个块的一个实例。这蕴涵着特定的关于限定有效性的检查,不能单独在程序正文的基础上进行,因此这种测试必须在程序执行期间完成。

引用是有限定的,蕴涵着一个给定引用所提及的对象,只能属于这个限定中提到的类,或属于这个限定类的子类。如果一个对象引用类型的限定,是另一个对象引用类型的限定类的子类,则前者被称为从属于后者。例如:

ref(Order) next, previous;

对象表达式是获得对一个对象的引用的一种规则。对象表达式的值,是被引用的对象或none运算符:-读作“指称”(denote),它指示将一个引用指派(assign)到处在“引用指派符”左侧的引用类型变量过程标识符。例如:

next :- new Plate(50);
previous :- next;
next :- none;

而运算符:=读作“成为”(become),指示将一个,指派到处在赋值符左侧的值类型的变量或过程标识符,或者将一个文本值,指派到在赋值符左侧者所引用的那个文本(frame)。

远程访问

一个对象的一个特定特性,由下列三项信息来完全标定(identify):

  1. 这个对象
  2. 外部于或等于这个对象的类的一个
  3. 在这个类中或在属于它的前缀序列的任何类中定义的一个特性标识符

对于任何特性标定,第二项类信息都是在字面上定义的,从而形成静态绑定[28]。属于其他对象的特性,可以要么单个的通过“远程标识符”(“点表示法”),要么成组的通过“连接”(connection)机制,从其外面“远程访问”来引用和使用。

远程标识符的形式为:简单对象表示式.特性标识符,它可用作简单变量、数组变量、函数指定式(designator)和过程语句中的标识符,这种点表示法给出对信息的单独片段的访问,例如:

if next.number >= previous.number then ……;

它将名为next的类Order的对象的特性number,比较于名为previous的类Order的对象的特性number

“成组访问”可通过连接语句inspect来完成,例如:

inspect next when Plate do begin …… end;
inspect new Plate(50) do begin …… end;

在这里的beginend之间的语句中,next引用的对象所属的Plate类的所有信息片段,都可以直接提及。

do后面的语句,如果是块以外的一个语句,它表现得也如同一个块。连接语句进而表现得如同被叫做“连接块”的第二个虚构(fictitious)的块所包围。连接机制的用途,是为在连接块中的特定特性标定,提供对象信息和在when子句中的类信息的隐式定义。

设对象表达式求值为X,在连接块执行期间,称对象X为“被连接上”了。连接块有一个关联的“块限定”,如果连接块有when子句,它是前面的类标识符,如果没有when子句,它是前面的对象表达式的限定。

虚过程

虚拟(virtual)量,是在类声明中由virtual:前导的,它有双重用途:

  • 允许在一个对象的一个前缀层级上,访问在内部前缀层级上声明的特性。
  • 允许在一个前缀层级上的特性重新定义,在外部前缀层级上有效。

不同于一般的特性标定,虚过程形成“半动态绑定”[8],在某种意义上是类似传名调用的机制[27]。这种机制不同于Smalltalk 80面向对象编程语言中,上溯子类至超类的继承链来找到最近的方法实现的那种动态绑定

一个对象的一个虚拟量,要么是“无匹配的”的,要么被标定为具有一个“匹配”特性,它是在这个虚拟量的前缀层级上或在内部前缀层级上声明的,其标识符重合英语Coincidence(coincide)于虚拟量的标识符的一个特性。匹配特性必须与虚拟特性有相同的种类。在一个给定前缀层级上的匹配量的类型,必须重合或从属于这个虚拟规定的类型,和在任何外部前缀层级上声明的任何匹配量的类型。

在1986年修订的语言标准中,虚过程量可选的可使用procedure <过程标识符> <过程规定>方式来指定,即凭借它的类型,并且如果它有形式参数,则还凭借它们的类型、种类和传送模态。含有过程规定is <过程声明>的虚过程量,只能由有相同类型的过程来匹配,并它与这个过程规定具有相同的过程头部。

例如,下面实现散列表HashTable类,声明了整数类型的虚过程hash并随即实现了它,又定义了要用到这个散列函数hash的查找过程lookup:

class HashTable(n); integer n;
    virtual: integer procedure hash;
begin 
    integer procedure hash(t); text t;
    begin 
        ……
    end hash;
    text array table(0 : n-1); ……
    integer procedure lookup(t, old);
        name old; Boolean old; text t;
    begin
        integer i, istart; Boolean entered;
        i := istart := hash(t);
        while not entered do
        begin
            ……
        end;
        lookup := i;
    end lookup;
end HashTable;
HashTable class ALGOL_hashing;
begin
    integer procedure hash(t); text t;
    begin
        ……
    end hash;
end ALGOL_hashing;

作为子类的ALGOL_hashing类和以HashTable类为前缀的有前缀块,可以用自己的hash过程实现,替代在HashTable类中的实现。在子类的类主体和有前缀块的主体块中,能获得到过程lookup,而它用到的过程hash是这里提供的替代实现。

对象表达式

对象表达式英语Expression (computer science),具有类型ref(限定)。对象表达式的限定规则包括:对象生成式(generator)、局部对象或限定对象,分别由跟随符号newthisqua的标识符的类来限定(qualification)。

对象生成式new C,这里标定了类C,它涉及到属于类C的对象的生成和执行。这个对象是类C的一个新实例。对象生成式的求值构成自如下行动:

  1. 生成这个对象,并且如果这个对象生成式有实际参数,则求值它们,将得到的这些值及或英语And/or引用传送给形式参数。
  2. 控制经过这个对象最初的begin进入其中,它籍此变成在“系附”状态下运行。当所生成的对象执行了detach基本过程变为“脱离”状态,或者经过这个对象最终的end退出而变为“终止”状态,对象生成式的求值完成。

局部对象this C,这里的C是类标识符。如果用在类CC的任何子类的类主体中,或用在其块限定为类CC的一个子类的连接块中,则这个对象表达式是有效的。在一个给定的上下文中,一个局部对象的值,是一个对象或连接到一个对象,它是这个对象表达式在其中有效的、最小的在字面上外在包围的块实例;如果没有这样的块,则这个对象表达式在这个上下文中是非法的。对于一个过程或类主体的实例(上下文),“在字面上外在包围”(textually enclosing)意为包含它的声明。

暂瞬限定X qua C,这里的X表示一个简单的引用表达式,而C是类标识符。设类D是对象X的限定,对于限定对象X qua C,如果类C外部于或等于类D,或者是D的一个子类,则这个对象表达式是合法的,否则是为非法。如果X的值是none,或者是属于外部于C的类的一个对象,则这个对象表达式求值形成一个运行时间错误;在其他情况下,X qua C的值,就是X的值。对一个串接的类对象的暂瞬限定,限制或扩展它的特性通过检视或远程访问的可见性。

对象关系表达式,使用运算符isin,来测试一个对象的类成员关系。关系X is C,在X引用的是属于类C的对象之时,值为true,否则值为false。关系X in C,在X引用的是属于类C的对象,或者X是内部于类C的类之时,值为true,否则值为false

不同于算术关系表达式使用比较运算符=<>,对象引用关系表达式,使用比较运算符===/=,来比较引用,从而区别于对应的被引用的值。两个对象引用XY,在它们都引用相同的对象,或者它们都是none之时,被称为是“同一英语Identity (object-oriented programming)”的。关系X == Y,在这种情况下拥有的值为true,否则值为false。关系X =/= Y的值,是X == Y的值的否定

作用域和可见性

不同于ALGOL 60规定了作用域可见性英语Visibility (disambiguation),在Simula 67中,一个标识符定义及其关联的标识符,在其作用域与可见性之间需要做出区别。

  • 作用域:一个标识符定义的作用域,是它在其中可能有作用的那一部份程序正文。同一个标识符,可以定义在程序的很多地方,因此可以关联于不同的。同一个标识符的这些定义的作用域,因而可能有所重叠,例如在一个标识符在内部块中被重新声明的情况下。
  • 可见性英语Visibility (disambiguation):一个标识符定义,如果它所关联的标识符,在程序的给定点上能够提及到这个定义所声明的,则它被称为在这个点上是可见英语Visibility (disambiguation)的。在给定标识符于此可见的程序正文的一个特定点上,最多只能有一个定义关联于这个标识符,例如在重新声明的情况下,在它们作用域的并集内任何给定点上,只有一个定义是可见的。

一个标识符定义的局部块,是在字面上最近的包围块,即子块、前缀块、过程主体或类主体,还包括围绕for语句的受控语句、过程声明、类声明和连接块等的虚构块。这个标识符和它的定义,被称为局部于这个块。 标识符定义,只在它们的作用域内是可见的。一个特定定义的可见性,在它的作用域内可以受到下列限制:

  • 在一个标识符定义的局部块所包围的某个构造内,出现的具有相同标识符的标识符定义,是这个标识符的重新定义。在它们共同的作用域内,只有最内部的重新定义是可见的。
  • 一个重新定义,出现在类的某个内部前缀层级。
  • 远程访问,可以导致某些标识符定义在检视块或点表示法内变成不可见的。
  • 使用thisqua,可以导致一个或多个重新定义被暂时停止。
  • 在这个标识符定义所局部于的类声明中,这个标识符出现在由hidden及或protected前导的保护部份中。

一个标识符的重新定义,不允许出现在它的局部块的头部,这禁止了在同一个块中出现相同标识符的两个定义。

块实例

一个程序执行英语Execution (computing)的各个组成部份,都是即子块、有前缀块、连接块和类主体的动态实例。一个块实例,被称为“局部于”直接包含它的描述正文的那个块实例。例如一个给定类的实例,局部于包含这个类声明的块实例。最外层块的实例不局部于块实例。

在任何时间,“程序顺序控制”(Program Sequence Control),首字母简写为PSC,参照在一个块实例中当前被执行的程序点;形象的说,PSC“定位”至这个程序点,并被这个块实例所“包含”。进入任何块,都涉及到生成这个块的一个实例,于是PSC进入这个块实例,到达它的第一个可执行语句上。

一个块实例在任何时间,都是处在四种执行状态英语State (computer science)之一:系附(attached)、脱离(detached)、恢复(resumed)或终止(terminated)。没有在字面上给出前缀的任何类,都被定义为前缀了一个虚构(fictitious)的类,它只有一个特性即detach过程,因此所有类对象或有前缀块的实例,都拥有detach过程。在环境ENVIRONMENT中,还定义了用于调度call过程和resume过程。

  • 一个非类块实例,总是在系附状态下,这个实例被称为系附到了导致它生成的那个块上。因此一个过程体的实例,系附到包含对应函数指定式或过程语句的块实例上。非类、非过程块实例,系附到它所局部的块实例。在PSC到达非类块实例的最终的end的时候,PSC返回到紧随导致这个块实例生成的语句或表达式的程序点。
  • 一个对象,最初是在系附状态下,并被称为系附到包含对应对象生成语句的那个块实例上。一个对象,可以通过执行detach过程,进入脱离状态。通过执行call过程,可以使一个脱离状态的对象重新进入系附状态,它借此变为系附到包含这个调用语句的那个块实例上。通过执行resume过程,可以使一个脱离状态的对象进入恢复状态。不使用detachcallresume过程的一个程序执行,是一个简单的系附状态的块的嵌套结构。
  • 当PSC通过一个对象的最终end,或通过goto语句离开它的时候,这个对象进入终止状态。没有块实例系附到一个终止状态的类对象上。终止状态的对象,仍作为一个数据项而存在,它可以通过针对这个对象的包括过程和函数特性的这些特性的远程标定来引用。

每当一个块实例不复存在,局部于或系附于它的所有块实例也不复存在。一个对象的动态作用域,因而受限于它的类声明的作用域。在Simula 67最初标准中曾提到过,语言实现可以使用垃圾回收技术[9],来进一步缩减一个对象的有效寿命的跨度。一个数组声明的动态作用域,可以扩展超出包含这个声明的块实例的作用域,因为传引用调用参数传送模态适用于数组。

准并行系统

一个准并行系统[29],由“构件”(component)构成。构件是块实例组成的嵌套结构,其中标定(identify)这个构件的那个块实例,叫做“构件头领”。在每个系统中,构件中总是有一个被称呼为“主构件”,在Simula 67最初标准中它叫做“主程序”,其他构件叫做“对象构件”。

构件的定序(sequencing),由detachcallresume过程支配。detach过程针对隐式指定的一个对象进行操作,而callresume过程显式指定所操作的对象。

一个准并行系统,由包含局部的类声明的一个子块或有前缀块的任何实例所标定。标定了一个系统的块实例,叫做“系统头领”。一个系统的主构件的头领(head),重合英语Coincidence(coincide)于系统头领。最外层的标定了一个系统的块实例,被称为“最外层系统”。

一个系统的主构件的头领,总是处在系附状态。一个系统的对象构件的头领,确切的就是局部于系统头领的那些脱离状态的或恢复状态的对象。在任何时间,在一个系统的构件之中,有确切的一个构件被称为“生效”(operative)的。不生效的构件,有关联的“重新激活点”,它标定在这个构件被激活(activate)时,要在它那里继续执行的程序点。一个对象构件是生效的,当且仅当这个构件的头领处在恢复状态。

称谓一个块实例P被一个块实例X“动态包围”,当且仅当存在一个块实例序列:(P = Z0), Z1, ……, Zn-1, (Zn = X) (n >= 0),使得对于i = 1, 2, ……, n有着:Zi-1系附到Zi;或者Zi-1是一个恢复状态的对象,它关联的系统头领系附到Zi。终止状态和脱离状态的对象,不被除了自身之外的块实例动态包围。

将当前包含PSC的块实例动态包围起来的块实例链,叫做“运行”。在运行链上的块实例,被称为是“运行”(operating)的,最外层的块实例总是运行的。一个构件只要其头领是运行的它就是运行的。

一个系统如果有一个构件是运行的,它就是运行的;在任何时间,一个系统最多有一个构件是运行的;运行的系统的头领,可以是不运行的。一个运行的构件总是生效的;如果一个系统的生效构件是不运行的,则这个系统也是不运行的。在不运行的系统中的生效的构件,是当这个系统成为不运行的时候在运行的构件,当这个系统再次成为运行的时候它将仍是运行的。

对于一个不生效的构件C,设它的头领是X,如果一个块实例P包含了它的重新激活点,则PX动态包围,并且P除了自身之外不动态包围块实例。由构件头领X动态包围的这个块实例序列,被称为构件C的“重新激活链”。除了X之外,这个链上的所有构件头领,标定了生效而不运行的构件。在构件C成为运行的时候,在它的重新激活链上所有块也成为运行的。

除了系统构件,程序执行还可以包含不属于特定系统的“独立对象构件”。任何这种构件的头领,都是一个脱离状态的对象,它局部于一个类对象或一个过程主体实例,也就是说不局部于某个系统头领。根据定义,独立构件总是不生效的,只能对它执行call过程。

协程和生成器

准并行系统的detach/resume机制,是一种协程[30]。在Simula 67最初标准中,没有恢复状态,并且没有规定call过程,但call过程通常会出现在当时的语言实现中[31]detach/call机制,相当于现在称为生成器的“半协程”,而最初标准中的resume过程可理解为detach过程与call过程的组合[32]。1986年修订的语言标准,增补定义了call过程,新增了恢复状态,并且重新定义了resume过程,它不可再理解为detach过程与call过程的组合。

准并行系统,被创建于进入包含局部的类声明的一个子块或有前缀块的时候,借此生成的实例成为这个新系统的头领。初始时,这个主构件是生效的,并且是这个系统唯一的构件。

建立一个对象构件,要通过针对一个系附状态的对象,执行detach过程,借此PSC返回到这个对象系附到的那个块实例。这个对象进入脱离状态,并成为一个新的不生效的构件的头领,这个构件的重新激活点被定位到紧后于这个detach过程调用。如果这个对象局部于一个系统头领,则这个新的构件成为这个关联的系统的成员

通过detachcall过程,可以形成“半对称定序”,这只涉及到对象构件,而不区分它们是属于系统的构件还是独立的构件。

  • 对于一个不生效的对象构件,通过针对它的脱离状态的头领,执行一个call过程,可以重新激活这个构件,借此PSC移动到它的重新激活点上。这个头领重新进入系附状态,并变成系附到包含这个call过程调用的块实例上。这个构件正式的失去了本身(作为构件)的状态。

通过detachresume过程,可以形成“对称构件定序”,这只涉及到属于一个准并行系统的那些构件。对立于半对称定序中的“调用者”与它的“被调用者”,在对称构件定序中的“恢复者”与它的“被恢复者”,具有完全的对称联系。

  • 对于这个系统的一个不生效的对象构件,通过针对它的脱离状态的头领,执行一个resume过程,可以重新激活这个构件,借此PSC移动到它的重新激活点上;这个构件的头领进入恢复状态,而这个构件变成生效的。这个系统以前生效的构件变成不生效的,并且它重新激活点被定位到紧后于这个resume过程调用;如果这个构件是一个对象构件,则它的头领进入脱离状态。
  • 对于当前生效的对象构件,通过针它的恢复状态的头领,执行一个detach过程调用,这个系统的主构件重获生效的状态,借此PSC移动回到主构件的重新激活点上。以前生效的构件变成不生效的,并且它重新激活点被定位到紧后于这个detach过程调用。这个构件的头领进入脱离状态。

PSC经过一个类对象的最终end的效果,除了使这个对象成为终止状态,而非脱离状态之外,相同于针对这个对象执行detach过程的效果。其结果是它不会得到重新激活点,并且在它已经拥有作为构件头领的状态时,失去这种状态。

程式範例

Hello, World!

Simula 67下的经典Hello, World!範例:

begin
    outtext("Hello, World!");
    outimage;
end;

outtext过程将字符串输出到缓冲区,而outimage过程将缓冲区内容输出到标准文件,二者都定义在输出文件类OutFile中,而它是文件类File的子类。

二叉树

下面例子中的Tree类,是二叉数据结构实现:

class Tree(val); integer val;
begin
    ref(Tree) left, right;
    procedure insert(x); integer x;
    begin
        if x < val then
        begin
            if left == none then
                left :- new Tree(x)
            else 
                left.insert(x)
        end
        else if right == none then
            right :- new Tree(x)
        else
            right.insert(x);
    end insert;  
    ref(Tree) procedure find(x); integer x;
    begin
        if x = val then
            this Tree
        else if x < val then
            (if left == none then
                none
             else 
                left.find(x))
        else if right == none then
            none
        else
            right.find(x);
    end find;
end Tree;

find过程的主体中出现了表达式this Tree,它产生的值所引用的是当前节点。这里通过函数指定式X.find(x),来调用对象Xfind过程,如果X.va​l = x,则这个函数的结果是到X自身的引用值。

协程

Simula 67标准通过如下实例来诠释叫做“准并行系统”的协程机制:

begin comment 系统S1;
    ref(C1) X1;
    class C1;
    begin
        procedure P1;
            detach;
        P1
    end C1;
    ref(C2) X2;
    class C2;
    begin
        procedure P2;
        begin
            detach; 
            ! 可尝试detachresume(X1);
        end P2;
        begin comment 系统S2;
            ref(C3) X3;
            class C3;
            begin 
                detach;
                P2
            end C3;
            X3 :- new C3;
            resume(X3)
        end S2
    end C2;
    X1 :- new C1;
    X2 :- new C2;
    call(X2); ! 可尝试resume(X2);
end S1;

这个例子程序中,有两个准并行系统S1S2。系统S1是对应最外层子块的最外层系统,它包含了两个类声明C1C2。系统S2对应于类C2的类主体中的匿名块,它包含了一个类声明C3

在PSC进入最外层子块开始处,产生系统S1的系统头领,到达第28行的系统S1主构件的第一个可执行语句,开始生成对象X1,进入类C1,PSC到达第7行的类主体的第一个可执行语句,调用了类C1的特性过程P1,进入第6行的过程体的第一个可执行语句,这时的状况是:

[S1] ← (X1) ← (P1) ← PSC

这里方括号表示系统头领,圆括号表示其他种类的块实例,左向箭头表示系附,使用粗体表示这个块实例执行了对象生成式。执行第6行的detach语句后,对象构件头领X1进入脱离状态,保存X1的重新激活点为第6行P1过程体结束,回溯到第28行对象产生式结束,系统S1的对象X1生成完毕,这时的情况是:

[S1] ← PSC
 |
(X1) ← (P1) ← <X1重新激活点>

这里的竖杠将对象构件头领列为一个系统的成员。PSC到达系统S1主构件第29行,开始生成对象X2,进入类C2,PSC到达第17行的类主体中匿名块开始处,进入这个子块产生系统S2的系统头领,PSC到达第24行的系统S2主构件的第一个可执行语句,开始生成对象X3,进入类C3,PSC到达第20行类主体开始处,这时的情况是:

[S1] ← (X2) ← [S2] ← (X3) ← PSC
 |
(X1) ← (P1) ← <X1重新激活点>

执行第21行类主体的第一个可执行语句detach后,对象构件头领X3进入脱离状态,保存X3的重新激活点为第22行,回溯到第24行对象产生式结束,系统S2的对象X3生成完毕,这时的情况是:

[S1] ← (X2) ← [S2] ← PSC
 |             |
 |            (X3) ← <X3重新激活点>
 |
(X1) ← (P1) ← <X1重新激活点>

PSC到达系统S2主构件的第25行,执行resume(X3)语句后,保存系统S2主构件的重新激活点为第26行,对象构件头领X3进入恢复状态,PSC恢复到第22行的对象X3的类主体之中,这时的情况是:

[S1] ← (X2) ← [S2] ← <S2重新激活点>
 |             |
 |            (X3) ← PSC
 |
(X1) ← (P1) ← <X1重新激活点>

这里的下划线指示这个块实例处在恢复状态。执行第22行的类C2的特性过程P2,到达在第14行的过程体的第一个可执行语句,这时的情况如下,并标记为“状况A”:

                                  ! 状况A ;
[S1] ← (X2) ← [S2] ← <S2重新激活点>
 |             |
 |            (X3) ← (P2) ← PSC
 |
(X1) ← (P1) ← <X1重新激活点>

执行在第14行P2过程体的detach语句后,对象构件头领X2进入脱离状态,保存X2的重新激活点为第15行,回溯到第29行对象产生式结束,系统S1的对象X2生成完毕,这时的情况如下,并标记为“状况B”:

                                  !状况B ;
[S1] ← PSC
 |
(X1) ← (P1) ← <X1重新激活点>
 |
(X2) ← [S2] ← <S2重新激活点>
        |
       (X3) ← (P2) ← <X2重新激活点>

注意对象X3仍是系统S2的生效构件,它并没有自己的重新激活点。序列(P2, X3, X2)X2的重新激活链,这里的X3是恢复状态,而它的系统头领S2系附到了X2

PSC进入系统S1主构件第30行,执行call(X2)语句后,对象构件头领X2进入系附状态,PSC恢复为第15行的P2过程体之中,这时的情况重现为前面的“状况A”(S1不加粗体)。

如果将系统S1主构件中第30行改写为一个resume(X2),执行后对象构件头领X2进入恢复状态,保存系统S1主构件的重新激活点为第31行,PSC恢复为第15行的P2过程体之中,会出现如下情况:

[S1] ← <S1重新激活点>
 |
(X1) ← (P1) ← <X1重新激活点>
 |
(X2) ← [S2] ← <S2重新激活点>
        |
       (X3) ← (P2) ← PSC

序列(P2, X3, X2)X2的运行链。

如果将P2过程体中第15行的注释替换为一个detach语句,执行后对象构件头领X2进入脱离状态,保存X2的重新激活点是第16行,PSC恢复到第31行的系统S1主构件之中,这时的情况重现为前面的“状况B”。

如果将P2中第15行的注释替换为一个resume(X1)语句,执行后对象构件头领X2进入脱离状态,对象构件头领X1进入恢复状态,保存X2的重新激活点为第16行,PSC恢复到第6行的P1过程体的过程结束,会出现如下情况:

[S1] ← <S1重新激活点>
 |
(X1) ← (P1) ← PSC
 |
(X2) ← [S2] ← <S2重新激活点>
        |
       (X3) ← (P2) ← <X2重新激活点>

抽象类

下面例子中,定义了一个字形Glyph,它是抽象基础类,并且有二个实现子类字符Char英语Line (text file)Line

begin
    class Glyph;
        virtual: procedure print is procedure print;
    begin
    end;
    Glyph class Char(c);
        character c;
    begin
        procedure print;
            outchar(c);
    end;
    Glyph class Line(cs);
        ref(Glyph) array cs;
    begin
        procedure print;
        begin
            integer i;
            for i := 1 step 1 until upperbound(cs, 1) do
                cs(i).print;
            outimage;
        end;
    end;
    ref(Glyph) rg;
    ref(Glyph) array rgs(1 : 4);
    rgs(1) :- new Char('A');
    rgs(2) :- new Char('b');
    rgs(3) :- new Char('b');
    rgs(4) :- new Char('a');
    rg :- new Line(rgs);
    rg.print;
end;

这里的虚过程量print具有过程规定is procedure print,它匹配既没有形式参数也没有结果值的print过程,如果不加以这种过程规定,则它可以匹配具有任何形式参数和任何结果值的print过程。在Simula 67中,没有不可以实例化带有纯虚过程类的特定限制,因而缺乏真正的抽象类的概念,所有类都可以被实例化,但是调用纯虚过程会产生运行时间错误

模拟器

在下面的离散事件模拟例子中,Sam、Sally和Andy正在逛商店买衣服,他们必须共享一个试衣间。他们每人只能浏览商店大约12分钟,并接着独占的使用试衣间大约3分钟,每个行动都服从正态分布,他们的试衣间经历被模拟如下:

Simulation
begin
    class FittingRoom;
    begin
        ref(Head) door;
        Boolean inUse;
        procedure request;
        begin
            if inUse then
            begin
                wait(door);
                door.first.out;
            end;
            inUse := true;
        end;
        procedure leave;
        begin
            inUse := false;
            activate door.first;
        end;
        door :- new Head;
    end;
    procedure report(message); text message;
    begin
        outfix(time, 2, 0);
        outtext(": " & message);
        outimage;
    end;
    Process class Person(pname); text pname;
    begin
        while true do
        begin
            hold(normal(12, 4, u));
            report(pname & " 要求用试衣间");
            fittingroom1.request;
            report(pname & " 已进入试衣间");
            hold(Normal(3, 1, u));
            fittingroom1.leave;
            report(pname & " 已离开试衣间");
        end;
    end;
    integer u;
    ref(FittingRoom) fittingRoom1;
    fittingRoom1 :- new FittingRoom;
    activate new Person("Sam");
    activate new Person("Sally");
    activate new Person("Andy");
    hold(100);
end;

主程序是前缀着模拟器Simulation的有前缀块。模拟器类可在任何块上使用,而且模拟器甚至可以嵌套,比如在模拟某人做模拟的时候[33]。时间过程time、等待过程wait、保持过程hold和激活过程ACTIVAT,定义在Simulation类之中。

进程类ProcessSimulation类的嵌套类。激活语句activate,只有处在Simulation类所包含的类的对象之中,或处在其前缀部份是这种对象的有前缀块之中,才是有效的。激活语句的作用,被定义为得到调用激活过程ACTIVAT的那种效果。

集合类SimsetSimulation类的前缀类,其中定义了三个嵌套类:表示集合整体Head类,表示集合元素Link类,和二者的前缀类链表Linkage。首位过程first,定义在Head类中;退出过程out和在等待过程wait中用到的进入过程into,定义在Link类中。

在试衣间类FittingRoom中,为了让人们排队等待访问试衣间,使用了门对象door,它是队列类即Head类的一个对象。试衣间类定义了两个过程:要求过程request:如果试衣间中正有人使用,则他必须等待于门队列之中,即wait(door);当他可以使用试衣间之时,相应的从门队列中移除自己,即door.first.out。离开过程leave:如果门队列中有人等待的话,放行其中第一个人,即activate door.first

个人类PersonProcess类的子类,它的行动是浏览商店和在试衣间试衣,使用保持过程hold来描述其所用时间,并调用对应的试衣间对象的过程来要求和离开试衣间。正态分布随机抽签过程normal,定义在环境类ENVIRONMENT中,它的最后的参数,必须是指定一个伪随机数串流的一个整数。

主程序是进程类Process的子类MAIN_PROGRAM类的对象。它建立FittingRoom类的实例fittingRoom1对象,接着建立并激活Person类的三个对象,从而将他们三人放置入事件队列之中,主程序在终止前保持100分钟的模拟时间。

参见

注释

延伸阅读

外部链接

Loading related searches...

Wikiwand - on

Seamless Wikipedia browsing. On steroids.