跳至內容

Modula-3

維基百科,自由的百科全書
Modula-3
編程範型指令式, 過程式, 結構化, 模塊化, 並發
語言家族Wirth Modula
設計者Luca Cardelli英語Luca Cardelli, James Donahue, Lucille Glassman, Mick Jordan, Bill Kalsow, Greg Nelson英語Greg Nelson (computer scientist)[1]
實作者DEC, Olivetti, elego Software Solutions GmbH
面市時間1988年,​36年前​(1988
型態系統強類型, 靜態, 安全或在不安全時顯式安全隔離
作用域詞法
系統平台IA-32, x86-64, PowerPC, SPARC
作業系統跨平台: FreeBSD, Linux, Darwin, SunOS
網站www.modula3.org
主要實作產品
SRC英語DEC Systems Research Center Modula-3, CM3[2], PM3[3], EZM3[4], M3/PC Klagenfurt[5]
啟發語言
ALGOL, Euclid英語Euclid (programming language), Mesa, Modula-2, Modula-2+英語Modula-2+, Oberon, Object Pascal
影響語言
C#, Java, Nim[6], Python[7], Baby Modula-3英語Baby Modula-3[8]

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

Modula-3的主要特點,是保持系統程式語言的強力,同時具有簡單性和安全性。Modula-3意圖延續Pascal類型安全和Modula-2的模塊化編程傳統,同時為實際編程引入新構造。特別是Modula-3添加了如下支持:例外處理關鍵字參數與預設參數值、有跟蹤的引用垃圾回收物件導向、不透明類型及其披露、泛型多執行緒和不安全代碼顯式標記。Modula-3的設計目標,是以非常基本的形式,實現指令式程式語言的大部份重要的現代特徵。因此省略了涉嫌危險和複雜的特徵,如多重繼承運算符重載

發展歷史

[編輯]

Modula-3項目始於1986年11月,當時莫里斯·威爾克斯尼克勞斯·維爾特寫信,提出了一些關於Modula新版本的想法。威爾克斯在此之前一直在DEC工作,他回到英格蘭並加入了Olivetti的研究策略委員會。Wirth已經轉移到了Oberon,但Wilkes的團隊在Modula名下繼續開發沒有任何問題。語言定義於1988年8月完成,並於1989年11月更新了版本[10]。DEC和Olivetti的編譯器很快就出現了,隨後又有了第三方實現。

它的設計受到SRC英語DEC Systems Research CenterAcorn計算機研究中心(ARC,在Olivetti收購Acorn之後叫做ORC)在Modula-2+英語Modula-2+語言上的工作的很大影響[11]Modula-2+英語Modula-2+是編寫DEC Firefly英語DEC Firefly多處理器VAX工作站的作業系統的語言[12][13],還是在基於ARMAcorn Archimedes英語Acorn Archimedes系列計算機的ARX作業系統英語ARX (operating system)項目中,ARC編寫用於Acorn C和模塊執行庫(CAMEL)的Acorn編譯器的語言[14]。正如修訂的Modula-3報告所述[14],該語言演化自MesaModula-2、Cedar和Modula-2+英語Modula-2+,還受到其他語言的影響如:Object PascalOberonEuclid英語Euclid (programming language)

在20世紀90年代,Modula-3作為一種教學語言獲得了可觀的傳播[15]華盛頓大學在1994年發行的微內核作業系統SPIN英語SPIN (operating system)是用Modula-3開發的[16],但它從未廣泛用於工業用途。造成這種情況的原因,可能是Modula-3的關鍵支持者DEC的消亡,特別是在1998年DEC被出售給康柏之前就不再有效地維護它了。無論如何,儘管Modula-3有著簡單性和強大功能,似乎當時對有限實現了物件導向編程的過程式編譯語言的需求很少。

Critical Mass公司曾在一段時間內提供了一個名為CM3的商業編譯器,它由以前DEC SRC的在DEC被出售給Compaq之前雇用的一個主要實現者維護,和叫做Reactor的一個集成開發環境以及可擴展的Java虛擬機(以二進制和源格式許可發行並可用Reactor來構建)。但該公司在2000年停止了活動,並將其產品的一些原始碼提供給了elego軟體解決方案有限公司。

基本上,Modula-3的唯一企業支持者是elego軟體解決方案有限公司,它繼承了Critical Mass的資源,並且已經以原始碼和二進制形式發布了幾個版本的CM3系統。Reactor IDE在幾年後才開源發布,新的名稱是CM3-IDE。2002年3月,elego還接管了此前由蒙特婁工程學院維護的另一個活躍的Modula-3發行版PM3的存儲庫,在此後幾年間以HM3名義進行了持續改進,直至最終被淘汰。

Modula-3是文檔記錄了語言特徵演化的少見語言之一,在《用Modula-3系統編程》中[17],收錄了設計者對四個設計要點的討論:結構等價與名稱等價、子類型規則、泛型模塊和參數模態。Modula-3現在主要是在大學中的比較程式語言課程中教授,其教科書已絕版。

程序示例

[編輯]

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

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

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

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

建議這個模塊所在的文件的名字與實際模塊名字相同,當它們不同時編譯器只是發出一個警告。

語言設計理念

[編輯]

Modula-3委員會將Modula-3設計為ALGOL語言家族的現代代表,他們認為:

  • 始自BCPLC語言家族,其優美在於以適度的代價得到了相對彙編語言的巨大飛躍,但它們接近於目標機器的低層編程模型英語Programming model是天然有危險性的;其中的衍生語言C++,通過增加對象豐富了C,但它放棄了C的最佳優點簡單性,卻沒有消除它最差缺點即低層編程模型。
  • 另一個極端是LISP語言家族,它們有著混合了lambda演算配對函數理論的編程模型英語Programming model,但它們趨於難以高效實現,因為在編程模型中值的統一對待,招致了所有的值都由指針統一表示的運行時間系統;這個家族的語言的良好實現避免了多數代價而能用於系統編程,但它們普遍秉性仍舊嚮往堆分配而非棧分配,這種運行時間系統經常將它們孤立於封閉環境中,而不能容納用其他語言寫的程序。
  • ALGOL語言家族處在這兩個極端之間,它的現代代表包括PascalAdaModula-2等,這些語言擁有的編程模型反映了隨機存取機在工程上的約束,隱藏了任何特定於機器的細節。它們放棄了LISP家族的優美和數學對稱性,使得無須特殊技巧就可能高效的實現,並且避免了C家族的多數危險和依賴機器的特徵。

ALGOL家族語言都有強類型系統,其基本想法是把值空間劃分成類型,限制變量持有單一類型的值,並且限制運算應用於固定類型的運算數。這個語言家族的趨勢,在1960年代是朝向控制流程和資料結構特徵,在1970年代是朝向信息隱藏英語Information hiding特徵如接口、不透明類型和泛型,在1980年代是採納來自LISP家族和始自BCPLC家族的仔細挑選的技術。

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家族傳統的強類型,並且為了使類型系統儘可能的統一,採用了如下原則:

  • 首先,類型或目標類型確定是沒有歧義的,所有的表達式的類型由它的子表達式來確定,而非由它的使用來確定。
  • 其次,沒有自動轉換
  • 第三,可賦值性和類型兼容性,是依據單一的在語法上用算符<:指定的子類型關係來定義的,它具有如下性質:如果TU子類型,則所有的T的成員都是U成員,子類型關係是自反傳遞的;子類型關係是處理具有繼承對象所需要的,但也能用於確定常規類型的兼容性規則。

一個典型的不安全運行時間錯誤,是釋放仍可由活躍引用(懸空指針)觸及的資料結構。避免這個問題的唯一可靠方式,是不再觸及存儲的自動釋放,或稱為垃圾收集。Modula-3因而提供了有跟蹤的引用,它類似於Modula-2指針,除了它們所指向的存儲被保存在有跟蹤的堆之中之外,在這裡它們將在到它們的引用都消失時自動釋放。垃圾收集的另一個巨大利益是簡化了接口,沒有垃圾收集,接口就必須規定客戶或實現是否有責任釋放每個分配的引用,和在什麼條件下可以安全的去這麼做。

例外是一次退出多層作用域控制結構。一個例外被重複的發起退出當前活躍的作用域,直到找到給這個例外的處理器(handler),並將控制權移交給這個處理器,如果沒有處理器,則計算以某種依賴於系統的方式終止,比如進入調試器。在Modula-3中,EXITRETURN語句的語義,分別使用退出例外exit-exception和返回例外return-exception來定義。Modula-3的設計者認為語言實現,除了這種返回例外和退出例外之外,更應當以各種例外為代價,加速正常的結果產出;為每個引發的例外花費一千條指令,來為每個過程節約一條指令都是合理的。

塊與聲明

[編輯]

聲明介入一個常量、類型、變量、例外或過程的一個名字。這個名字的作用域是包含這個聲明的。一個塊可以出現為一個模塊或過程的本體,或一個塊語句PascalModula-2沒有塊語句[18]。一個塊有如下形式:

    Decls
BEGIN
    S
END

這裡的Decls是成序列的聲明,而S是一個語句,經常是順序複合語句即成序列的語句,它是這個塊的執行部份。這裡的DeclsS縮進上對齊,是因為在Pascal語言家族中,可能含有嵌套過程聲明的聲明序列的位置相較於ALGOL 60,從緊隨在關鍵字BEGIN之後轉移到了緊靠在其前面。一個塊的聲明可以介入一個名字最多一次,然而一個名字可以在嵌套的塊中重新聲明。在塊中這些聲明的次序,除了確定變量的初始化次序之外無關緊要。

一個Modula-3程序規定了一個計算,它作用在一序列的叫做地點(location)的數字元件之上。變量是地點的一個集合,它依據這個變量的類型所確定的約定,表示一個數學上的英語Value (mathematics)。如果一個值可以由類型T的某個變量來表示,則稱謂這個值是T的一個成員,並且T包含這個值。

聲明為一個變量類型過程常量英語Constant (computer programming)例外指定一個被稱為標識符符號英語Symbol (programming)作為名字。一個聲明應用於其上的程序區域,叫做這個聲明的作用域。作用域是可以嵌套的。一個標識符的含義,由這個標識符在其中聲明的最小的包圍作用域來確定。

類型聲明有如下形式: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的簡寫。

常量英語Constant (computer programming)聲明有如下形式:CONST id: T = C,這裡的id是一個標識符,T是一個類型,而C是一個常量表達式。它聲明id為具有類型T和值C的一個常量。: T可以省略,在這種情況下id的類型是C的類型。如果T存在,則它必須包含C

值與類型

[編輯]

Modula-3的類型系統使用結構等價英語Structural type system,而非Modula-2名稱等價英語Nominal type system即類型構造子的每次出現產生一個新類型。在結構等價中,在如果兩個類型的定義在展開的時候,也就是在所有常量表達式都被替換為它們的,並且所有類型名字都被替代為它們的定義的時候,變成相同的,則這兩個類型是相同的。在遞歸數據類型的情況下,這個展開是部份展開的無窮極限

有三種序數類型:枚舉子範圍整數類型,預定義了如下序數類型:INTEGERLONGINTCARDINAL(如同子範圍[0..LAST(INTEGER)])、BOOLEAN(枚舉{FALSE, TRUE})和CHAR(包含至少256個元素表示ISO-Latin-1代碼的枚舉)。整數枚舉元素合起來叫做序數。序數值v如果是整數或擴展精度整數,則v的基礎類型分別是INTEGERLONGINT,否則v的基礎類型是包含它的那個唯一性的枚舉類型。兩個序數如果有相同的值則二者相等。

有三種浮點類型:REALLONGREALEXTENDED,這些類型的屬性規定在對應的必要接口中。兩個浮點數如果定義它們的底層實現相同則二者相等。

一個引用值要麼是NIL,要麼是一個叫做所引用者(referent)的變量的地址。預定義了如下引用類型:REFANY,它包含所有的有跟蹤的引用,ADDRESS,它包含所有的無跟蹤的引用,和NULL,它只包含NIL。Modula-3支持在運行時間的數據分配。分配操作有如下形式:NEW(T, ...),這裡的T是除了預定義的REFANYADDRESSNULL之外的引用類型。兩個引用如果尋址相同的地點則二者相等。

記錄是叫做這個記錄的欄位的命名變量的一個序列,記錄類型的聲明有如下形式:TYPE T = RECORD FieldList END,在這裡的類型表達式中,算子英語Operator (computer programming)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,它是沒有欄位或方法的無跟蹤的對象類型。

不透明類型英語Opaque data type是一個名字,它指示某個給定引用類型的未知子類型。預定義了兩個不透明類型英語Opaque data typeTEXT <: REFANYMUTEX <: ROOT,它們分別表示文本互斥信號量,它們的屬性分別規定於必要接口TextThread之中。

數組是叫做這個數組的元素的組成變量的有索引的搜集。有兩種數組類型,固定的和開放的。固定數組的長度是在編譯時間確定的,開放數組的長度是在運行時間分配確定的,這個長度此後不能改變。兩個數組如果有相同的長度,並且對應的元素都相等則二者相等。

包裝類型的變量出現在記錄、對象或數組中,占據指定數量的位元,並毗鄰於前導的欄位或元素緊壓打包。包裝類型的聲明有如下形式:TYPE T = BITS n FOR Base,這裡的Base是一個基礎類型,而n是整數取值的常量表達式。

集合是取自某個序數類型的搜集。集合類型聲明有如下形式:TYPE T = SET OF Base,這裡的基礎類型Base是一個序數類型。兩個集合如果有相同的元素則二者相等。

不包含值的一個類型是空的,例如[1..0]是一個空類型。空類型可以用來建造非空類型,比如SET OF [1..0]。聲明一個空類型的一個變量是靜態錯誤。

所有的表達式英語Expression (computer science)都有一個唯一的類型,但是一個可以是很多類型的一個成員,例如值6[0..9]INTEGER二者的成員,因此論述「一個值的類型」將會是有歧義的。短語「x的類型」,意味著「表達式x的類型」;然而短語「xT的成員」,意味著「x的值是T的成員」。但在有一種情況下,一個值可以稱為有著一個類型:所有的對象或有跟蹤的引用值,包括了一個類型代碼,它叫做這個引用值的分配類型。分配類型通過TYPECASE語句來測試。

語言規定提供了類型運算BITSIZE()BYTESIZE()ADRSIZE(),分別返回變量x或者類型T的那些變量的位元數目、8位字節的數目和可尋址地點的數目。在所有情況下,x必須是指定式,而T必須不是開放數組類型,指定式x只在它的類型是開放數組的時候求值。

序數類型

[編輯]

枚舉類型聲明有如下形式:TYPE T = {id_1, id_2, ..., id_n},這裡的id是各不相同的標識符,類型Tn個值的有序集合,表達式T.id_i指示這個類型在遞增次序下的第i個值,空枚舉{ }是允許的。

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

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

TYPE
    T1 = {A, B, C};
    T2 = {A, B, C};
    U1 = [T1.A..T1.C];
    U2 = [T1.A..T2.C];  (* sic *) 
    V = {A, B}

這裡的T1T2是相同類型,因為它們有相同的展開定義。U1U2也是相同的類型,因為T1.C = T2.C。類型T1U1卻是不同的,儘管它們包含相同的值,但是T1的展開定義是枚舉,而U1的展開定義是子範圍。類型V是第三個類型,它的值V.AV.B無關於值T1.AT1.B

語言規定提供了支持序數類型的一些類型運算:對於序數類型TNUMBER(T)返回在T中元素的數目,FIRST(T)返回T的最小值,LAST(T)返回T的最大值。ORD(T.id)將一個枚舉的元素T.id,轉換成表示它在枚舉次序中所處位置的整數,如果元素的類型是枚舉T的子範圍,結果是元素在T內的位置,而非在子範圍內。VAL(i, T)將表示在枚舉類型T中位置的整數i,轉換成在這個枚舉中此位置上的那個元素,如果T是枚舉類型的子範圍,結果是在源出枚舉類型的位置i上的元素,而非在子範圍內。如果n是類型T的一個整數,則有平凡的:ORD(n) = VAL(n, T) = n

數組類型

[編輯]

固定數組類型聲明有如下形式:TYPE T = ARRAY Index OF Element,這裡的索引類型Index是序數類型,而元素類型Element是除了開放數組之外的任何類型。開放數組類型聲明有如下形式:TYPE T = ARRAY OF Element,這裡的元素類型Element是任何類型。

數組類型T的值,是元素類型為Element的數組;對於固定數組類型,這個數組的長度是索引類型Index的元素數目;對於開放數組類型,這個數組的長度是任意的;開放數組的索引類型是INTEGER子範圍[0..n-1],這裡的n是這個數組的長度。開放數組類型只能用作形式參數的類型,引用類型的所引用者,另一個開放數組的元素類型,或作為數組構造子中的類型。

多維數組的形狀是它每一維的長度的序列。更精確的說,一個數組的形狀是它的長度跟隨著任何它的元素的形狀,非數組的形狀是空序列。數組如果有相同的類型和形狀,則它們是可賦值的。如果賦值的要麼來源要麼目標是開放數組,則需要運行時間檢查。形如ARRAY Index_1, ..., Index_n OF Element的表達式,是ARRAY Index_1 OF ... OF ARRAY Index_n OF Element的簡寫。

如果a有數組類型T,則a[i]指示a的一個指定元素,它在數組中的位置對應於i在索引類型中位置。形如a[i_1, ..., i_n]的表達式,是a[i_1]...[i_n]的簡寫。索引的解釋由一個數組的類型確定,而非它的;數組賦值改變數組變量的,而非它的類型。例如:

VAR
    a := ARRAY [1..3] OF REAL {1.0, 2.0, 3.0};
    b: ARRAY [-1..1] OF REAL := a;

這裡在ab做元素值變更之前,a = bTRUE,儘管a[1] = 1.0b[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的分配類型的這個代碼。如果TREFANY,或者不是對象類型或有跟蹤的引用類型,則是一個靜態錯誤。類型運算ISTYPE(x, T),測試x是否為T的成員,這裡的T必須是對象類型或有跟蹤的引用類型,而x必須可賦值給T

表達式

[編輯]

表達式英語Expression (computer science)規定產生一個值或變量的一個計算。所有的表達式都有一個靜態確定的類型,它包含這個表達式可以產生的所有的值。在語法上,一個表達式要麼是一個運算數,要麼是應用於自身也是表達式的實際參數的一個運算。運算數是標識符文字英語Literal (computer programming)類型。通過遞歸的求值一個運算的實際參數並應用這個運算,來求值一個表達式。實際參數的求值次序,對除了短路求值ANDOR之外的所有運算都是未定義的。

對一個過程的調用,在這個過程是返回一個結果的函數過程的時候,是稱為函數應用的一個表達式,這個表達式的類型是過程的結果類型。

Modula-3語言規定提供了前綴算術運算:+-,中綴算術運算:+-*/DIVMOD,數值函數運算:ABS(x)FLOAT(x, T)FLOOR(x)CEILING(x)ROUND(r)TRUNC(r)MAX(x, y)MIN(x, y),關係運算:=#<=>=><,邏輯運算:NOT pp AND qp OR q,文本串接運算:a & b,內存分配運算:NEW(T, ...),集合運算:併集+差集-交集*對稱差集/隸屬關係測試e IN s,它在序數e是集合s的元素的時候返回TRUE

表達式可建構自如下文字英語Literal (computer programming)整數文字英語Integer literal、實數文字、字符文字英語Character literal文本文字英語String literal。集合、數組和記錄有構造子表達式:

  • S{e_1, ..., e_n},這裡的S是一個集合類型,而這些e是表達式或lo..hi形式的範圍。這個構造子指示類型為S的一個值,它包含列出的那些值和在列出的範圍中的那些值,這些elohi必須可賦值給類型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的一個值,它的欄位值由重寫後的這些綁定來指定。

常量英語Constant (computer programming)表達式是一般性的表達式類的子集,所要求的限制使得可以靜態的求值這個表達式。在常量表達式中,除了NEW()、解引用(顯式的或隱式的)、SUBARRAY()TYPECODE()NARROW()ISTYPE()ADR()LOOPHOLE()之外的所有運算都是合法的,唯獨可運用的過程,是在提供無符號運算的Word接口中的函數。變量在常量表達式中,只能作為給FIRST()LAST()NUMBER()BITSIZE()BYTESIZE()ADRSIZE()的實際參數出現,並且這個變量必須不是開放數組類型的。文字和頂層過程常量在常量表達式中是合法的。

指定式

[編輯]

產生變量的表達式叫做指定式(designator)。常量表達式永遠不是指定式。指定式的類型是它產生的變量的類型。一個指定式依賴於上下文,可以指示要麼一個變量,要麼這個變量的值。比如,它作為賦值的左值時是變量,作為右值時是這個變量的值。一些指定式是只讀的,它們不能用在可能改變這個變量的值的上下文中,並非只讀的指定式就是可寫的指定式。

一個標識符是可寫的指定式的條件為,它被聲明為變量,是VARVALUE形式參數,TYPECASETRY-EXCEPT語句的局部變量,或綁定到可寫指定式的WITH局部變量。一個標識符是只讀的指定式的條件為,它是READONLY形式參數,FOR語句的局部變量,或綁定到非指定式或只讀的指定式的WITH局部變量。只有如下運算可以產生指定式:

  • r^,這個運算叫做解引用英語Dereference operator(dereferencing),它表示r的所引用者。表達式r^總是可寫的指定式。如果r的類型是REFANYADDRESSNULL、對象類型或不透明類型,則是一個靜態錯誤;如果rNIL,則是一個必查的運行時間錯誤。
  • 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.fo.fI.xo.mT.mE.id,這種點表示法運算叫選取(selection)。
    • r.f表示記錄r的指名欄位f,表達式r.f是否為指定式進而是否可寫同於r。如果r是到記錄的引用,則這裡有隱含的解引用,即r.fr^.f的簡寫。
    • o.f表示對象o的指名欄位f,表達式o.f是可寫的指定式。
    • I.x表示導入的接口I的指名實體x,如果x被聲明為變量,則表達式I.x是指定式並且總是可寫的。
    • o.m表示對象o的指名方法mo.m不產生指定式。
    • T.m表示對象類型T的指名方法mT.m不產生指定式。
    • E.id表示枚舉類型E的值idE.id不產生指定式。
  • SUBARRAY(a, from, for),它產生數組a的越過前from個元素並包含下for個元素的子數組。SUBARRAY()不複製數組,它是否為指定式進而是否可寫同於a。如果a是多維數組,SUBARRAY()只應用於頂層數組。如果from + for超過了NUMBER(a),則是一個必查的運行時間錯誤。

子類型規則

[編輯]

使用T <: U指示TU子類型,也就是UT的超類型。子類型規則是:如果T <: U,則類型T的所有值,都是類型U的值,反過來則不成立。

對於序數類型TUT <: U的條件為,它們擁有相同的基礎類型,並且所有的T的成員都是U的成員。就是說序數類型上的子類型,反映了在值集合上的子集關係。

對於數組類型,一個數組類型A是一個數組類型B的子類型的條件為,它們有相同的最終元素類型,相同的維度數,並且對於每個維度,要麼二者都是開放的,要麼A是固定的而B是開放的,要麼它們都是固定的並有著相同的大小。

對於過程類型,T <: U的條件為,它們除了形式參數名字、預設值和RAISES集合之外是相同的,並且TRAISES集合包含在URAISES集合中。NIL是所有的過程類型的一個成員。

對於包裝類型,BITS n FOR TT有相同的值,也就是說:BITS n FOR T <: T並且T <: BITS n FOR T

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

對於引用類型有:

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

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

對於對象類型有:

ROOT <: REFANY
UNTRACED ROOT <: ADDRESS
NULL <: T OBJECT ... END <: T

就是說,所有對象都是引用,NIL是所有的對象類型的一個成員,而所有的子類型都包括在它的超類型之中。第三個規則也適用於加銘牌的類型。

對於所有的T,都有T <: T。對於所有的T, U, VT <: U並且U <: V蘊涵T <: V。就是說<:自反傳遞的。但是T <: U並且U <: T,不蘊涵TU是相同的,因為子類型關係不受形式參數名字、預設值和包裝的影響。

可賦值性

[編輯]

一個類型T賦值(assignable)給類型U的條件是:

  • T <: U,或者
  • U <: T並且T是一個數組或除了ADDRESS之外的一個引用類型(這個限制在不安全模塊中解除),或者
  • TU是至少有一個共同成員的序數類型。

在第一種情況下,沒有運行時間錯誤是有可能的。在第二種情況下,允許賦值一個REFANY到一個REF T,還允許賦值一個ARRAY OF T到一個ARRAY I OF T,這裡分別要求對引用的類型和數組長度的運行時間檢查;可以使用類型運算NARROW(x, T): T,將對象類型或有跟蹤的引用類型的超類型變量當作其子類型的成員,這時需要運行時間檢查,Modula-2+英語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的值,ve的求值次序是未定義的,但e會在v更新之前求值。特別是,如果ve是有交疊的子數組,以沒有元素在被用作來源之前被用作目標的方式進行賦值。

順序複合語句有如下形式:S_1; S_2,它在Modula-2中稱為「語句序列」。一些編程者使用分號作為語句終結符(terminator),另一些編程者將它用作分隔符(separator);Modula-3允許這兩種風格,語言報告將其用作分隔符。

語句有如下形式:Decls BEGIN S END,這裡的Decls是一序列的聲明,S是一個語句。塊介入在Decls中聲明的常量、類型、變量和過程,並接著執行S。聲明的名字的作用域是這個塊。

選擇控制結構

[編輯]

Modula-3提供了選擇控制結構IFCASE語句。IF語句有如下形式:

IF B_1 THEN S_1
ELSIF B_2 THEN S_2
... 
ELSIF B_n THEN S_n
ELSE S_0
END

這裡的這些B是布爾表達式,而這些S是語句。ELSE S_0和每個ELSIF B_i THEN S_i都是可選的。IF語句依次求值B,直到某個B_i求值為TRUE,則接著執行S_i。如果沒有表達式求值為TRUE,並且ELSE S_0存在,則執行S_0。如果沒有表達式求值為TRUE,並且ELSE S_0缺席,則IF語句是無操作的(no-op)。Modula-3的CASE語句,不同於Modula-2的同名語句,它有如下形式:

CASE Expr OF  
    L_1 => S_1 
  | ...
  | L_n => S_n
    ELSE S_0 
END

這裡的Expr是類型為序數類型的一個表達式,而每個L是一個列表,這個列表構成自常量表達式,或用e_1..e_2指示的常量表達式的範圍,它表示從e_1e_2含二者的值。如果e_1超出了e_2,這個範圍為空。如果兩個L表示的集合有交疊,或者任何常量表達式不是類型Expr的一個成員,則是一個靜態錯誤。ELSE S_0是可選的。

CASE語句求值Expr。如果結果的值在任何L_i之中,則執行S_i。如果這個值不在任何L_i之中,並且ELSE S_0存在,則執行它。如果這個值不在任何L_i中,並且ELSE S_0缺席,則發生一個必查的運行時間錯誤。一些編程者使用豎槓作為初始符(initiator),另一些編程者將它用作分隔符。Modula-3允許這兩種風格,語言報告將其用作分隔符。

分配類型測試

[編輯]

TYPECASE語句有如下形式:

TYPECASE Expr OF
    T_1 (v_1) => S_1
  | ...
  | T_n (v_n) => S_n
    ELSE S_0
END

這裡的Expr是類型為引用類型的一個表達式,這些S是語句,這些T是引用類型,而這些v是標識符。如果Expr有類型ADDRESS,或者任何T不是Expr的類型的子類型,則為一個靜態錯誤。ELSE S_0和每個(v)都是可選的。如果(v_i)缺席,形式T_1, ..., T_n => S,是T_1 => S | ... | T_n => S的簡寫。

TYPECASE語句求值Expr,如果結果的引用值,是任何列出的類型T_i的成員,則針對其中最小的i,執行S_iNIL是所有引用類型的一個成員,因此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的值是NULLREF BOOLEANREF INTEGER中的哪一個類型。

循環控制結構

[編輯]

Modula-3提供了循環控制結構FORLOOPWHILEREPEAT語句,和必須在字面上包圍在這四個循環語句中,用於從循環中退出的EXIT語句。語句LOOP S END,循環直到S中有一個EXIT發生。語句WHILE B DO S END,被定義為:

LOOP
    IF B THEN S
    ELSE EXIT 
    END
END

語句REPEAT S UNTIL B,被定義為:

LOOP
    S; 
    IF B THEN EXIT END 
END

FOR語句有如下形式:FOR id := first TO last BY step DO S END,這裡的id是一個標識符,而firstlast是有相同基礎類型的序數表達式,step是整數取值的表達式。而S是一個語句。BY step是可選的,如果省略,step預設為1。標識符id指示其作用域S的一個只讀變量,其類型是firstlast共同的基礎類型。FOR語句的語義,可如下這樣用WHILEWITH語句精確的定義為一個塊:

    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

這裡的Tid的類型,idonedelta代表不出現在FOR語句中的變量。從FOR語句的這個定義可以看出,如果一個FOR循環的上界是LAST(INTEGER)LAST(LONGINT),就應當將它改寫為WHILE循環來避免溢出。

With語句

[編輯]

Modula-3的WITH語句,無關於Modula-2的同名語句,它有如下形式:WITH id = e DO S END,這裡的id是一個標識符,e是一個表達式,而S是一個語句。WITH語句聲明了具有作用域Sid,作為變量e的別名,或作為值e的只讀名字。表達式e在進入WITH語句的時候被求值一次。一個單一的WITH語句可以包含多個綁定,它們順序的求值,就是說WITH id_1 = e_1, id_2 = e_2, ...,等價於WITH id_1 = e_1 DO WITH id_2 = e_2 DO ....

WITH語句可類比於某種過程調用P(e),這裡的P被聲明為:

PROCEDURE P(mode id: typeof e) =
BEGIN
    S
END P

這裡的算符typeof只是個示意,它不在語言規定之中;如果e是可寫的指定式,則modeVAR,否則modeREADONLY。在WITH語句和調用P(e)之間唯一的區別為,出現在WITH語句內的自由變量RETURNEXIT,是在WITH語句所在的上下文中解釋的,而非在新增加的P的上下文中。

Modula-3語言報告使用WITH語句定義其他語句,比如,INCDEC語句分別有如下形式:INC(v, n)DEC(v, n),這裡的v指示一個序數類型的變量,而n是可選的一個整數取值表達式,如果省略n則預設為1,這兩個語句分別等價於WITH x = v DO x := VAL(ORD(x) + n, T) ENDWITH x = v DO x := VAL(ORD(x) - n, T) END,這裡的Tv的類型,而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語句。採用類似的塊系統的有DelphiPython[20]Scala[21]Visual Basic.NETTRY-EXCEPT語句有如下形式:

TRY
    Body
EXCEPT 
    id_1 (v_1) => Handler_1
  | ... 
  | id_n (v_n) => Handler_n
    ELSE Handler_0 
END

這裡的Body和每個Handler都是語句,每個id指名一個例外,而每個v_i是一個標識符。ELSE Handler_0和每個(v_i)都是可選的。每個(v_i)聲明一個變量,其類型是例外id_i的實際參數類型,而它的作用域Handler_i。如果(v_i)缺席,形式id_1, ..., id_n => Handler,是id_1 => Handler; ...; id_n => Handler的簡寫。

TRY子句執行Body。如果結果是正常的,則忽略EXCEPT等子句。如果Body引發任何列出的例外id_i,則執行Handler_i。在處理具有配對實際參數x的例外id_i的時候,v_i在執行Handler_i之前被初始化為x。如果Body引發任何其他例外,並且ELSE Handler_0存在,則執行它。在這兩種情況的任何之一下,TRY-EXCEPT語句的結果是選出的處理器的結果。如果Body引發未列出的例外,而ELSE Handler_0缺席,則TRY-EXCEPT語句的結果是Body引發的例外。

Modula-3還提供TRY-FINALLY語句,它有如下形式:TRY S_1 FINALLY S_2 END,它執行語句S_1接著執行語句S_2。如果S_1的結果是正常的,則TRY語句的結果等價於S_1; S_2。如果S_1的結果是例外,並且S_2的結果是正常,則在S_2執行之後,重新發起來自S_1的例外。如果二者的結果都是例外,則TRY語句的結果是來自S_2的例外。

在Modula-3中,EXITRETURN語句的語義,分別被定義為發起叫做exit-exceptionreturn-exception的例外。exit-exception不接受實際參數,return-exception接受任意類型的實際參數,程序不能顯式的命名這些例外。語句LOOP S END,非正式的等價於:

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

過程

[編輯]

過程類型

[編輯]

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

  • 過程體,它是一個語句。
  • 簽名,它有如下形式(formal_1; ...; formal_n): R RAISES S,這裡的每個formal_i是形式參數,R是結果類型,SRAISES集合(這個過程可以引發的例外的集合)。
  • 環境,它是在解釋過程體中變量名字方面有關的作用域

形式參數有如下形式:Mode Name: Type := Default,這裡的:

  • Mode是參數模態,它可以是VALUEVARREADONLY。如果省略了Mode,它預設為VALUE
  • Name是命名這個形式參數的標識符。形式參數名字必須是各不相同的。
  • Type是這個形式參數的類型。
  • Default是一個常量表達式,它是這個形式參數的預設值。如果ModeVAR,則:= 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形式參數綁定到具有未用的位置的一個變量上,並且它被初始化為實際參數對應的值。對於VALUEREADONLY形式參數,實際參數是可賦值給形式參數類型的任何表達式,並且放開了針對賦值局部過程的禁止。

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_ARAISES集合包含sig_BRAISES集合。

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

PROCEDURE P(txt: TEXT := "P") = 
BEGIN
    IO.Put(txt)
END P;
VAR q: PROCEDURE(txt: TEXT := "Q") := P;

聲明了過程常量P和過程變量q。兩個過程如果有一致的閉包,就是說它們涉及相同的過程體和環境,則二者相等即有相同的值。形式參數名字和預設值,影響一個過程的類型即簽名,而非這個過程的值。將一個過程常量賦值給一個過程變量,改變這個它的值,而非它的類型。比如上例中有:P = qTRUE。預設的形式參數的解釋,由一個過程的類型來確定,而非這個過程的值。比如上例子中:P()列印P,而q()列印Q

過程調用

[編輯]

過程調用有如下形式:P(Bindings),這裡的P是一個過程取值的表達式,而Bindings是關鍵字或位置綁定的一個列表。關鍵字綁定有如下形式:name := actual,這裡的actual是一個表達式,而name是一個標識符。位置綁定有如下形式:actual,這裡的actual是一個表達式。當關鍵字和位置綁定混合在一個調用中的時候,位置綁定必須前導於關鍵字綁定。如果綁定列表為空,圓括號仍然是需要的。

綁定列表要如下這樣重寫,來適合P的類型簽名:首先,每個位置綁定actual,若為Bindings中的第i個綁定,則通過補充上第i個形式參數的名字,轉換成關鍵字綁定增加到關鍵字綁定列表中。其次,對於有預設值並且在第一步之後沒有被綁定的每個形式參數,將它的形式參數的名字name和它的預設值default,形成關鍵字綁定name := default增加到關鍵字綁定列表。重寫成的關鍵字綁定列表必須只綁定形式參數,並且必須只綁定每個形式參數正好一次。

執行過程調用,需要求值過程取值表達式P和它的實際參數,綁定形式參數,並執行過程體。P和它的實際參數的求值次序是未定義的。調用一個未定義或NIL過程,是一個必查的運行時間錯誤。調用具有過程體B的真正過程,在綁定了實際參數之後等價於:

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

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

TRY
    B; (错误:没有返回值)
EXCEPT
    return-exception (v) => (结果成为v)
END

遞歸定義

[編輯]

對於常量、類型或過程聲明N = E、變量聲明N: E、例外聲明N(E)或揭示N = E,如果N出現在E的任何部份展開之中,則它是遞歸的。對於省略了類型的變量聲明N := I,如果N出現在I的類型E的任何部份展開中,則它是遞歸的。允許這種聲明的條件,是NE的任何部份展開中所有的出現為如下三者之一:在類型構造子REFPROCEDURE的某個出現之內,在類型構造子OBJECT的一個欄位或方法類型之內,或者在一個過程體之內。下面是合法的遞歸聲明的例子:

TYPE 
    RealList = REF RECORD
        val: REAL;
        link: RealList
    END;
    ListNode = OBJECT
        link: ListNode
    END;
    IntList = ListNode OBJECT
        val: INTEGER
    END;
    T = PROCEDURE(n: INTEGER; p: T);
EXCEPTION E(PROCEDURE() RAISES {E});
PROCEDURE P(b: BOOLEAN) =
BEGIN
    IF b THEN P(NOT b) END 
END P;

特徵性技術

[編輯]

Modula-3的設計者認為Modula-2最成功的特徵,是提供了在模塊之間的顯式接口,故而接口在Modula-3中保留而在本質上沒有變化。到模塊的接口,是披露了模塊公開部份的聲明搜集,而未聲明於接口中的模塊中的東西是私有的。模塊導入它所依賴的接口,而導出它所實現的接口(在Modula-3中可以是多個接口)。

在現代程式語言的實現下,一個抽象類型的值,被表示一個對象,它的操作由叫做對象的方法的過程值的一個套件來實現。一個新的對象類型,可以定義為現存類型的子類型,在這種情況下新類型擁有舊類型的所有方法,並且可能還有新的方法(繼承)。新類型可以為舊方法提供新的實現(覆蓋)。對象類型和Modula-2不透明類型英語Opaque data type的組合,產生了一個新事物:部份不透明(partially opaque)類型,這裡的一個對象的一些欄位在一個作用域內是可見的,而其他的是隱藏的。

泛型模塊是一種模板,在其中一些導入的接口被當作形式參數,它們在實例化泛型的時候要綁定到實際接口。不同的泛型實例是獨立編譯的,原始碼程序被重複使用,而編譯後的代碼對不同實例通常是不同的。為保持簡單性,在Modula-3中泛型被限制在模塊層面,泛型的過程和類型不孤立存在,並且泛型形式參數必須都是完全的接口。

將計算分解入並發進程(或執行緒)中是關注點分離的基本方法。Modula-2提供的執行緒很弱,本質上相當於協程。Modula-3採用了霍爾監視器,它是並發編程的更堅實基礎。Modula-2+英語Modula-2+在設計中加入了簡化的Mesa同步原語,它足夠成功而在實質上沒有改變的結合入了Modula-3。

一個語言特徵是不安全的,如果它的濫用可以破壞運行時系統,使得程序的進一步運行不忠實於語言的語義,不安全特徵的一個例子,是沒有邊界檢查的數組賦值。一個安全的程序的一個錯誤,可以導致計算中止,並給出運行時間錯誤信息或給出錯誤解答,但它不會導致計算分崩離析。Modula-3在這個領域裡跟從Cedar,接受只在顯式的標記為不安全的模塊中才允許的少量不安全特徵。

模塊化

[編輯]

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

INTERFACE id;
    Imports;
    Decls
END id.

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

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

MODULE id EXPORTS Interfaces;
    Imports;
Block id.

這裡的id是命名這個模塊的一個標識符,Interfaces是這個模塊導出的那些接口的不同名字的一個列表,Imports是一序列的導入語句,而Block是一個塊,它是模塊體。名字id必須重複於終結模塊體的END之後。EXPORTS Interfaces可以省略,在這種情況下Interfaces預設為id,即模塊將預設導出相同名稱的接口。Modula-3中的導出EXPORTS,與Modula-2中的導出EXPORT無關,它在概念上對應於Modula-2的「實現」。

如果模塊M導出了接口I,則在I中聲明的所有名字在M中是不加以限定而可見的。一個模塊M導出一個接口I,可以重新聲明在這個接口中聲明的一個或多個過程,為其提供過程體。在M中的簽名,必須被在I中的簽名所含蓋。要確定在對這個過程的調用中的關鍵字綁定的解釋,在M內使用M中的簽名,在其他地方使用I中的簽名。除了重新聲明導出的過程之外,在Block的頂層中聲明的名字,可見的導入的名字,和在導出的接口中聲明的名字必須是各不相同的。

一個程序的模塊和接口的名字叫做全局名字。查找全局名字的方法是依賴於實現的,比如通過文件系統查找路徑。一個模塊或接口X導入一個接口I,從而使得在I中聲明的實體和揭示在X中可見,但不包括I所導入的那些。一個模塊M使用了接口I,如果M導入或導出了I,或者M使用了導入I的一個接口。Modula-3中所有的編譯單元,要麼是接口要麼是模塊。任何編譯單元都可以使用IMPORT語句從其他接口導入標識符。與其他語言的包含功能不同,任何標識符都可以追蹤到它的起源位置。

IMPORT語句有如下的典型使用形式:IMPORT I,它導入全局名字為I的接口,並給它一個同樣的局部名字,從而使用點表示法訪問接口中的實體(類似於訪問記錄中的欄位)。比如接口中的主要類型按慣例命名為T,則使用它時採用的限定形式為I.T。如果導入接口的名字與模塊內的其他實體的名字發生衝突,則可以使用IMPORT I AS X這樣的形式給與它不同的局部名字。

IMPORT語句還有如下的另一種形式:FROM I IMPORT N,它為在接口I中聲明為N的實體,介入一個局部名字N,比如:FROM IO IMPORT Put,並且不加限定的使用它,比如:Put("Hello World\n")。局部綁定優先於全局綁定,例如:IMPORT I AS J, J AS I; FROM I IMPORT N,為全局名字為J.N的實體介入了局部名字N。非法使用相同的局部名字兩次是靜態錯誤。

一個程序是模塊和接口的搜集,包含了任何它的模塊或接口所導入或導出的所有接口,並且在其中沒有重複定義的過程、模塊或接口。執行一個程序的效果是執行它的所有模塊的模塊體。模塊的執行次序受到初始化規則的約束:如果模塊M依賴於模塊N並且N不依賴於M,則N的模塊體將先於M的模塊體執行。這裡的模塊M依賴於模塊N,如果M使用了N導出的一個接口,或者M所依賴的一個模塊依賴於N

其模塊體最後執行的模塊叫做主模塊。在初始化規則不能唯一的確定它的情況下,語言實現提供了指定這個主模塊的方法,即主模塊是導出Main的模塊,它的內容是依賴於實現的。程序執行在主模塊終止的時候終止,即使並發的控制執行緒仍在執行。

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

INTERFACE Stack;
    TYPE T <: REFANY;
    PROCEDURE Create(): T;
    PROCEDURE Push(VAR s: T; x: REAL);
    PROCEDURE Pop(VAR s: T): REAL;
END Stack.
MODULE Stack;
    REVEAL T = BRANDED OBJECT
        item: REAL;
        link: T
    END;
    PROCEDURE Create(): T = 
    BEGIN 
        RETURN NIL 
    END Create;
    PROCEDURE Push(VAR s: T; x: REAL) =
    BEGIN 
        s := NEW(T, item := x, link := s)
    END Push;
    PROCEDURE Pop(VAR s: T): REAL =
        VAR res: REAL;
    BEGIN 
        res := s.item;
        s := s.link;
        RETURN res
    END Pop;
BEGIN
END Stack.

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

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

INTERFACE StackRep;
    IMPORT Stack;   
    REVEAL Stack.T = BRANDED OBJECT
        item: REAL;
        link: Stack.T
    END
END StackRep.
MODULE Stack;
    IMPORT StackRep;
    (* Push、Pop和Create同上 *)
BEGIN
END Stack.

對象類型

[編輯]

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

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

TYPE T = ST OBJECT
        Fields
    METHODS
        Methods
    OVERRIDES
        Overrides
    END

這裡的ST是可選的超類型,如果省略則預設為ROOTFields是完全如同記錄類型那樣的欄位聲明的一個列表,Methods是方法聲明的一個列表,而Overrides是方法覆蓋的一個列表。在欄位和方法聲明中介入的名字,必須相互不同,並且不同於在覆蓋聲明中的名字。T的欄位構成自ST的欄位和隨後的新聲明的欄位。T的方法構成自經過OVERRIDES中的覆蓋修改的ST的方法,和隨後的METHODS中聲明的方法。T有著同ST一樣的引用類。

關鍵字OBJECT可選的可以前導上BRANDEDBRANDED 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的方法mproc,並非指定ST.m。如果proc非空,它的第一個形式參數必須是預設的模態VALUE,並有著T的某個超類型的類型(包括且經常是T自身),去除了proc第一個形式參數結果就是STm方法所含蓋的簽名。舉例說明:

TYPE 
    Super = OBJECT
        a: INTEGER;
    METHODS
        p()
    END;
    Sub = Super OBJECT
        b: INTEGER
    END;
PROCEDURE ProcSuper(self: Super) = ... ; 
PROCEDURE ProcSub(self: Sub) = ... ;

這裡的過程ProcSuperProcSub是類型SuperSub的對象的p方法的候選值。例如:

TYPE T1 = Sub OBJECT
    OVERRIDES
        p := ProcSub
    END
TYPE T2 = Super OBJECT
    OVERRIDES
        p := ProcSuper
    END
聲明了具有Sub數據記錄和預期一個Subp方法的一個類型。T1Sub的有效的子類型。 聲明了具有Super數據記錄和預期一個Superp方法的一個類型。T2Super的有效的子類型。
TYPE T3 = Sub OBJECT
    OVERRIDES
        p := ProcSuper
    END
TYPE T4 = Super OBJECT
    OVERRIDES
        p := ProcSub
    END
聲明了具有Sub數據記錄和預期一個Superp方法的一個類型。因為所有Sub都是Super,這個方法不挑剔要放入的對象。 嘗試聲明具有Super數據記錄和預期一個Subp方法的一個類型,因為不是所有Super都是Sub,這個方法很挑剔要放入的對象。T4的聲明是個靜態錯誤。

如果T是對象類型或到記錄的引用,NEW(T)操作有如下形式:NEW(T, Bindings),這裡的Bindings是用來初始化新欄位的關鍵字綁定的一個列表,不允許位置綁定。每個綁定f := v將欄位f初始化為值v。如果T是對象類型,則Bindings還可以包括形如m := P的方法覆蓋,這裡的mT的一個方法而P是一個頂層過程常量。NEW(T, m := P)NEW(T OBJECT OVERRIDES m := P END)語法糖

如果o是一個對象,則o.f指示在o的數據記錄中名為f的數據欄位。o.f是可寫的指定式,它的類型是這個欄位的所聲明的類型。如果mo方法之一,以o.m(Bindings)形式的過程調用,指示執行om方法,這等價於:(o的m方法) (o, Bindings)

如果T是對象類型而mT的方法的名字之一,則T.m指示Tm方法。它的過程類型的第一個實際參數有類型T,而第一個實際參數對應的形式參數名字是未指定的,因此在調用T.m時,第一個實際參數必須按位置來指定,不能用關鍵字。這種表示法便於子類型方法調用它的某個超類型的對應方法。

在子類型中的欄位或方法遮蓋(mask)了在超類型中的任何同名的欄位或方法。要訪問被遮蓋的欄位,使用類型運算NARROW(x, T)來將子類型變量當作超類型的成員,這裡的T必須是對象類型或有跟蹤的引用類型,而x必須可賦值給TNARROW(x, T): T在檢查了xT一個成員之後返回x,如果檢查失敗則發生運行時間錯誤。

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

TYPE
    Super = OBJECT 
    METHODS
        m() := ProcSuper
    END;
    SubOverridden = Super OBJECT
    OVERRIDES
        m := ProcSub
    END;
    SubExtended = Super OBJECT
    METHODS
        m() := ProcSub
    END;
VAR
    a := NEW(Super); 
    b := NEW(SubOverridden);
    c := NEW(SubExtended);

然後可以用a.m()激活ProcSuper(a),用b.m()激活ProcSub(b),用c.m()激活ProcSub(c),如此調用在覆蓋和擴展之間沒有區別。但是擴展的c的方法套件有兩個方法,而覆蓋的b的方法套件只有一個方法,這可以通過使用NARROW(x, T)將子類型的變量bc視為超類型Super的成員來披露:NARROW(b, Super).m()激活ProcSub(b)NARROW(c, Super).m()激活ProcSuper(c)

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

實現

[編輯]

下面是語言設計者提出的對象的一種可能實現的梗概[19],一個對象可以表示為它的數據記錄的第一個的地址。前面的字存儲一個對象頭部,它包含一個唯一於對象類型的類型代碼。這些類型代碼是小型整數,對每個對象類型和每個有跟蹤的引用類型都有一個代碼。在對象頭部之前的字,存儲到這個對象的方法套件的一個引用。如果這個對象沒有方法,這個字可以省略。這還允許對象共享方法套件,這是常見情況。如果o是一個對象,d是它的數據欄位之一,而m是它的方法之一,則在這種表示下:

o.d Mem[o + d]
o.m Mem[Mem[o – 2] + m]
TYPECODE(o) Mem[o – 1]

這裡假定了欄位和方法以自然方式表示為偏移量。進一步的問題是如何高效的測試一個對象o是否有著類型T,這是NARROW()TYPECASE語句所需要的。NARROW()的最簡單實現,是維護一個以類型代碼為索引的數組stst[tc]是其類型代碼為tc的對象類型的超類型的類型代碼,如果它沒有超類型則為NIL。要測試o是否是一個T,使用一個循環來計算T的類型代碼是否出現在如下序列之中:

TYPECODE(o), st[TYPECODE(o)], st[st[TYPECODE(o)]], ... NIL

這個序列可以稱為o的類型的超類型路徑,而它的長度可稱為o的類型的深度。利用上每個類型的深度都是編譯時間確定的,因此可以同相應的類型代碼一起存儲的事實,可以有更快速的NARROW()實現。如果T的類型代碼,出現在類型U的超類型路徑上,它就該在位置depth(U) - depth(T)上。如果每個類型的超類型路徑都是順序數組,這意味著NARROW()可以用恆定時間實現。因為超類型路徑通常不太長,這是一個有吸引力的策略。在不常見的對象類型有非常長的超類型鏈的情況下,只有這個鏈的直到某個極大長度的一個前綴,會被順序的存儲。在運行時間,這個深度差如果超出這個鏈的順序存儲的長度,實現必須回退到鍊表

不透明類型及其披露

[編輯]

不透明英語Opaque data type(opaque)類型聲明有如下形式:TYPE T <: U,這裡的T是標識符,而U是指示一個引用類型的表達式。它將名字T介入為不透明類型,並披露了UT的超類型。

Modula-3的揭示(revelation)機制,將關於一個不透明類型的信息,介入到一個作用域之內,不同於其他聲明,揭示不介入新的名字。不同的作用域,可以披露(reveal)關於一個不透明類型的不同的信息。例如,某個不透明類型,在一個作用域內知曉為REFANY的子類型,在其他的作用域內可以知曉為ROOT的子類型。

不透明類型名字所指示的實際類型叫做具體類型。T的具體類型,必須披露於程序的其他地方。在對象聲明中,如果ST被聲明為不透明類型,則只在知曉ST的具體類型為對象類型的作用域內,T的聲明是合法的。如果T被聲明為不透明類型,則只在完全知曉T的具體類型,或者知曉它為對象類型的作用域內,NEW(T)是合法的。

揭示分為兩種:部份的和完全的,一個程序可以包含一個不透明類型的任意多個部份揭示,但必須包含唯一的一個完全揭示。

部份揭示有如下形式:REVEAL T <: V,這裡的V是類型表達式(可能就是個名字),而T是被聲明為不透明類型的一個標識符(可能有限定)。它披露了VT的超類型。在任何作用域內,對一個不透明類型披露的超類型,必須是在子類型關係下是線性有序的。就是說,如果披露了T <: U1T <: U2,則必須也要披露要麼U1 <: U2要麼U2 <: U1REVEAL T <: T是合法的非遞歸聲明。

完全揭示有如下形式:REVEAL T = V,這裡的V是類型表達式(不能就是個名字),它的最外層類型構造子,是有銘牌的一個引用或對象類型,而T是被聲明為不透明類型的一個標識符(可能有限定)。這個揭示指定了VT的具體類型。在任何作用域內,披露為T的超類型的任何類型,都必須是V的超類型,否則是一個靜態錯誤。不同的不透明類型有不同的具體類型,因為V包含了一個銘牌並且在程序中所有的銘牌都是獨一的。REVEAL I.T = I.T BRANDED OBJECT ... END是非法的遞歸聲明。

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

INTERFACE I; 
    TYPE T <: ROOT;
    PROCEDURE P(x: T): T;
END I.

INTERFACE IRep;
    IMPORT I;
    REVEAL I.T = MUTEX BRANDED OBJECT
        count: INTEGER
    END;
END IRep.

INTERFACE IClass;
    IMPORT I;
    REVEAL I.T <: MUTEX;
END IClass.

導入I的編譯單元見到的I.TROOT的不透明子類型,因而被限制為可以分配類型I.T的對象,把它們傳遞給I.P,或聲明I.T的子類型。導入IRep的編譯單元見到的I.T是具體類型,它是有擴展的count欄位的MUTEX的子類型。導入IClass的編譯單元見到的I.TMUTEX的不透明子類型,因而可以鎖定類型I.T的對象。

泛型

[編輯]

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

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

這裡的G是命名這個泛型接口的一個標識符,F_1, ..., F_n是叫做G的形式導入的標識符的一個列表,而Body同在非泛型接口中一樣,是一序列導入和隨後的一序列聲明。這裡的I是這個實例的名字,而A_1, ..., A_nG的形式導入要綁定的實際接口的一個列表。

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

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

這裡的G是命名這個泛型模塊的一個標識符,F_1, ..., F_n是叫做G的形式導入的標識符的一個列表,而Body同在非泛型模塊中一樣,是一序列的導入和隨後的一個塊。這裡的I是這個實例的名字,E是由M導出的接口的一個列表,而A_1, ..., A_nG的形式導入要綁定的實際接口的一個列表。EXPORTS E可以省略,在這種情況下它預設為EXPORTS I。泛型模塊自身沒有導出,它們只在被實例化時提供。

泛型接口及其對應的泛型模塊(與C++模板一樣)可以輕鬆定義和使用抽象數據類型,但粒度在模塊級別,裸露的類型INTEGERREAL不能使用,相比之下,它們在C++模板中可以使用。例如,可以定義泛型堆疊

GENERIC INTERFACE Stack(Elem);
    (* 这里的Elem.T不是开放数组类型 *)
    TYPE T <: REFANY;
    PROCEDURE Create(): T;
    PROCEDURE Push(VAR s: T; x: Elem.T);
    PROCEDURE Pop(VAR s: T): Elem.T;
END Stack.
GENERIC MODULE Stack(Elem);
    REVEAL T = BRANDED OBJECT
        n: INTEGER;
        a: REF ARRAY OF Elem.T
    END;
    PROCEDURE Create(): T =
    BEGIN
        RETURN NEW(T, n := 0, a := NIL)  
    END Create;
    PROCEDURE Push(VAR s: T; x: Elem.T) =
    BEGIN
        IF s.a = NIL THEN 
            s.a := NEW(REF ARRAY OF Elem.T, 5)
        ELSIF s.n > LAST(s.a^) THEN
            WITH temp = NEW(REF ARRAY OF Elem.T, 2 * NUMBER(s.a^)) DO
                FOR i := 0 TO LAST(s.a^) DO
                    temp[i] := s.a[i] 
                END;
                s.a := temp
            END
        END;
        s.a[s.n] := x;
        INC(s.n)
    END Push;
    PROCEDURE Pop(VAR s: T): Elem.T =
    BEGIN
        DEC(s.n);
        RETURN s.a[s.n]  
    END Pop;
BEGIN
END Stack.

然後使用接口例如IntegerElem,對其進行實例化,只要這個接口定義了泛型模塊所需的屬性即可:

INTERFACE IntegerElem;
    TYPE T = INTEGER;
END IntegerElem.

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

物件導向

[編輯]

Modula-3下的物件導向編程,經常採用部份不透明類型,它通過如下慣用法來聲明:Type T <: Public; Public = OBJECT ... END,這裡的不透明類型T,支持在隨後定義的對象類型中的欄位和方法,而不會披露T的確切結構,也不披露T可能支持的其他方法。

下面的例子,定義一個接口Person,具有按習慣約定命名的兩個類型,導出類型T,和公開對象類型Public,這裡聲明了TPublic的不透明子類型Public具有兩個方法getAge()init();隨後是Person接口的完全實現:

INTERFACE Person;
    TYPE T <: Public;
        Public = OBJECT 
        METHODS
            getAge(): INTEGER;
            init(name: TEXT; age: INTEGER): T;
        END;
END Person.
MODULE Person;
    REVEAL T = Public BRANDED OBJECT 
        name: TEXT;
        age: INTEGER;
    OVERRIDES
        getAge := Age;
        init := Init;
    END;
    PROCEDURE Age(self: T): INTEGER =
    BEGIN
        RETURN self.age;
    END Age;
    PROCEDURE Init(self: T; name: TEXT; age: INTEGER): T =
    BEGIN
        self.name := name;
        self.age := age;
        RETURN self;
    END Init;
BEGIN
END Person.

要建立一個新的Person.T對象,要使用內建操作NEW(),並調用方法init()

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

下面是整數堆疊的例子:

INTERFACE IntegerStack;
    TYPE
        T <: Public OBJECT;
        Public = OBJECT
        METHODS
            init(): T;
            push(x: INTEGER);
            pop(): INTEGER;
        END;
END IntegerStack.
MODULE IntegerStack;
    REVEAL T = Public BRANDED OBJECT
        n: INTEGER;
        a: REF ARRAY OF INTEGER;
    OVERRIDES
        init := Init;
	    push := Push;
	    pop := Pop;
    END;
    PROCEDURE Init(self: T): T =
    BEGIN
        self.n := 0;
        self.a := NIL;
        RETURN self
    END Init;
    PROCEDURE Push(self: T; x: INTEGER) =
    BEGIN
        IF self.a = NIL THEN 
            self.a := NEW(REF ARRAY OF INTEGER, 5)
        ELSIF self.n > LAST(self.a^) THEN
            WITH temp = NEW(REF ARRAY OF INTEGER, 2 * NUMBER(self.a^)) DO
                FOR i := 0 TO LAST(self.a^) DO
                    temp[i] := self.a[i] 
                END;
                self.a := temp
            END
        END;
        self.a[self.n] := x;
        INC(self.n)
    END Push;
    PROCEDURE Pop(self: T): INTEGER =
    BEGIN
        DEC(self.n);
        RETURN self.a[self.n]  
    END Pop;
BEGIN
END IntegerStack.

創建新堆疊在這裡的部份不透明類型實現下為:VAR myStack := NEW(IntegerStack.T).init(),在上述的不透明類型實現下為:VAR myStack := IntStack.Create()。而壓入一個數在這裡的部份不透明類型實現下為:myStack.push(8),在上述的不透明類型實現下為:IntStack.Push(myStack, 8)

多執行緒

[編輯]

語言支持多執行緒和在執行緒間的同步。在運行時庫(m3core)中有一個必要接口叫做Thread,它支持採用分叉會合模型的多執行緒應用。這裡還預定義不透明類型MUTEX被用來同步多個執行緒,並保護共享數據免於具有可能損害或競爭條件的同時訪問。MUTEX是一個對象,因此可以從它派生其他對象。

LOCK語句有如下形式:LOCK mu DO S END,這裡的S是一個語句,而mu是一個表達式,它等價於:

WITH m = mu DO
    Thread.Acquire(m);
    TRY S FINALLY Thread.Release(m) END
END

這裡的m表示不出現在S中的一個變量。LOCK語句介入一個互斥鎖要鎖定的塊,並暗含著在代碼執行軌跡離開這個塊時的解鎖它。

不安全標記

[編輯]

某些功能被認為是不安全的,編譯器無法再保證結果是一致性的(例如,當與C程式語言交接時)。在INTERFACEMODULE前面加上前綴關鍵字UNSAFE,可用於告訴編譯器啟用語言的某些不安全的低級功能。例如,在UNSAFE模塊中,使用LOOPHOLE()將整數的諸位元複製成浮點REAL數,使用DISPOSE()釋放無跟蹤的內存。

一個接口是內在安全的,如果在安全模塊中使用這個接口,無法產生不核查的運行時間錯誤。如果導出一個安全接口的所有的模塊都是安全的,編譯器保證這個接口的內在安全性。如果導出一個安全接口的任何模塊是不安全的,就要編程者而非編譯器,去做這種保證了。安全接口導入不安全接口,或安全模塊導入或導出不安全接口,都是靜態錯誤。導入不安全接口的編譯單元本身必定也是不安全的。

對其他程式語言的影響

[編輯]

儘管Modula-3沒有獲得主流地位,DEC-SRC M3發行的某些部份做到了。可能最有影響的一部份是網絡對象庫,它形成了Java包括了網絡協議的最初的遠程方法調用(RMI)實現的基礎。在Sun從通用對象請求代理架構(CORBA)標準轉移到基於IIOP的協議的時候才被放棄。Java關於遠程對象的垃圾收集的文檔仍提及了Modula-3網絡對象為其先驅性工作[22]

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

引用

[編輯]
  1. ^ Modula-3 Reference Manual頁面存檔備份,存於網際網路檔案館) Luca Cardelli, James Donahue, Lucille Glassman, Mick Jordan, Bill Kalsow, Greg Nelson. DEC Systems Research Center (SRC) (February 1995)
  2. ^ Critical Mass Modula-3 (CM3). Critical Mass Modula-3. elego Software Solutions GmbH. [2020-03-21]. (原始內容存檔於2020-09-24). 
  3. ^ Polytechnique Montréal Modula-3 (PM3): What is it. Polytechnique Montréal Modula-3. elego Software Solutions GmbH. [2020-03-21]. (原始內容存檔於2020-11-19). 
  4. ^ Polstra, John D. Ezm3: An Easier Modula-3 Distribution. CVSup.org. November 9, 2006 [2020-03-21]. (原始內容存檔於2007-04-29). 
  5. ^ Weich, Carsten. M3/PC Klagenfurt 96: a Modula-3 environment for MS-DOS. Department of Informatics. University of Klagenfurt. [2020-03-21]. (原始內容存檔於2000-05-20). 
  6. ^ Picheta, Dominik; Locurcio, Hugo. Frequently Asked Questions. [2020-03-21]. (原始內容存檔於2016-12-10). 
  7. ^ van Rossum, Guido. Programming Python: Foreword (1st ed.). Python.org. May 1996 [2020-03-21]. (原始內容存檔於2014-07-24). besides ABC, my main influence was Modula-3. This is another language with remarkable elegance and power, designed by a small, strong-willed team (most of whom I had met during a summer internship at DEC's Systems Research Center in Palo Alto). 
  8. ^ Baby Modula-3 and a theory of objects頁面存檔備份,存於網際網路檔案館Martin Abadi英語Martín Abadi. Digital Equipment Corporation (DEC) Systems Research Center (SRC) Research Report 95 (February 1993).
  9. ^ Design and History FAQ - Why must ‘self’ be used explicitly in method definitions and calls?. Python.org. March 21, 2020 [2020-03-21]. (原始內容存檔於2012-10-24). The idea was borrowed from Modula-3. It turns out to be very useful, for a variety of reasons. 
  10. ^ Modula-3: Language definition.. [2021-06-25]. (原始內容存檔於2021-06-25). 
  11. ^ Rovner, Paul; Levin, Roy; Wick, John. SRC-RR-3 On extending Modula-2 for building large, integrated systems. Hewlett-Packard Labs (報告). January 11, 1985 [2021-08-11]. (原始內容存檔於2021-05-16). 
  12. ^ Thacker, Charles P.; Stewart, Lawrence C.; Satterthwaite, Edwin H. Jr. Firefly: a multiprocessor workstation SRC-RR-23. Hewlett-Packard Labs (報告). December 30, 1987 [2021-08-11]. (原始內容存檔於2021-05-16). 
  13. ^ McJones, Paul R.; Swart, Garret F. Evolving the Unix system interface to support multithreaded programs SRC-RR-21. Hewlett-Packard Labs (報告). September 28, 1987 [2021-08-11]. (原始內容存檔於2021-05-16). 
  14. ^ 14.0 14.1 Modula-3 report (revised)頁面存檔備份,存於網際網路檔案館) Luca Cardelli, James Donahue, Lucille Glassman, Mick Jordan, Bill Kalsow, Greg Nelson. DEC Systems Research Center (SRC) Research Report 52 (November 1989)
  15. ^ Peter Robinson英語Peter Robinson (computer scientist). From ML to C via Modula-3 - an approach to teaching programming (PDF). 1994 [2021-08-11]. (原始內容 (PDF)存檔於2021-12-17). The Computer Science course at the University of Cambridge teaches ML as an introductory language at the beginning of the freshman year, and then uses Modula-3 to introduce imperative programming at the end of that year. Further lectures on advanced features of Modula-3 are given early in the second year, together with separate lectures on C. Other, specialised languages are introduced subsequently as the course progresses. 
  16. ^ SPIN OVERVIEW. [2021-08-11]. (原始內容存檔於2022-02-24). SPIN and its extensions are written in Modula-3, a type-safe programming language developed at DEC SRC. Modula-3 offers modern language features such as objects, garbage collection, and threads. We rely on its type-safe properties to protect sensitive kernel data and interfaces from malicious or errant extensions. 
  17. ^ Systems Programming with Modula-3.
  18. ^ Modula-2 Reference - Statements. [2023-02-21]. (原始內容存檔於2023-02-21). 
  19. ^ 19.0 19.1 Luca Cardelli, Jim Donahue, Mick Jordan, Bill Kalsow, Greg Nelson. The Modula-3 Type System (PDF). 1989 [2021-08-12]. (原始內容 (PDF)存檔於2021-03-24). 
  20. ^ Why was Python created in the first place?. [2021-06-22]. (原始內容存檔於2008-02-23). I had some experience with using Modula-2+ and talked with the designers of Modula-3 and read the Modula-3 report. Modula-3 is the origin of the syntax and semantics used for exceptions, and some other Python features. 
  21. ^ Scala Center at EPFL.. [2022-05-15]. (原始內容存檔於2020-09-23). 
  22. ^ Garbage Collection of Remote Objects頁面存檔備份,存於網際網路檔案館), Java Remote Method Invocation Documentation for Java SE 8.
  23. ^ The Python Tutorial - Classes. [2018-07-01]. (原始內容存檔於2020-12-03). It is a mixture of the class mechanisms found in C++ and Modula-3. ……As in Modula-3, there are no shorthands for referencing the object’s members from its methods: the method function is declared with an explicit first argument representing the object, which is provided implicitly by the call. ……I would use Modula-3 terms, since its object-oriented semantics are closer to those of Python than C++, but I expect that few readers have heard of it. 

外部連結

[編輯]