Simula 誕生之前的面向物件程式設計 | Linux 中國

語言: CN / TW / HK

我們可能會理所當然地認為,面向物件程式設計的方式千篇一律,即程式就是對事件的一長串記錄:某個物件以特定順序對其他物件產生作用。Simula I 的程序系統表明,面向物件程式設計的方式不止一種。

(本文字數:9602,閱讀時長大約:14 分鐘)

想象一下,你坐在河邊,河岸上如茵綠草,不遠處湍急河流;午後的陽光慵懶愜意,使人陷入冥想哲思,不覺開始思考眼前的河流是否真實存在。誠然,幾米外確實有河水奔流而下。不過,我們所稱為“河流”的存在究竟是什麼呢?畢竟,河水奔流不息,一直處於變化之中。似乎,“河流”這個詞無法指代任何固定不變的事物。

2009 年,Clojure 的創始人 裡奇·希基(Rich Hickey) 發表了一場精彩的演講,探討了為什麼上文那樣的哲學窘境會給面向物件程式的程式設計正規化帶來難題。他認為,人們看待計算機程式中的物件與看待河流的邏輯是一樣的:我們想象物件是固定不變的,即使物件的許多或者說全部的屬性都無時無刻不處於變化之中。所以,這種邏輯並不正確,我們無法區分在不同狀態下同一物件例項的不同之處。程式中沒有明確的時間的概念。人們只是單純地用著同一個名字,以期在引用物件時,物件能夠處於預期的狀態中。這樣,我們也就難免會遇到 故障(bug)

希基總結道,這一難題的應對辦法就是人們應該將世界建模成作用於不可變資料的 程序(process) 的集合,而不是可變的物件的集合。換句話說,我們應把每個物件看作一條“河流”,因果相連。總結說來,你應該使用 Clojure 等函式式語言。

自從希基發表演講之後,人們對函數語言程式設計語言的興趣不斷提升,主流的面向物件程式語言也大多都採用了函數語言程式設計語言。儘管如此,大多數程式設計師依舊沿用自己的老一套,繼續將物件例項化,不斷改變其狀態。這些人長此以往,很難做到用不同的視角看待程式設計。

我曾經想寫一篇關於 Simula 的文章,大概會寫到我們今天所熟知的面向物件的理念是何時又是如何應用到程式語言之中的。但是,我覺得寫當初的 Simula 與如今的面向物件程式設計的 迥然不同之處 ,會更有趣一些,這我敢打包票。畢竟,我們現在熟知的面向物件程式設計還未完全成型。Simula 有兩個主要版本:Simula I 和 Simula 67。Simula 67 為世界帶來了 類(class)類的繼承(class hierarchy) 以及 虛擬方法(virtual method) ;但 Simula I 是一個初稿,它實驗瞭如何能夠將資料和程序捆綁起來的其他設想。Simula I 的模型不是希基提出的函式式模型,不過這一模型關注的是隨時間展開的 程序 ,而非有著隱藏狀態的物件之間的相互作用。如果 Simula 67 採用了 Simula I 的理念,那麼我們如今所知的面向物件程式設計可能會大有不同——這類偶然性啟示我們,不要想著現在的程式設計正規化會一直佔據主導地位。

從 Simula 0 到 Simula 67

Simula 是由兩位挪威人 克里斯汀·尼加德(Kristen Nygaard)奧利-約翰·達爾(Ole-Johan Dahl) 建立的。

20 世紀 50 年代末,尼加德受僱於 挪威防務科學研究中心(Norwegian Defense Research Establishment) (NDRE),該研究中心隸屬於挪威軍方。在那裡,他負責設計 蒙特卡洛模擬方法(Monte Carlo simulations) ,用於核反應堆設計與操作研究。最初,那些模擬實驗是由人工完成的;後來,實驗在 Ferranti Mercury 電腦[1] 上編入程式執行。尼加德隨後發現,將這些模擬實驗輸入電腦需要一種更有效的方式。

尼加德設計的這種模擬實驗就是人們所知的“ 離散事件模型(discrete event model) ”,這種模擬記錄了一系列事件隨著時間改變系統狀態的程序。但是問題的關鍵在於模擬可以從一個事件跳躍到另一個事件中,因為事件是離散的,事件之間的系統不存在任何變化。根據尼加德和達爾在 1966 年發表的一篇關於 Simula 的論文,這種模型被迅速應用於“神經網路、通訊系統、交通流量、生產系統、管理系統、社會系統等”[2] 領域的分析。因此,尼加德認為,其他人描述模擬實驗時,可能也需要更高層級的模型。於是他開始物色人才,幫助他完成他稱之為“ 模擬語言(Simulation Language) ”或者“ 蒙特卡洛編譯器(Monte Carlo Compiler) ”的專案[3]。

達爾當時也受僱於挪威防務科學研究中心,專攻語言設計,此時也加入了尼加德的專案,扮演“沃茲尼亞克”的角色(LCTT 譯註:指蘋果公司聯合創始人斯蒂夫·蓋瑞·沃茲尼亞克)。在接下來一年左右的時間,尼加德和達爾攜手開發了 Simula 0 語言。[4] 這一語言的早期版本僅僅是在 ALGOL 60 基礎上進行的較小拓展,當時也只是打算將其用作預處理程式而已。當時的語言要比後來的程式語言抽象得多,其基本語言結構是“ 車站(stations) ”與“ 乘客(customers) ”,這些結構可以用於針對具體某些離散事件網路建立模型。尼加德和達爾給出了一個模擬飛機離港的例子。[5] 但是尼加德和達爾最後想出了一個更加通用的語言結構,可以同時表示“車站”和“乘客”,也可以為更廣泛的模擬建立模型。這是兩個主要的概括,它改變了 Simula 作為 ALGOL 專屬包的定位,使其轉變為通用程式語言。

Simula I 沒有“ 車站(stations) ”和“ 乘客(customers) ”的語言結構,但它可以通過使用“ 程序(process) ”再現這些結構。(LCTT 譯註:此處使用的“程序”,與當前計算機中用來指代一個已執行程式的實體的概念不同,大致上,你可以將本文中所說的“程序”理解為一種“物件”。)一個程序包含大量資料屬性,這些屬性與作為程序的 操作規程 的單個行為相聯絡。你可能會把程序當作是隻有單個方法的物件,比如 run() 之類的。不過,這種類比並不全面,因為每個程序的操作規程都可以隨時暫停、隨時恢復,因為這種操作規程屬於 協程(coroutine) 的一種。Simula I 程式會將系統建立為一套程序的模型,在概念上這些程序並行執行。實際上,一個時間點上能稱為“當前程序”的只有一個程序。但是,一旦某個程序暫停執行,那麼下一個程序就會自動接替它的位置。隨著模擬的執行,Simula 會保持一個 “ 事件通知(event notices) ” 的時間線,跟蹤記錄每個程序恢復的時間。為了恢復暫停執行的程序,Simula 需要記錄多個 呼叫棧(call stacks) 的情況。這就意味著 Simula 無法再作為 ALGOL 的預處理程式了,因為 ALGOL 只有一個 呼叫棧(call stacks) 。於是,尼加德和達爾下定決心,開始編寫自己的編譯器。

尼加德和達爾在介紹該系統的論文中,藉助圖示,通過模擬一個可用機器數量有限的工廠,闡明瞭其用法。[6] 在該案例中,程序就好比訂單:通過尋找可用的機器,訂單得以發出;如果沒有可用的機器,訂單就會擱置;而一旦有機器空出來,訂單就會執行下去。有一個訂單程序的定義,用來例項化若干種不同的訂單例項,不過這些例項並未呼叫任何方法。該程式的主體僅僅是建立程序,並使其執行。

歷史上第一個 Simula I 編譯器釋出於 1965 年。尼加德和達爾在離開挪威防務科學研究中心之後,就進入了 挪威計算機中心(Norwegian Computer Center) 工作,Simula I 也是在這裡日漸流行起來的。當時,Simula I 在 UNIVAC 公司的計算機和 Burroughs 公司的 B5500 計算機上均可執行。[7] 尼加德和達爾兩人與一家名為 ASEA 的瑞典公司達成了諮詢協議,運用 Simula 模擬加工車間。但是,尼加德和達爾隨後就意識到 Simula 也可以寫一些和模擬完全不搭邊的程式。

奧斯陸大學(University of Oslo) 教授 斯坦因·克羅達爾(Stein Krogdahl) 曾寫過關於 Simula 的發展史,稱“真正能夠促使新開發的通用語言快速發展的催化劑”就是 ref=" https:// archive.computerhistory.org /resources/text/algol/ACM_Algol_bulletin/1061032/p39-hoare.pdf ">一篇題為《記錄處理》(Record Handling)的論文,作者是英國電腦科學家 查爾斯·安東尼·理查德·霍爾(C.A.R. Hoare) 。[8] 假如你現在讀霍爾的這篇論文,你就不會懷疑這句話。當人們談及面嚮物件語言的發展史時,一定會經常提起霍爾的大名。以下內容摘自霍爾的《記錄處理》一文:

該方案設想,在程式執行期間,計算機內部存在任意數量的記錄,每條記錄都代表著程式設計師在過去、現在或未來所需的某個物件。程式對現有記錄的數量保持動態控制,並可以根據當前任務的要求建立新的記錄或刪除現有記錄。

霍爾在這片論文中並未提到子類的概念,但是達爾由衷地感謝霍爾,是他引導了兩人發現了這一概念。[9] 尼加德和達爾注意到 Simula I 的程序通常具有相同的元素,所以引入父類來執行共同元素就會非常方便。這也強化了“程序”這一概念本身可以用作父類的可能性,也就是說,並非每種型別都必須用作只有單個操作規程的程序。這就是 Simula 語言邁向通用化的第二次飛躍,此時,Simula 67 真正成為了通用程式語言。正是如此變化讓尼加德和達爾短暫地萌生了給 Simula 改名的想法,想讓人們意識到 Simula 不僅僅可以用作模擬。[10] 不過,考慮到 “Simula”這個名字的知名度已經很高了,另取名字恐怕會帶來不小的麻煩。

1967 年,尼加德和達爾與 控制資料公司(Control Data) 簽署協議,著手開發Simula 的新版本:Simula 67。同年六月份的一場會議中,來自控制資料公司、奧斯陸大學以及挪威計算機中心的代表與尼加德和達爾兩人會面,意在為這門新語言制定標準與規範。最終,會議釋出了 《Simula 67 通用基礎語言》 ,確定了該語言的發展方向。

Simula 67 編譯器的開發由若干家供應商負責。 Simula 使用者協會(The Association of Simula Users) (ASU)也隨後成立,並於每年舉辦年會。不久,Simula 67 的使用者就遍及了 23 個國家。[11]

21 世紀的 Simula 語言

人們至今還記得 Simula,是因為後來那些取代它的程式語言都受到了它的巨大影響。到了今天,你很難找到還在使用 Simula 寫程式的人,但是這並不意味著 Simula 已經從這個世界上消失了。得益於GNU cim,人們在今天依然能夠編寫和執行 Simula 程式。

cim 編譯器遵循 1986 年修訂後的 Simula 標準,基本上也就是 Simula 67 版本。你可以用它編寫類、子類以及虛擬方法,就像是在使用 Simula 67 一樣。所以,用 Python 或 Ruby 輕鬆寫出短短几行面向物件的程式,你照樣也可以用 cim 寫出來:

! dogs.sim ;
Begin
    Class Dog;
        ! The cim compiler requires virtual procedures to be fully specified ;
        Virtual: Procedure bark Is Procedure bark;;
    Begin
        Procedure bark;
        Begin
            OutText("Woof!");
            OutImage;           ! Outputs a newline ;
        End;
    End;

    Dog Class Chihuahua;        ! Chihuahua is "prefixed" by Dog ;
    Begin
        Procedure bark;
        Begin
            OutText("Yap yap yap yap yap yap");
            OutImage;
        End;
    End;

    Ref (Dog) d;
    d :- new Chihuahua;         ! :- is the reference assignment operator ;
    d.bark;
End;

你可以按照下面程式碼執行程式的編譯與執行:

$ cim dogs.sim
Compiling dogs.sim:
gcc -g -O2 -c dogs.c
gcc -g -O2 -o dogs dogs.o -L/usr/local/lib -lcim
$ ./dogs
Yap yap yap yap yap yap

(你可能會注意到,cim 先將 Simula 語言編譯為 C 語言,然後傳遞給 C 語言編譯器。)

這就是 1967 年的面向物件程式設計,除了語法方面的不同,和 2019 年的面向物件程式設計並無本質區別。如果你同意我的這一觀點,你也就懂得了為什麼人們會認為 Simula 在歷史上是那麼的重要。

不過,我更想介紹一下 Simula I 的核心概念——程序模型。Simula 67 保留了程序模型,不過只有在使用 Process 類 和 Simulation 塊的時候才能呼叫。

為了表現出程序是如何執行的,我決定模擬下述場景。想象一下,有這麼一座住滿了村民的村莊,村莊的旁邊有條小河邊,小河裡有很多的魚。但是,村裡的村民卻只有一條魚竿。村民們胃口很大,每隔一個小時就餓了。他們一餓,就會拿著魚竿去釣魚。如果一位村民正在等魚竿,另一位村民自然也用不了。這樣一來,村民們就會為了釣魚排起長長的隊伍。假如村民要等五、六分鐘才能釣到一條魚,那麼這樣等下去,村民們的身體狀況就會變得越來越差。再假如,一位村民已經到了骨瘦如柴的地步,最後他可能就會餓死。

這個例子多少有些奇怪,雖然我也不說不出來為什麼我腦袋裡最先想到的是這樣的故事,但是就這樣吧。我們把村民們當作 Simula 的各個程序,觀察在有著四個村民的村莊裡,一天的模擬時間內會發生什麼。

完整程式可以通過此處 GitHub Gist 的連結獲取。

我把輸出結果的最後幾行放在了下面。我們來看看一天裡最後幾個小時發生了什麼:

1299.45: 王五餓了,要了魚竿。
1299.45: 王五正在釣魚。
1311.39: 王五釣到了一條魚。
1328.96: 趙六餓了,要了魚竿。
1328.96: 趙六正在釣魚。
1331.25: 李四餓了,要了魚竿。
1340.44: 趙六釣到了一條魚。
1340.44: 李四餓著肚子等著魚竿。
1340.44: 李四在等魚竿的時候餓死了。
1369.21: 王五餓了,要了魚竿。
1369.21: 王五正在釣魚。
1379.33: 王五釣到了一條魚。
1409.59: 趙六餓了,要了魚竿。
1409.59: 趙六正在釣魚。
1419.98: 趙六釣到了一條魚。
1427.53: 王五餓了,要了魚竿。
1427.53: 王五正在釣魚。
1437.52: 王五釣到了一條魚。

可憐的李四最後餓死了,但是他比張三要長壽,因為張三還沒到上午 7 點就餓死了。趙六和王五現在一定過得很好,因為需要魚竿的就只剩下他們兩個了。

這裡,我要說明,這個程式最重要的部分只是建立了程序(四個村民),並讓它們執行下去。各個程序操作物件(魚竿)的方式與我們今天對物件的操作方式相同。但是程式的主體部分並沒有呼叫任何方法,也沒有修改程序的任何屬性。程序本身具有內部狀態,但是這種內部狀態的改變只有程序自身才能做到。

在這個程式中,仍然有一些欄位發生了變化,這類程式設計無法直接解決純函數語言程式設計所能解決的問題。但是正如克羅達爾所注意到的那樣,“這一機制引導進行模擬的程式設計師為底層系統建立模型,生成一系列程序,每個程序表示了系統內的自然事件順序。”[12] 我們不是主要從名詞或行動者(對其他物件做事的物件)的角度來思考正在進行的程序。我們可以將程式的總控制權交予 Simula 的事件通知系統,克羅達爾稱其為 “ 時間管理器(time manager) ”。因此,儘管我們仍然在適當地改變程序,但是沒有任何程序可以假設其他程序的狀態。每個程序只能間接地與其他程序進行互動。

這種模式如何用以編寫編譯器、HTTP 伺服器以及其他內容,尚且無法確定。(另外,如果你在 Unity 遊戲引擎上編寫過遊戲,就會發現兩者十分相似。)我也承認,儘管我們有了“時間管理器”,但這可能並不完全是希基的意思,他說我們在程式中需要一個明確的時間概念。(我認為,希基想要的類似於 阿達·洛芙萊斯(Ada Lovelace) 用於區分一個變數隨時間變化產生的不同數值的上標符號 。)儘管如此,我們可以發現,面向物件程式設計前期的設計方式與我們今天所習慣的面向物件程式設計並非完全一致,我覺得這一點很有意思。我們可能會理所當然地認為,面向物件程式設計的方式千篇一律,即程式就是對事件的一長串記錄:某個物件以特定順序對其他物件產生作用。Simula I 的程序系統表明,面向物件程式設計的方式不止一種。仔細想一下,函式式語言或許是更好的設計方式,但是 Simula I 的發展告訴我們,現代面向物件程式設計被取代也很正常。

如果你喜歡這篇文章,歡迎關注推特@TwoBitHistory,也可通過RSS feed 訂閱,獲取最新文章(每四周更新一篇)。

  1. Jan Rune Holmevik, “The History of Simula,” accessed January 31, 2019, http:// campus.hesge.ch/daehne/ 2004-2005/langages/simula.htm .↩︎
  2. Ole-Johan Dahl and Kristen Nygaard, “SIMULA—An ALGOL-Based Simulation Langauge,” Communications of the ACM 9, no. 9 (September 1966): 671, accessed January 31, 2019, http:// citeseerx.ist.psu.edu/v iewdoc/download?doi=10.1.1.95.384&rep=rep1&type=pdf .↩︎
  3. Stein Krogdahl, “The Birth of Simula,” 2, accessed January 31, 2019, http:// heim.ifi.uio.no/~steink r/papers/HiNC1-webversion-simula.pdf .↩︎
  4. 出處同上。↩︎
  5. Ole-Johan Dahl and Kristen Nygaard, “The Development of the Simula Languages,” ACM SIGPLAN Notices 13, no. 8 (August 1978): 248, accessed January 31, 2019, https:// hannemyr.com/cache/knoj d_acm78.pdf .↩︎
  6. Dahl and Nygaard (1966), 676.↩︎
  7. Dahl and Nygaard (1978), 257.↩︎
  8. Krogdahl, 3.↩︎
  9. Ole-Johan Dahl, “The Birth of Object-Orientation: The Simula Languages,” 3, accessed January 31, 2019, http://www. olejohandahl.info/old/b irth-of-oo.pdf .↩︎
  10. Dahl and Nygaard (1978), 265.↩︎
  11. Holmevik.↩︎
  12. Krogdahl, 4.↩︎

via: https:// twobithistory.org/2019/ 01/31/simula.html

作者:Two-Bit History 選題: lujun9972 譯者: aREversez 校對: 校對者ID

本文由 LCTT 原創編譯,Linux中國 榮譽推出