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

在程式設計中,模組 (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 來抓取套件,使用私有套件不會很困難。

選擇軟體授權模式

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

comments powered by Disqus