Module

模組 (module) 是較新的程式語言都會有的概念。通常程式碼一多,不會將程式碼都寫在同一個檔案中,因為造成協同管理與版本控制的不便。然而,這些分開的檔案,要如何描述在程式語言中呢?

在較早的程式語言中,會用包含 (include) 的方式將檔案嵌套在一起,變成一個超大的檔案,因為畢竟那就是編譯二進制檔案 (binary file) 完後的樣子。但是這種方式會把變數、函式等等的名稱都混淆在一起,根本無法辨認。所以一些改良後的程式語言就發明了名稱空間。

Namespace

名稱空間是一種解決方案,透過此一語法的包裹,能夠替其中的名稱加上前綴 (prefix),例如 my_func 變成類似 __abc_my_func 的樣子。除了層層包裹,再透過設計一些可見性 (visibility) 概念,就可以將不想看見的名稱都編上一個雜湊碼 (hash code) 避免重複就行了。

可見性分為基本的兩種:公開 (public) 與私有 (private),即向其他名稱空間公開,或僅向相同名稱空間公開。當然這些只是語義上的識別,真正的二進制檔案中全都是可見的,只是都編上了雜湊前綴,以避免重複和識別。各種程式語言的可見性語義稍有不同,例如在 C++ 中 protect 關鍵字表示名稱僅在繼承後的類型中可見。

在名稱空間的改良下,原始碼檔案可以用一個名稱空間包裹,以表示該檔案是一個作用範圍。在更後期的程式語言中,名稱空間會造成閱讀程式馬上的不便,因此更簡單的方式便是,將原始碼檔案與目錄 (directory) 直接當作名稱空間即可,並稱之為模組。

Module

模組以檔案與目錄的關係建構,不過一些不同的程式語言仍有些微的差異。以下是專有名詞:

  • 根模組 (root module):整個專案的起始模組。
  • 導入 (import):引用來自其他模組的名稱。
  • 遞迴導入 (recursive import):兩模組包含互相引用,或導入順序形成閉環。
  • 分支 (branch):樹狀結構的非末端,通常只有目錄的位置。
  • 子模組 (child module):下一層模組,只有分支會包含子模組。
  • 父模組 (parent module):上一層模組,父模組就是分支。

關於定義分支,如 Python 中使用 __init__.py 特殊檔案名稱來表示與目錄同名的模組;過去的 Rust 則是使用與目錄同名的檔案名稱,不過現在使用 mod.rs 來代表,因為 mod 是關鍵字不會重複,修改模組名稱就不用跟著改。

關於模組的搜尋方式,如 Python 是動態語言,所以僅在導入時才會搜尋並快取,最初執行或拿來導入的地方作為根模組,且不可以遞迴導入,必須是樹狀相依。另外 Python 在導入時能使用 . 和更多 .. 作為前綴,表示上一層和更上層模組,但是由於必須避開遞迴導入,盡量使用從根模組開始的全名描述。

Rust 則是將可執行程式 binary crate 的根模組設定為 main.rs,程式庫 library crate 的根模組設定為 lib.rs,且必須在根模組與分支中用 mod 關鍵字列出全部子模組的名稱。其中根模組使用關鍵字 crate 表示,當前模組使用 self 關鍵字表示,上一層模組則是用 super 關鍵字。而且由於有實現真正的前向引用 (forward reference),Rust 不會被導入的相依性影響,可以比較沒負擔的全部導入可見名稱。不過重複名稱仍以最後導入的為主,且引用上不可以有岐義。

Visibility

可見性在模組間的處理,除了用關鍵字 public 等直接定義,如 Python 沒有可見性設計的程式語言會使用 _ 底線字元作為前綴,來表示僅限內部引用,不過語義上仍沒有強制力,使用者仍可刻意使用。對程式庫來說,這些「可見的部份」其實就是應用程式介面 (Application Programing Interface, API),意思是使用者能夠在程式庫中使用的名稱。

藉由導入,重新出現的名稱可作為一種別名 (alias),這對 API 設計有幫助。程式庫的使用者可以比較輕鬆的引入想要的名稱,而非親自從個別的模組導入。但是也會產生一些問題,如 Python 其實可以任意導入一個模組已導入的其他名稱,使得 API 看起來很亂,所以設計了 __all__ 名稱清單列舉 API 提供的名稱,不過仍沒有強制性。

而設計了可見性標示的程式語言,有益於透過分析程式碼的工具將「可見」的名稱製作成 API 說明文件,也算是盡其優勢。