Ada 是一種程式語言,它將泛型——一種在 CLU 中被探討、並在 1970 年代的研究系統中出現的概念——變成了一個一級、標準化的廣泛部署的系統語言功能。它吸收了 Pascal 的子範圍型別和 Pascal 與 CLU 的變體記錄,並將其深化為一個具有非凡嚴謹性的型別系統。它將並行性內建於語言規格中,而非僅限於函式庫。它強制要求介面與實作的分離。它從 C.A.R. Hoare 關於通訊程序的形式化工作等眾多來源汲取靈感,並產生了一種任務通訊模型,與 Go 在數十年後透過不同途徑普及的通道(channels)屬於同一大類。Ada 最初於 1980 年被標準化為 MIL-STD-1815,標準的 Ada 83 於 1983 年問世。後續的修訂在 1995 年增加了保護物件(protected objects),2005 年增加了存取型別的非空排除,2012 年增加了合約功能,並在 2022 年進行了進一步的完善。這是一種被業界斥責為冗長、官僚且過時的語言。然而,它也以一種令軟體進步的典型敘事相形見絀的直接性,以非凡的精確度預見了現代語言現正獨立獲取的許多安全功能。

Ada 並不有名。它不是熱情洋溢的會議演講或令人屏息的部落格文章的主題。它沒有一位能就程式設計哲學發表主題演講的魅力創始人。它有一個自 1983 年以來已修訂四次的正式標準。它存在於許多主要商用飛機和航空電子系統的軟體中。它有一套在 1970 年代末期政府合約下做出的設計決策,後來的語言或多或少地獨立地重新發現了這些決策。而且,在那些認識它的人眼中,它被譽為「說不」的語言——一種其編譯器強制執行合法性、可見性、型別檢查和一定程度的安全檢查的語言,而大多數語言將這些留給慣例或工具;它讓程式設計師命名他們的意思,將歧義視為錯誤而非特徵。這些特質長期以來被認為是它的弱點。然而,經過檢視,它們正是許多被稱為現代的語言現正努力獲取的特質。

要理解 Ada 的存在,需要理解催生它的特定危機——這場危機並非源於電腦科學,而是源於採購。美國國防部在 1970 年代初期調查其武器系統、後勤基礎設施和指揮控制設備的軟體時,就遇到了這個危機。調查發現的並非軟體單一文化,而是恰恰相反:在國防部系統中,有超過四百五十種不同的程式語言和方言被廣泛使用,其中大多數與特定承包商或特定開發時代綁定,很少能互通,許多除了原始作者之外無人能維護。1 指導飛彈的軟體無法由維護導航艦船軟體的人員來維護。排程後勤的軟體無法與處理通訊的軟體共享程式碼。這些語言的累積方式就像技術債一樣:無形、漸進,每個單獨的決策在局部是合理的,但總體卻是災難性的。

國防部的回應,對於一個政府機構而言,異常地複雜。它沒有簡單地強制採用現有語言——COBOL、Fortran 和 PL/1 都曾被考慮但被拒絕——而是進行了一個持續約五年的需求流程,並產生了一系列越來越精確的文件:Strawman、Woodenman、Tinman、Ironman 和最終的 Steelman,每一份都精煉並收緊了國防部程式語言必須具備的規格。1978 年發布的 Steelman 文件是一份卓越的工程需求文獻:它沒有指定一種語言,而是描述了一種語言必須具備的屬性——這些屬性源自國防部現有軟體資產的實際故障模式。它要求模組化,並明確分離介面與實作。它要求強型別、靜態型別。它要求內建對並行任務的支援。它要求一致的例外處理機制。它要求語言獨立於機器。它要求程式碼能被非作者本人閱讀。它要求語言使程式驗證變得可行。這些並非理想化的偏好,而是源自缺乏這些特質的程式所導致的後果。2

這些要求現在很容易受到讚賞,因為業界已經讓它們聽起來像常識。但在 1970 年代,它們不像現在看起來那麼明顯。它們必須從昂貴的經驗中提煉出來。

1979 年,一場由四個決賽隊伍——分別被指定為 Green、Red、Blue 和 Yellow——進行的競賽產生了獲勝者:由 Jean Ichbiah 在法國 CII Honeywell Bull 的團隊領導的 Green 設計。獲勝的設計被命名為 Ada,以紀念 19 世紀的數學家 Augusta Ada King,她是寫下被普遍認為是第一份為機械計算設計的演算法的人。選擇這個名字是故意的:國防部想要一個名字而不是縮寫,並希望表彰一位在當時女性鮮少受到讚揚的領域中的女性。Ichbiah 隨標準附上了一份闡述文件——對主要設計決策及其背後原因的廣泛解釋——這份文件對於任何閱讀它的人來說,仍然是關於程式語言設計實際目的的最清晰的說明之一。

Ada 的出現並非孤立。它的設計者精通當時的各種研究語言:Barbara Liskov 的 CLU,它開創了參數化抽象、結構化例外處理和型別安全變體形式;Niklaus Wirth 的 Pascal,它引入了子範圍型別和變體記錄;Hoare 的並行通訊形式化模型;以及 Fortran、COBOL 和催生了 Steelman 文件以解決危機的系統語言所帶來的慘痛教訓。Ichbiah 的團隊所做的並非從零開始發明,而是進行了綜合——將研究文獻中最好的想法融合,首次將它們鑄造成一種單一的、標準化的、旨在進行大規模系統工作的語言,並有正式標準支持、附有闡述文件,且從一開始就將驗證、安全和長期維護作為首要考量。個別的組成部分是存在的,但組合起來卻是全新的。

Ada 架構的核心是套件(package):一個由規格(specification)和,若有需要,由實作(body)組成的編譯單元,兩者物理上分離,編譯器強制執行它們之間的關係。規格是合約——它聲明了套件提供了什麼:型別、子程式、常數,任何套件向外界公開的內容。實作是履行合約的程式碼。規格是客戶端程式碼所看到的。實作對客戶端程式碼是不可見的,可以獨立編譯,在不重新編譯僅依賴規格的程式碼的情況下進行更改,並在規格及其行為假設保持兼容的情況下進行替換。這種分離不是風格建議,也不是由 linter 強制執行的。它是語言的結構屬性:試圖存取規格中未聲明的任何內容的客戶端程式碼將無法編譯,因為編譯器不允許它看到實作。3

這很重要,並非因為沒有其他語言有模組系統,而是因為 Ada 將一個特別強大且明確的版本作為內建的結構特性——並且很早就這樣做了。Java 的套件並非如此:它們是具有存取修飾符的命名空間機制,但實作對反射、子類別以及同一套件內可能未預料到的程式碼是可見的。Python 的模組也不是這樣:它們是檔案,介面與實作之間沒有正式分離,沒有編譯器來強制執行界線。JavaScript 的模組系統——於 2015 年推出,比 Ada 晚了三十二年——提供了 import 和 export,但沒有機制讓一個型別擁有一個對匯入者隱藏其表示形式的規格。C 的標頭檔近似於這種分離,但不完美:預處理器可能會洩漏實作細節,並且標頭檔與原始檔之間的一致性僅部分強制執行。Go 的匯出識別符——大寫名稱可見,小寫名稱不可見——實現了類似的效果,但沒有正式的規格-實作分離。Rust 的模組系統加上 pub 可見性規則更接近。Ada 的套件設計不是模組化的唯一良好解決方案。然而,它是一個早期且異常嚴謹的解決方案,並且令人驚訝的是,許多後來的解決方案都朝著這個方向發展。

從套件架構自然衍生的私有型別機制,賦予 Ada 一種比許多物件導向語言中普通存取修飾符更強大的封裝形式。在 Ada 套件規格中聲明為私有的型別,可以透過名稱看到——客戶端程式碼可以宣告該型別的變數,將它們傳遞給子程式,從函式中返回它們——但其表示形式完全不透明。客戶端不知道該型別是記錄、陣列、整數、指標還是其他任何東西。它無法存取欄位,因為它不知道有欄位。套件的設計者決定該型別上存在哪些操作,並在規格中聲明它們,其餘世界只能使用這些操作。預定義的操作——賦值、相等性——仍然存在,除非設計者進一步限制它們,但表示形式本身是不可見的。這不是 Java 的 private 關鍵字意義上的存取控制,它阻止了直接存取,同時將表示形式對繼承、反射以及編譯器本身在檢查子類別兼容性時可見。這是表示形式的不可見性:型別的結構實際上並未出現在客戶端程式碼編譯所依據的文字中。4

重點不在於每個後來的語言都在不成功地試圖重現 Ada 的套件系統。而是 Ada 將抽象界線視為首要的語義關注點,而不是禮儀問題——以及「存取修飾符」與真正的表示形式隱藏之間的距離,比物件導向主流長期以來所假設的要大。C# 透過記錄型別、init-only 屬性和密封類別逐步建構真正的封裝;Java 在 2021 年的 Java 16 中才姍姍來遲地引入記錄——這些都不是 Ada 的衍生品。它們是獨立發現了與 Ada 設計者從一開始就內建的相同洞見。旅程是相同的;路線不同。

Ada 的型別系統在 1983 年時,與當時生產環境中使用的任何東西都不同,而且在其本質上,仍然比今天大多數語言更具表現力。組織它的區別在於型別(type)和子型別(subtype)之間——不是物件導向中型別擴展另一個型別的意義,而是數學意義上的受限集合。需要表示個人年齡的型別的 Ada 程式設計師不會去尋找 int 並加上註解。他們會寫 `type Age is range 0 .. 150`,編譯器將在沒有進一步指示的情況下生成一個值必須在此範圍內的型別,其算術運算在執行時會對此範圍進行檢查,除非程式設計師明確選擇了未檢查的操作,並且它是一個與程式中所有其他整數型別不同的獨立型別,因此將日曆年傳遞給預期年齡的地方會是編譯時錯誤,而不是執行時驚喜或靜默的錯誤答案。5

將型別限制在範圍內的觀念並不新鮮。Pascal 在十年前就有子範圍型別。Ada 所增加的是組合:範圍約束與強型別區分結合,因此具有相同範圍但不同意義的兩個型別不會混淆;列舉型別是命名值而不是偽裝的整數;定點型別直接在型別系統中編碼數值精度。該語言鼓勵程式設計師不僅要說明值的規模,還要說明它是什麼類型的東西。這仍然是一個活躍的問題。Haskell 的 newtype 包裝提供了非常相似的機制,透過不同的路徑到達相同的目的地。Rust 對 newtype 包裝的偏愛源於相同的認識。TypeScript 的品牌型別——一種涉及幻象型別參數的變通模式,廣泛使用正是因為 TypeScript 的結構型別系統否則會將所有整數合併在一起——存在是為了解決 Ada 在 1983 年命名並解決的問題。當語義上不同的值合併為單一未區分的數值或結構型別時,錯誤就很容易編寫且難以發現。這種認識是永恆的;解決方案會不斷重複。

Ada 的辨識記錄型別(discriminated record types)屬於同一個故事。辨識記錄是一種帶有變體欄位的記錄型別——一個欄位的值決定了其他欄位是否存在。一個形狀可能有一個辨識符選擇圓形或矩形;圓形有一個半徑欄位;矩形有一個寬度和高度欄位;編譯器知道對於哪個辨識符值存在哪些欄位,並且不會編譯存取矩形半徑的程式碼。這與現在所謂的代數資料型別、總和型別、標記聯合——函數式程式設計師數十年來一直倡導的、用於模擬可以代表多種事物之一的資料的正確方法的機制——非常相似。Pascal 早就有變體記錄,CLU 也有型別安全的變體形式。Ada 並沒有發明整個家族。它確實透過編譯器強制執行的欄位存取檢查深化了該機制,並提供了其早期、標準化、工業級的形式。Haskell、Rust、Swift、Kotlin 和 TypeScript 後來將總和型別建模作為其設計的核心——每個都獨立到達,每個都解決了相同的持久性問題。

Ada 的泛型可能是最清晰的案例,其中一個長期被視為現代的特性,實際上比主流故事通常承認的要早得多。Ada 中的泛型是參數化套件或子程式——一個可以透過特定型別或值實例化以產生具體套件或子程式的範本。一個泛型排序程序接受一個型別參數、一個陣列型別參數和一個比較函式參數;它可以被實例化來排序整數、字串或任何可以提供比較函式的型別。這是參數化多型:能夠編寫一次程式碼並將其應用於多個型別,由編譯器驗證每個實例化的正確性,而不是將檢查延遲到執行時或依賴於鴨子型別。CLU 在 1977 年就有了參數化抽象。Ada 將這個想法納入並於 1983 年標準化,用於大規模生產系統的語言。1979 年,由四個決賽隊伍——分別被指定為 Green、Red、Blue 和 Yellow——進行的競賽產生了獲勝者:由 Jean Ichbiah 在法國 CII Honeywell Bull 的團隊領導的 Green 設計。獲勝的設計被命名為 Ada,以紀念 19 世紀的數學家 Augusta Ada King,她是寫下被普遍認為是第一份為機械計算設計的演算法的人。選擇這個名字是故意的:國防部想要一個名字而不是縮寫,並希望表彰一位在當時女性鮮少受到讚揚的領域中的女性。Ichbiah 隨標準附上了一份闡述文件——對主要設計決策及其背後原因的廣泛解釋——這份文件對於任何閱讀它的人來說,仍然是關於程式語言設計實際目的的最清晰的說明之一。

時間順序比較很有啟發性,儘管需要謹慎。C++ 大約從 1990 年開始有了模板。Java 直到 2004 年才有了泛型——比 Ada 晚了二十一年——而 Java 的泛型在推出時是透過型別擦除實現的,這意味著型別參數在編譯時存在,但在程式執行前被移除,阻止了 Ada 泛型所能實現的執行時型別特化。C# 在 2005 年推出了泛型,並具備了在執行時保留型別資訊的重現泛型(reified generics)。Go 在 2022 年的 1.18 版本之前根本沒有泛型——比 Ada 晚了三十九年——而且在 Go 使用的最初十年裡,其缺乏泛型被廣泛認為是一個重大的限制。Rust 擁有泛型和單型化(monomorphisation):每個泛型型別的實例化都會在編譯時產生一個具體的型別,在架構上比 Java 更接近 Ada 的世界。Ada 的泛型系統不僅僅是 Rust 的原始版本,Rust 也不是延遲的 Ada。這些語言以不同的周邊型別系統和不同的設計目標解決重疊的問題。可以有把握地說,Ada 在許多主流語言之前幾十年就將泛型編程視為一種嚴肅的、標準化的能力。6

Ada 的泛型形式參數比大多數現代泛型系統更具表現力。Ada 中的泛型單元不僅可以接受型別作為參數,還可以接受子程式——你可以將一個函式作為形式參數傳遞給泛型,並由編譯器驗證它具有正確的簽名——以及套件,允許泛型透過整個模組而不是僅僅一個型別來參數化。這實際上是一種對型別建構子和模組結構的抽象——Haskell 的型別類別透過不同的機制達到這種表現力,而 Rust 的 trait 系統也接近於此。C++ 的概念(concepts),在 2020 年的 C++20 中加入,允許泛型型別參數受到其操作需求的約束——這正是 Ada 的泛型形式型別參數一直以來所指定的。它們之間有家族相似性,但並非完全相同。後來的語言透過其他方式達到了類似的表現力領域,通常是在不知道 Ada 已經在那裡的情況下。

Ada 的並行模型是其設計雄心與業界後來的習慣之間差異特別明顯的地方之一。Ada 具有語言級別的任務(tasks)。它有會合(rendezvous):一種同步的進入呼叫,其中一個任務呼叫一個進入點,另一個任務接受它,通訊發生在一個定義好的會合點。後來它增加了保護物件(protected objects),提供了受控的共享狀態存取,並強制執行互斥和受保護的進入條件。Ada 並非在 1983 年就 somehow 解決了所有並行程式設計問題。但它在許多流行語言尚未認真對待並行性時,就將其作為語言設計問題來認真對待——而 2000 年代和 2010 年代的並行危機,其中共享的可變狀態因多核心處理器而變得災難性,產生了測試無法可靠檢測的死鎖和競態條件,這並非不可預見。它在一定程度上是業界偏好比 Ada 所提供的更缺乏紀律的並行方法所導致的結果。

Ada 任務是語言級別的建構:使用 `task` 聲明,由 Ada 執行環境調度,透過會合、保護物件和其他語言提供的機制進行通訊。會合是一個同步通訊點:呼叫任務命名它希望使用的進入點,接受任務在 `accept` 語句中命名相同的進入點,並且在兩者都準備好之前,任何一方都無法繼續。通訊發生在會合點;設計沒有提供任何機制讓一個任務能夠進入另一個任務並修改其狀態。Go 的通道和 Ada 的會合在更廣泛的傳統中是近親,該傳統源自 Hoare 1978 年的通訊順序程序(Communicating Sequential Processes)形式化。Go 的自身血統明確地追溯到 Rob Pike 在為 Go 貢獻通道模型之前開發的 Newsqueak 和 Limbo 語言。與 Ada 的家族相似性是真實的,但 neither 是對方的複製品:兩者都是同一概念祖先的獨立後代,並且都透過不同的路徑達到了生產級別的解決方案。7

Ada 95 的保護物件解決了確實需要共享狀態的情況。保護型別封裝資料並聲明對其的操作:保護過程(protected procedures),具有獨佔的讀寫存取權;保護函式(protected functions),可以同時呼叫,因為它們是唯讀的;以及保護進入點(protected entries),它們類似於過程,但包含一個屏障條件(barrier condition)——一個必須為真才能執行操作的布林表達式,呼叫任務會自動暫停直到條件滿足。執行環境強制執行過程和進入點的互斥,而無需程式設計師編寫鎖。進入點的屏障條件會在任何操作完成時重新評估,提供安全的條件等待,而無需 Java 的並行模型所需的額外條件變數訊號。Rust 的 Mutex 和 RwLock 型別以類似的方式保護資料——將狀態封裝在強制執行存取紀律的型別中——但透過函式庫而非語言建構,並且沒有屏障條件機制。Java 的 `synchronized`、`wait` 和 `notify` 是程式設計師會使用的替代方案,而這些組合很容易導致細微的錯誤:忘記同步、通知錯誤的條件、在呼叫外部程式碼時持有鎖。Ada 的保護物件使這些錯誤在結構上無法發生,而不僅僅是被勸阻。

Ada 的 SPARK 子集將並行性保證擴展到形式化證明。SPARK 限制了任務可存取狀態之間的別名(aliasing),並將子程式的副作用限制在子程式合約中聲明的範圍內。其主要的分析工具 GNATprove 使用抽象解釋和 SMT 求解器來數學上而非經驗上地證明程式滿足其合約、陣列存取在界限內、整數運算不會溢位,以及任務不會競用共享狀態。Rust 的借用檢查器在編譯時阻止了一類記憶體安全錯誤,這是一個相關但更窄的保證:它阻止了使用後釋放(use-after-free)、雙重釋放(double-free)和某些類型的別名變異,但它並沒有形式化地證明程式邏輯的正確性。SPARK 在存在必要的合約和註解的情況下,可以證明沒有執行時錯誤,並驗證使用者聲明的函數屬性。這種比較不是一場必須決出勝負的競賽。它提醒我們,「編譯時安全」代表了幾個不同層次的雄心,而 Ada 的生態系統長期以來一直包含在一個特別嚴苛的層級上運作的工具——在生產安全關鍵系統中,比 Rust 作為一個專案存在的時間還要早。8

Ada 2012 在語言中增加了合約:前置條件、後置條件和型別不變式,可以用 Ada 的語法表達,在啟用斷言檢查時在執行時檢查,並可用作 SPARK 中的證明義務。子程式的前置條件是子程式呼叫時必須滿足的布林表達式;其後置條件是子程式返回時必須滿足的布林表達式;型別不變式是對於任何對外部程式碼可見的值,該值必須滿足的屬性。這些不是執行時檢查意義上的斷言,因為它們可能會在生產環境中被禁用。它們是規格:機器可讀的聲明,說明子程式的要求和保證,並且可以被 SPARK 工具鏈在不執行程式的情況下進行驗證。9

由 Bertrand Meyer 在 1986 年的 Eiffel 語言中命名和系統化的「合約設計」(Design by Contract)是此機制的概念基礎。Ada 並沒有發明術語或基礎概念。Eiffel 最早擁有它。Ada 2012 所做的是在一個成熟的語言中標準化合約表示法,該語言擁有現有的使用者群、正式標準和能夠對這些合約做更多事情(而不僅僅是在執行時測試它們)的驗證工具鏈。業界對此類功能的進展並不均衡。C++ 在委員會意見不合後推遲了 C++20 標準的合約提案;一個修訂後的提案(P2900)最終在 C++26 中被採納,於 2026 年發布——比 Ada 的原始標準晚了四十三年,比 Ada 2012 的合約晚了十四年。Java 在語言中從未有過合約;Java 中的 DbC 是透過函式庫、Javadoc 約定或近似後置條件檢查的 JUnit 測試來完成的。Python 的型別提示系統,於 2015 年在 3.5 版本中引入並持續擴展,是一種部分合約方法:它指定了輸入和輸出的型別,但沒有行為屬性。Rust 的 trait 邊界和型別約束是另一種部分方法。這些都沒有提供 Ada 2012 所提供的:一種標準的、編譯器整合的表示法,用於聲明子程式的要求和保證,可在開發期間在執行時檢查,並可由隨語言一起發布的工具鏈進行靜態證明。

教訓不是所有這些語言都在秘密地試圖變成 Ada。而是讓期望明確化的願望——一個函式需要什麼,它承諾什麼,一個值可能是什麼——是永恆的,而 Ada 將這種願望的一個異常直接的版本融入了其標準語言模型。可空參考危機——空值是「十億美元的錯誤」,Tony Hoare 自稱其最糟糕的設計錯誤——說明了相同的模式。從一開始,Ada 就比 C 處於更有利的地位:解引用一個空存取值會引發執行時的 `Constraint_Error`,這是一個定義明確且可恢復的行為,不像 C 在空值解引用時會產生未定義行為。但 Ada 並非從一開始就具備完全的編譯時空安全。存取值預設為空;這是原始設計的一部分。Ada 2005 引入了非空存取型別註解,允許程式設計師聲明特定存取值永遠不能為空,並由編譯器在靜態上強制執行限制。C# 的可空參考型別後來從不同的預設值達到了相同的洞見。兩種語言都達到了相同的架構洞見——空值性應該在型別中可見——但沒有一種從一開始就將其設為預設值。Ada 從一開始就提供的是更安全的失敗模式:引發異常而不是損壞的記憶體。

Ada 的例外模型,存在於 1983 年的標準中,是最早的結構化例外處理的生產實現之一——這種觀念認為例外不僅僅是跳轉到錯誤處理器,而是引發、透過定義的呼叫堆疊傳播,並在語法上與建立它的區塊或子程式相關聯的例外處理器中處理的事件。CLU 早已探索過結構化例外處理,Ada 的設計者也意識到這項工作。Ada 所做的是將該機制引入一個標準化的系統語言,並達到工業規模,要求例外在使用前必須聲明,處理器必須與特定範圍相關聯,並且傳播規則必須精確定義。C++ 大約在 1990 年採用了結構化例外處理,比 Ada 晚了約七年。Java 在一個重要方面比 Ada 走得更遠:Java 的檢查型例外要求某些例外類型必須被捕獲或在方法的 `throws` 子句中聲明,使得呼叫者處理失敗的責任成為函式編譯介面的一部分。Ada 沒有等效的機制——Ada 例外會自由地透過呼叫堆疊傳播,子程式的規格沒有說明它可能引發哪些例外。Java 的檢查型例外設計從 Ada 借鑒較少,更多是從 CLU 的訊號機制和 Modula-3 的例外聲明借鑒,而且這個實驗從一開始就備受爭議:檢查型例外被廣泛認為是 Java 的設計失誤之一,Scala 和 Kotlin 完全取消了它們,業界從未就編譯器是否應該在呼叫點強制執行例外意識的問題達成一致。10

Rust 選擇了移除例外,這是一個相關的選擇:錯誤是值,從函式返回一個 `Result` 型別,函式是否可能失敗的問題在其返回型別中表達,而不是在單獨的例外機制中。這是對同一潛在問題的不同解決方案——呼叫者必須知道被呼叫的函式是否可能失敗以及以何種方式失敗——並且它得出了 Ada 自己的例外模型未能達到的結論:在 Ada 中,就像在 C++ 和 Python 中一樣,例外是一個隱藏的通道,透過呼叫堆疊傳播而不在子程式規格中出現,呼叫者可以完全忘記它們,直到它們出現。Ada 的貢獻不是關閉那個通道,而是結構化它——用一個傳播規則已定義、處理器已範圍化、行為可預測的機制取代 `setjmp`/`longjmp` 的原始跳轉和訊號處理器的歧義。這種結構化——建立在 CLU 早期工作之上,並將其擴展到為複雜系統設計的語言——是每個後續例外系統的基礎,即使是那些比 Ada 更進一步的系統。

Ada 的附錄(annexes)——標準中定義的語言核心的可選擴展,需要單獨的編譯器認證——代表了一種業界可能從中受益的設計決策。附錄為特定領域定義了功能:系統程式設計、即時系統、分散式系統、資訊系統、數值計算、高完整性系統。實作系統程式設計附錄的編譯器必須實作某些預定義屬性和表示法子句。實作即時系統附錄的編譯器必須以標準指定的方式實作任務優先級、調度策略和時間限制。編譯器符合附錄的認證可以透過 Ada 合規性評估測試套件獨立驗證。編譯器的使用者確切地知道它支援什麼和不支援什麼,因為支援是針對正式標準的可文件化、可測試的聲明,而不是編譯器作者選擇實作的任何內容的湧現屬性。11

這種模型從未在主流語言文化中流行起來,主流語言文化傾向於接受標準、實作和平台行為之間鬆散得多的關係。JavaScript 的功能支援是透過相容性表追蹤的,因為標準和實作是兩個沒有正式耦合的世界。Python 的標準函式庫覆蓋範圍在不同實作之間有所不同——CPython、PyPy 和 MicroPython 是自稱為 Python 的不同東西。Rust 的功能集是正式穩定或不穩定的,但兩者之間的界線會隨著時間移動,並且沒有可認證合規性的概念。Ada 的附錄模型是標準應該是一個合約——可測試、可認證、有用,正是因為它不僅指定了允許什麼,還指定了要求什麼。在認證要求嚴格的領域,這種差異很重要。航空軟體認證的 DO-178C 標準要求文件和流程證據,而一個正式標準化且具有可認證編譯器合規性的語言可以更容易地生成這些。C 和 C++ 可以滿足相同的認證要求——並且確實經常滿足——但需要額外的流程文件和工具。Ada 在此背景下的優勢不是獨特性,而是契合度:該語言標準在一定程度上是為了滿足形式認證的需求而設計的。

Ada 的影響力為何總是被低估的問題有幾個答案,沒有一個完全令人滿意。最直接的是制度性的:Ada 是一種政府語言,透過商業軟體行業未密切關注的流程進行採購。C++、Java 和 Python 的設計者並沒有閱讀 Steelman 文件。他們正在解決眼前的問題——讓 C 更安全、讓軟體物件工作、讓腳本編寫變得簡單——他們的解決方案與 Ada 的解決方案趨同,不是因為他們在追隨 Ada,而是因為問題是相同的問題,而好的解決方案就是好的解決方案。

第二個答案是美學上的。Ada 的語法冗長,對於有 C 背景的程式設計師來說很不舒服。`if X then Y; end if;` 而不是 `if (x) { y; }`。`procedure Sort (A : in out Array_Type)` 而不是 `void sort(int* a)`。這種冗長是故意的——Ichbiah 希望程式碼能被非作者本人閱讀,而 Steelman 文件也明確要求了這一點——但它被體驗為官僚和不「駭客」風格,並且在 1980 年代和 1990 年代形成的程式設計文化圍繞著簡潔就是精緻的命題。Ada 是採購官的語言。C 是理解機器的語言。文化判決早已做出,且未曾實質性地重新審視。

第三個答案是 Ada 的部署領域意味著 Ada 的成功是不可見的。一個編譯無誤、運行無競態條件、並經正式驗證滿足其規格的軟體專案不會產生事件報告、事後分析或關於出了什麼問題的會議演講。Ada 的成功——未墜毀的飛機、未失效的鐵路信號系統、未誤導的飛彈導引軟體——正是因為它們是成功,所以才不可見。那些明顯失敗的語言,在緩衝區溢位、空指標例外、資料競態和安全漏洞方面,產生了討論。Ada 產生了可靠的軟體,而可靠的軟體不會產生討論。

這並不是說所有現代語言都抄襲了 Ada,或者 Ada 的設計者應得而未得的讚譽。大多數趨同確實是獨立的:Rust 的設計者沒有從 Ada 的存取型別規則中衍生出借用檢查器;Go 的設計者沒有從 Ada 的會合中衍生出通道;TypeScript 的設計者沒有從 Ada 的變體記錄中衍生出辨識聯合。趨同是真實的,但它是對真實問題的正確解決方案的趨同,而不是抄襲。Ada 的設計者很早就發現了這些問題,並且以非凡的清晰度發現了它們,因為他們是為一個問題的後果已經造成死亡並且如果解決方案錯誤將會造成更多死亡的環境而設計的。而且他們並非在真空中工作:他們閱讀了 CLU,閱讀了 Pascal,閱讀了 Hoare。他們自己也是一個傳承的受益者。他們所做的就是認真對待那個傳承,並將其建構成一個標準。

Ada 所展示的並非它應該被更廣泛地使用——儘管在任何軟體可靠性至關重要的領域使用它的論點比業界給予它的信譽更強——而是現代語言設計正在解決的問題是老問題,而現代語言正在發現的解決方案是老解決方案。空值參考需要顯式註解,並行性需要語言級別的強制執行而非函式庫級別的慣例,介面和實作應在結構上分離,型別系統應編碼領域約束而非僅僅機器表示,泛型程式碼應在實例化時進行驗證——這些都不是 2010 年代或 2020 年代的見解。它們是 1970 年代和 1980 年代的見解,是為了應對軟體故障而形成的,這些故障的後果足夠具體,以至於負責預防它們的人願意為一場持續五年的語言競賽付出代價。

業界花了四十年時間構建語言,其最佳功能獨立地趨同於 Ada 幾十年前就確立的立場。同時,業界也花了同樣的四十年時間將 Ada 描述為無關緊要。第一個觀察和第二個觀察之間存在一種業界尚未完全承認的緊張關係,而 Ada——部署在頭頂的飛機、軌道旁的鐵路信號、全球的航空航太和太空系統中——則無需承認這種緊張關係,它忙於正確運行,無暇顧及它是否受到讚賞的問題。