在程式設計中,模組 (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 = "Michelle 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 來抓取套件,使用私有套件不會很困難。
選擇軟體授權模式
如果只是練習用的或私有的套件,其實不太需要理會套件的授權,程式碼就是自己的。不過,了解軟體授權模式,對於程式設計者來說,不論是要使用他人的套件或是撰寫自己的套件,仍然有其重要性。筆者本身也不是法律專家,幸好自由軟體鑄造場的法律專欄整理了許多自由軟體授權相關資料,有需要的讀者可以自行前往參考。