Nim 語言程式教學:模組 (Module) 和套件 (Package)

PUBLISHED ON APR 19, 2018 — PROGRAMMING
FacebookTwitter LinkedIn LINE Skype EverNote GMail Email Email

    在程式設計中,模組 (module) 和套件 (package) 會隨著情境而有不同的意義。模組原先來自於模組化開發 (modular development),意指將軟體拆成許多子部件 (subpart),後來就用來指可分享的程式碼的集合,即元件 (component);在某些程式語言中,像是 Java、Go、Dart 等,則稱為套件。

    在 Nim 語言中,模組指的是單一的 Nim 檔案;而套件由一個或多個模組組成,是 Nim 語言分享程式碼的方法。本文將介紹 Nim 語言的模組和套件。

    模組

    每一個單一的 Nim 檔案,都視為一個 Nim 模組。由於我們先前的 Nim 程式碼都在同一個檔案中執行,模組對我們來說沒有特別的意義。隨著程式碼變大,我們會將程式碼拆成數個模組,以利於維護專案;更重要的是,模組提供可視度 (scope),可將程式碼分為公開的 (public) 和私有的 (private)。

    import

    一般來說,使用 import 來引入模組。以下實例是我們先前撰寫的 Point 物件,位於 point.nim 模組中:

    # point.nim
    type
      # Public class.
      Point* = ref object
        # Private fields.
        px: float
        py: float
    
    # Public getters.
    method x*(p: Point): float {.base.} =
      p.px
    
    method y*(p: Point): float {.base.} =
      p.py
    
    # Private setters.
    method `x=`(p: Point, x: float) {.base.} =
      p.px = x
    
    method `y=`(p: Point, y: float) {.base.} =
      p.py = y
    
    proc newPoint*(x: float, y: float): Point =
      new(result)
      result.x = x
      result.y = y
    

    接著,我們可以從主程式 (main.nim) 中使用 import 引入 Point 模組,兩個模組位於同一個資料夾:

    # main.nim
    import point
    
    var p = newPoint(3, 4)
    assert(p.x == 3)
    assert(p.y == 4)
    

    編譯此程式並執行:

    $ nim c --run main.nim point.nim
    

    在我們先前的例子中,我們的 setter 是私有的,從外部呼叫 setters 會引發錯誤:

    # main.nim
    import point
    
    var p = newPoint(0, 0)
    
    # Error
    p.x = 3
    p.y = 4
    
    assert(p.x == 3)
    assert(p.y == 4)
    

    include

    Nim 另外提供 include 來含入模組,可以將 include 想成將該模組的程式碼整個複雜貼上的此檔案中,即使是私有方法也可以引用。我們延續上例,改寫如下:

    # main.nim
    include point
    
    var p = newPoint(0, 0)
    
    # No error.
    p.x = 3
    p.y = 4
    
    assert(p.x == 3)
    assert(p.y == 4)
    

    這時候,可以使用私有的 setters 而不會出錯。

    實際上,我們很少用 include,大部分都用 import,因為 include 可能會不慎引入不必要的私有方法;含入多個模組時,可能會相互覆寫程式碼而造成非預期的 bug。

    套件

    建立套件

    對於程式語言社群來說,透過套件,可以分享程式碼,達到程式碼重覆利用的目的,我們不需要自行撰寫一些基礎的套件,可以直接撰寫我們感興趣的核心功能;在典型的程式開發情境中,約有 15-20 % 的功能需要重新撰寫;而 80% 左右的功能可藉由引入第三方套件來解決。對於 Nim 社群來說,由於 Nim 社群相對較小,可用的套件不多,要達到這樣的理想仍然有一段路要走。

    Nim 語言的套件管理程式稱為 Nimble,同樣也是以 Nim 寫成,其主程式為 nimble。接著,我們由實際的 Nim 套件來觀摩如何撰寫 Nim 套件。首先,建立專案架構:

    註:讀者可到 nimalgo 專案 觀看完整的內容。

    $ mkdir -p nimalgo/nimalgo
    $ cd nimalgo
    $ nimble init
    

    目前 Nim 套件建議套件原始碼放在同名的子目錄中,我們即依照此建議來做。輸入 nimble init 時,系統會詢問數個問題,按照實際情形回答即可。設定檔之後可以再修改,修改也不困難,讀者不用太擔心答錯。Nim 套件的設定檔為 package.nimble,在本例中,為 *nimalgo.nimble*。設定檔的語法為 Nimble Script,實例如下:

    # Package
    version       = "0.1.0"
    author        = "Michael Chen"
    description   = "Common data structures and algorithms in Nim."
    license       = "MIT"
    
    skipDirs = @["tests"]
    
    # Dependencies
    requires "nim >= 0.17.2"
    
    task test, "Run tests":
      withDir "tests":
        exec "nim c -r denseTest"
    

    一開始會有一些套件相關的敘述和版本號,依照各套件實際的情形撰寫即可。skipDirs 指的是編譯套件時會排除的資料夾,一般會將測試程式排除在外。接下來描述套件相依性,在本例中,我們僅依賴 nim 本身。接著,我們新增一個任務,這個任務會執行測試程式,之後可以用 nimble test 來簡化輸入指令的動作。

    另外,還會有一個 package.nim 模組,像是本例的 *nimalgo.nim*,我們通常不把實際的功能寫在這個模組中,而這個模組僅用來引入其他模組,如下例:

    import nimalgo/dense
    
    export dense
    

    實際的程式碼寫在 nimalgo/dense.nim 中,讀者可自行前往觀看。

    安裝套件

    延續上例,假若我們現在要安裝 nimalgo 套件,輸入 nimble install 指令即可:

    $ nimble install https://github.com/cwchentw/nimalgo.git
    

    若有發布的套件,則可以直接輸入套件名稱:

    $ nimble install nimalgo
    

    不一定每次撰寫 Nim 套件都要發布,有時候可能套件還不夠成熟,不到實用的地步;有時候,為了某些因素,我們不想公開自己的私有套件。由於 Nimble 會呼叫 Git 或 Mercurial 來抓取套件,使用私有套件不會很困難。

    選擇軟體授權模式

    如果只是練習用的或私有的套件,其實不太需要理會套件的授權,程式碼就是自己的。不過,了解軟體授權模式,對於程式設計者來說,不論是要使用他人的套件或是撰寫自己的套件,仍然有其重要性。筆者本身也不是法律專家,幸好自由軟體鑄造場的法律專欄整理了許多自由軟體授權相關資料,有需要的讀者可以自行前往參考。